mirror of
https://github.com/esphome/esphome.git
synced 2025-11-05 09:31:54 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f27757039 | ||
|
|
532907219b | ||
|
|
eeaba74553 | ||
|
|
dd637582a4 | ||
|
|
b0d12aeea1 | ||
|
|
bdbd813455 | ||
|
|
a6fac2b175 | ||
|
|
5ce923ea90 | ||
|
|
29f0508dc2 | ||
|
|
3ffa59f0cd | ||
|
|
790d6ef94c | ||
|
|
7828f48b9a | ||
|
|
9fbb3659a6 | ||
|
|
fee446c28a | ||
|
|
1d56f0b035 | ||
|
|
341fddb9aa | ||
|
|
456824669f | ||
|
|
62f3039d82 | ||
|
|
be4c718859 | ||
|
|
c2f9ed7c59 | ||
|
|
bfac6607d1 | ||
|
|
e43dcded62 | ||
|
|
887081fd71 | ||
|
|
71ded24fce | ||
|
|
1e2a9e8348 | ||
|
|
64a3aa7092 | ||
|
|
fda8dd4ce3 | ||
|
|
1efabd27d8 | ||
|
|
caa651e55b | ||
|
|
10a6e9b4ee | ||
|
|
4b8ec44262 | ||
|
|
bd74ed4bc0 | ||
|
|
d01f296420 | ||
|
|
27112e2ace | ||
|
|
837930234f | ||
|
|
e19aa3bbe0 | ||
|
|
35b5c1ed56 | ||
|
|
c9d93ff685 | ||
|
|
fa72990a63 | ||
|
|
e5afb1c4ea | ||
|
|
73ead5f328 | ||
|
|
5c57b51378 | ||
|
|
e25935ef21 | ||
|
|
c7a52c3894 | ||
|
|
53a4689ed1 | ||
|
|
0a82e6e792 | ||
|
|
98855e4123 | ||
|
|
a91e6a6bdf | ||
|
|
8600620305 | ||
|
|
96721f305f | ||
|
|
2bf70d7d00 | ||
|
|
1d8c170f48 | ||
|
|
6009c7edb4 | ||
|
|
e3f36c033e | ||
|
|
d4eb0f1655 | ||
|
|
e20ec00071 | ||
|
|
150114d774 | ||
|
|
89dfa5ea82 | ||
|
|
97aa930ad2 | ||
|
|
2a5def10e7 | ||
|
|
969834e037 | ||
|
|
d73a44c504 | ||
|
|
8aec092ab6 | ||
|
|
4fa959ba45 | ||
|
|
b43712d78d | ||
|
|
01904a0f10 | ||
|
|
dd875e7529 | ||
|
|
f1dcf0f0b8 | ||
|
|
a045d001bf | ||
|
|
066c1022d0 | ||
|
|
59c192becc | ||
|
|
a800816750 | ||
|
|
970563e07b |
@@ -11,7 +11,30 @@ namespace adc {
|
|||||||
static const char *const TAG = "adc";
|
static const char *const TAG = "adc";
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; }
|
void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
|
||||||
|
|
||||||
|
inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
|
||||||
|
switch (pin) {
|
||||||
|
case 36:
|
||||||
|
return ADC1_CHANNEL_0;
|
||||||
|
case 37:
|
||||||
|
return ADC1_CHANNEL_1;
|
||||||
|
case 38:
|
||||||
|
return ADC1_CHANNEL_2;
|
||||||
|
case 39:
|
||||||
|
return ADC1_CHANNEL_3;
|
||||||
|
case 32:
|
||||||
|
return ADC1_CHANNEL_4;
|
||||||
|
case 33:
|
||||||
|
return ADC1_CHANNEL_5;
|
||||||
|
case 34:
|
||||||
|
return ADC1_CHANNEL_6;
|
||||||
|
case 35:
|
||||||
|
return ADC1_CHANNEL_7;
|
||||||
|
default:
|
||||||
|
return ADC1_CHANNEL_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
@@ -21,7 +44,9 @@ void ADCSensor::setup() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
analogSetPinAttenuation(this->pin_, this->attenuation_);
|
adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_);
|
||||||
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
|
adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void ADCSensor::dump_config() {
|
void ADCSensor::dump_config() {
|
||||||
@@ -36,18 +61,20 @@ void ADCSensor::dump_config() {
|
|||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||||
switch (this->attenuation_) {
|
switch (this->attenuation_) {
|
||||||
case ADC_0db:
|
case ADC_ATTEN_DB_0:
|
||||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
|
||||||
break;
|
break;
|
||||||
case ADC_2_5db:
|
case ADC_ATTEN_DB_2_5:
|
||||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
|
||||||
break;
|
break;
|
||||||
case ADC_6db:
|
case ADC_ATTEN_DB_6:
|
||||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
|
||||||
break;
|
break;
|
||||||
case ADC_11db:
|
case ADC_ATTEN_DB_11:
|
||||||
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
|
||||||
break;
|
break;
|
||||||
|
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
@@ -60,20 +87,23 @@ void ADCSensor::update() {
|
|||||||
}
|
}
|
||||||
float ADCSensor::sample() {
|
float ADCSensor::sample() {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT
|
int raw = adc1_get_raw(gpio_to_adc1(pin_));
|
||||||
|
float value_v = raw / 4095.0f;
|
||||||
switch (this->attenuation_) {
|
switch (this->attenuation_) {
|
||||||
case ADC_0db:
|
case ADC_ATTEN_DB_0:
|
||||||
value_v *= 1.1;
|
value_v *= 1.1;
|
||||||
break;
|
break;
|
||||||
case ADC_2_5db:
|
case ADC_ATTEN_DB_2_5:
|
||||||
value_v *= 1.5;
|
value_v *= 1.5;
|
||||||
break;
|
break;
|
||||||
case ADC_6db:
|
case ADC_ATTEN_DB_6:
|
||||||
value_v *= 2.2;
|
value_v *= 2.2;
|
||||||
break;
|
break;
|
||||||
case ADC_11db:
|
case ADC_ATTEN_DB_11:
|
||||||
value_v *= 3.9;
|
value_v *= 3.9;
|
||||||
break;
|
break;
|
||||||
|
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return value_v;
|
return value_v;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
#include "esphome/components/voltage_sampler/voltage_sampler.h"
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
#include "driver/adc.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace adc {
|
namespace adc {
|
||||||
|
|
||||||
@@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
|||||||
public:
|
public:
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
/// Set the attenuation for this pin. Only available on the ESP32.
|
/// Set the attenuation for this pin. Only available on the ESP32.
|
||||||
void set_attenuation(adc_attenuation_t attenuation);
|
void set_attenuation(adc_atten_t attenuation);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Update adc values.
|
/// Update adc values.
|
||||||
@@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
|||||||
uint8_t pin_;
|
uint8_t pin_;
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
adc_attenuation_t attenuation_{ADC_0db};
|
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ from esphome.const import (
|
|||||||
AUTO_LOAD = ["voltage_sampler"]
|
AUTO_LOAD = ["voltage_sampler"]
|
||||||
|
|
||||||
ATTENUATION_MODES = {
|
ATTENUATION_MODES = {
|
||||||
"0db": cg.global_ns.ADC_0db,
|
"0db": cg.global_ns.ADC_ATTEN_DB_0,
|
||||||
"2.5db": cg.global_ns.ADC_2_5db,
|
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
|
||||||
"6db": cg.global_ns.ADC_6db,
|
"6db": cg.global_ns.ADC_ATTEN_DB_6,
|
||||||
"11db": cg.global_ns.ADC_11db,
|
"11db": cg.global_ns.ADC_ATTEN_DB_11,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,6 @@ void ADS1115Component::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->prev_config_ = config;
|
this->prev_config_ = config;
|
||||||
|
|
||||||
for (auto *sensor : this->sensors_) {
|
|
||||||
this->set_interval(sensor->get_name(), sensor->update_interval(),
|
|
||||||
[this, sensor] { this->request_measurement(sensor); });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
void ADS1115Component::dump_config() {
|
void ADS1115Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||||
@@ -107,17 +102,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
|||||||
}
|
}
|
||||||
this->prev_config_ = config;
|
this->prev_config_ = config;
|
||||||
|
|
||||||
// about 1.6 ms with 860 samples per second
|
// about 1.2 ms with 860 samples per second
|
||||||
delay(2);
|
delay(2);
|
||||||
|
|
||||||
uint32_t start = millis();
|
// in continuous mode, conversion will always be running, rely on the delay
|
||||||
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
// to ensure conversion is taking place with the correct settings
|
||||||
if (millis() - start > 100) {
|
// can we use the rdy pin to trigger when a conversion is done?
|
||||||
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
if (!this->continuous_mode_) {
|
||||||
this->status_set_warning();
|
uint32_t start = millis();
|
||||||
return NAN;
|
while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) {
|
||||||
|
if (millis() - start > 100) {
|
||||||
|
ESP_LOGW(TAG, "Reading ADS1115 timed out");
|
||||||
|
this->status_set_warning();
|
||||||
|
return NAN;
|
||||||
|
}
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
yield();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_
|
|||||||
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID);
|
||||||
if (chr == nullptr) {
|
if (chr == nullptr) {
|
||||||
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
|
ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str());
|
||||||
|
ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this->char_handle_ = chr->handle;
|
this->char_handle_ = chr->handle;
|
||||||
|
|||||||
@@ -422,6 +422,12 @@ enum SensorStateClass {
|
|||||||
STATE_CLASS_MEASUREMENT = 1;
|
STATE_CLASS_MEASUREMENT = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SensorLastResetType {
|
||||||
|
LAST_RESET_NONE = 0;
|
||||||
|
LAST_RESET_NEVER = 1;
|
||||||
|
LAST_RESET_AUTO = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ListEntitiesSensorResponse {
|
message ListEntitiesSensorResponse {
|
||||||
option (id) = 16;
|
option (id) = 16;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
@@ -438,6 +444,7 @@ message ListEntitiesSensorResponse {
|
|||||||
bool force_update = 8;
|
bool force_update = 8;
|
||||||
string device_class = 9;
|
string device_class = 9;
|
||||||
SensorStateClass state_class = 10;
|
SensorStateClass state_class = 10;
|
||||||
|
SensorLastResetType last_reset_type = 11;
|
||||||
}
|
}
|
||||||
message SensorStateResponse {
|
message SensorStateResponse {
|
||||||
option (id) = 25;
|
option (id) = 25;
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
|
|||||||
msg.force_update = sensor->get_force_update();
|
msg.force_update = sensor->get_force_update();
|
||||||
msg.device_class = sensor->get_device_class();
|
msg.device_class = sensor->get_device_class();
|
||||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
msg.state_class = static_cast<enums::SensorStateClass>(sensor->state_class);
|
||||||
|
msg.last_reset_type = static_cast<enums::SensorLastResetType>(sensor->last_reset_type);
|
||||||
|
|
||||||
return this->send_list_entities_sensor_response(msg);
|
return this->send_list_entities_sensor_response(msg);
|
||||||
}
|
}
|
||||||
@@ -570,11 +571,11 @@ bool APIConnection::send_number_info(number::Number *number) {
|
|||||||
msg.object_id = number->get_object_id();
|
msg.object_id = number->get_object_id();
|
||||||
msg.name = number->get_name();
|
msg.name = number->get_name();
|
||||||
msg.unique_id = get_default_unique_id("number", number);
|
msg.unique_id = get_default_unique_id("number", number);
|
||||||
msg.icon = number->get_icon();
|
msg.icon = number->traits.get_icon();
|
||||||
|
|
||||||
msg.min_value = number->get_min_value();
|
msg.min_value = number->traits.get_min_value();
|
||||||
msg.max_value = number->get_max_value();
|
msg.max_value = number->traits.get_max_value();
|
||||||
msg.step = number->get_step();
|
msg.step = number->traits.get_step();
|
||||||
|
|
||||||
return this->send_list_entities_number_response(msg);
|
return this->send_list_entities_number_response(msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::Sens
|
|||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
template<> const char *proto_enum_to_string<enums::SensorLastResetType>(enums::SensorLastResetType value) {
|
||||||
|
switch (value) {
|
||||||
|
case enums::LAST_RESET_NONE:
|
||||||
|
return "LAST_RESET_NONE";
|
||||||
|
case enums::LAST_RESET_NEVER:
|
||||||
|
return "LAST_RESET_NEVER";
|
||||||
|
case enums::LAST_RESET_AUTO:
|
||||||
|
return "LAST_RESET_AUTO";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case enums::LOG_LEVEL_NONE:
|
case enums::LOG_LEVEL_NONE:
|
||||||
@@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
|||||||
this->state_class = value.as_enum<enums::SensorStateClass>();
|
this->state_class = value.as_enum<enums::SensorStateClass>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case 11: {
|
||||||
|
this->last_reset_type = value.as_enum<enums::SensorLastResetType>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
|||||||
buffer.encode_bool(8, this->force_update);
|
buffer.encode_bool(8, this->force_update);
|
||||||
buffer.encode_string(9, this->device_class);
|
buffer.encode_string(9, this->device_class);
|
||||||
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
|
buffer.encode_enum<enums::SensorStateClass>(10, this->state_class);
|
||||||
|
buffer.encode_enum<enums::SensorLastResetType>(11, this->last_reset_type);
|
||||||
}
|
}
|
||||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
@@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
|||||||
out.append(" state_class: ");
|
out.append(" state_class: ");
|
||||||
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
|
out.append(proto_enum_to_string<enums::SensorStateClass>(this->state_class));
|
||||||
out.append("\n");
|
out.append("\n");
|
||||||
|
|
||||||
|
out.append(" last_reset_type: ");
|
||||||
|
out.append(proto_enum_to_string<enums::SensorLastResetType>(this->last_reset_type));
|
||||||
|
out.append("\n");
|
||||||
out.append("}");
|
out.append("}");
|
||||||
}
|
}
|
||||||
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t {
|
|||||||
STATE_CLASS_NONE = 0,
|
STATE_CLASS_NONE = 0,
|
||||||
STATE_CLASS_MEASUREMENT = 1,
|
STATE_CLASS_MEASUREMENT = 1,
|
||||||
};
|
};
|
||||||
|
enum SensorLastResetType : uint32_t {
|
||||||
|
LAST_RESET_NONE = 0,
|
||||||
|
LAST_RESET_NEVER = 1,
|
||||||
|
LAST_RESET_AUTO = 2,
|
||||||
|
};
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
LOG_LEVEL_ERROR = 1,
|
LOG_LEVEL_ERROR = 1,
|
||||||
@@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
|||||||
bool force_update{false};
|
bool force_update{false};
|
||||||
std::string device_class{};
|
std::string device_class{};
|
||||||
enums::SensorStateClass state_class{};
|
enums::SensorStateClass state_class{};
|
||||||
|
enums::SensorLastResetType last_reset_type{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void dump_to(std::string &out) const override;
|
void dump_to(std::string &out) const override;
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from esphome.const import (
|
|||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
ICON_LIGHTBULB,
|
ICON_LIGHTBULB,
|
||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_HERTZ,
|
UNIT_HERTZ,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
@@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
|
|||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
UNIT_WATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
2,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT
|
UNIT_WATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
2,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
|
||||||
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
|
||||||
|
|||||||
@@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) {
|
|||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Random 32bit value; If this changes existing restore preferences are invalidated
|
||||||
|
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||||
|
|
||||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||||
this->rtc_ = global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash());
|
this->rtc_ =
|
||||||
|
global_preferences.make_preference<ClimateDeviceRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION);
|
||||||
ClimateDeviceRestoreState recovered{};
|
ClimateDeviceRestoreState recovered{};
|
||||||
if (!this->rtc_.load(&recovered))
|
if (!this->rtc_.load(&recovered))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ class ClimateCall {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Struct used to save the state of the climate device in restore memory.
|
/// Struct used to save the state of the climate device in restore memory.
|
||||||
|
/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries.
|
||||||
struct ClimateDeviceRestoreState {
|
struct ClimateDeviceRestoreState {
|
||||||
ClimateMode mode;
|
ClimateMode mode;
|
||||||
bool uses_custom_fan_mode{false};
|
bool uses_custom_fan_mode{false};
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_(
|
|||||||
DisplayIsDisplayingPageCondition = display_ns.class_(
|
DisplayIsDisplayingPageCondition = display_ns.class_(
|
||||||
"DisplayIsDisplayingPageCondition", automation.Condition
|
"DisplayIsDisplayingPageCondition", automation.Condition
|
||||||
)
|
)
|
||||||
DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger")
|
DisplayOnPageChangeTrigger = display_ns.class_(
|
||||||
|
"DisplayOnPageChangeTrigger", automation.Trigger
|
||||||
|
)
|
||||||
|
|
||||||
CONF_ON_PAGE_CHANGE = "on_page_change"
|
CONF_ON_PAGE_CHANGE = "on_page_change"
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ void DutyCycleSensor::setup() {
|
|||||||
this->store_.pin = this->pin_->to_isr();
|
this->store_.pin = this->pin_->to_isr();
|
||||||
this->store_.last_level = this->pin_->digital_read();
|
this->store_.last_level = this->pin_->digital_read();
|
||||||
this->last_update_ = micros();
|
this->last_update_ = micros();
|
||||||
|
this->store_.last_interrupt = micros();
|
||||||
|
|
||||||
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE);
|
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
|
|||||||
protected:
|
protected:
|
||||||
GPIOPin *pin_;
|
GPIOPin *pin_;
|
||||||
|
|
||||||
DutyCycleSensorStore store_;
|
DutyCycleSensorStore store_{};
|
||||||
uint32_t last_update_;
|
uint32_t last_update_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ def _compute_destination_path(key: str) -> Path:
|
|||||||
return base_dir / h.hexdigest()[:8]
|
return base_dir / h.hexdigest()[:8]
|
||||||
|
|
||||||
|
|
||||||
def _run_git_command(cmd):
|
def _run_git_command(cmd, cwd=None):
|
||||||
try:
|
try:
|
||||||
ret = subprocess.run(cmd, capture_output=True, check=False)
|
ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False)
|
||||||
except FileNotFoundError as err:
|
except FileNotFoundError as err:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"git is not installed but required for external_components.\n"
|
"git is not installed but required for external_components.\n"
|
||||||
@@ -147,18 +147,20 @@ def _process_git_config(config: dict, refresh) -> str:
|
|||||||
age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
|
age = datetime.datetime.now() - datetime.datetime.fromtimestamp(
|
||||||
file_timestamp.stat().st_mtime
|
file_timestamp.stat().st_mtime
|
||||||
)
|
)
|
||||||
if age.seconds > refresh.total_seconds:
|
if age.total_seconds() > refresh.total_seconds:
|
||||||
_LOGGER.info("Updating %s", key)
|
_LOGGER.info("Updating %s", key)
|
||||||
_LOGGER.debug("Location: %s", repo_dir)
|
_LOGGER.debug("Location: %s", repo_dir)
|
||||||
# Stash local changes (if any)
|
# Stash local changes (if any)
|
||||||
_run_git_command(["git", "stash", "push", "--include-untracked"])
|
_run_git_command(
|
||||||
|
["git", "stash", "push", "--include-untracked"], str(repo_dir)
|
||||||
|
)
|
||||||
# Fetch remote ref
|
# Fetch remote ref
|
||||||
cmd = ["git", "fetch", "--", "origin"]
|
cmd = ["git", "fetch", "--", "origin"]
|
||||||
if CONF_REF in config:
|
if CONF_REF in config:
|
||||||
cmd.append(config[CONF_REF])
|
cmd.append(config[CONF_REF])
|
||||||
_run_git_command(cmd)
|
_run_git_command(cmd, str(repo_dir))
|
||||||
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
|
# Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch)
|
||||||
_run_git_command(["git", "reset", "--hard", "FETCH_HEAD"])
|
_run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
|
||||||
|
|
||||||
if (repo_dir / "esphome" / "components").is_dir():
|
if (repo_dir / "esphome" / "components").is_dir():
|
||||||
components_dir = repo_dir / "esphome" / "components"
|
components_dir = repo_dir / "esphome" / "components"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_NONE,
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
@@ -121,14 +122,16 @@ CONFIG_SCHEMA = (
|
|||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
2,
|
2,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||||
UNIT_KILOWATT_HOURS,
|
UNIT_KILOWATT_HOURS,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
0,
|
0,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema(
|
cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema(
|
||||||
UNIT_HOURS,
|
UNIT_HOURS,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ void HLW8012Component::dump_config() {
|
|||||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_)
|
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_)
|
||||||
LOG_SENSOR(" ", "Current", this->current_sensor_)
|
LOG_SENSOR(" ", "Current", this->current_sensor_)
|
||||||
LOG_SENSOR(" ", "Power", this->power_sensor_)
|
LOG_SENSOR(" ", "Power", this->power_sensor_)
|
||||||
|
LOG_SENSOR(" ", "Energy", this->energy_sensor_)
|
||||||
}
|
}
|
||||||
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
|
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
void HLW8012Component::update() {
|
void HLW8012Component::update() {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
@@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
UNIT_WATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
1,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance,
|
cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance,
|
||||||
cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float,
|
cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float,
|
||||||
|
|||||||
@@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) {
|
|||||||
|
|
||||||
// our transition will handle brightness, disable brightness in correction.
|
// our transition will handle brightness, disable brightness in correction.
|
||||||
this->correction_.set_local_brightness(255);
|
this->correction_.set_local_brightness(255);
|
||||||
uint8_t orig_w = target_color.w;
|
|
||||||
target_color *= static_cast<uint8_t>(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f));
|
target_color *= static_cast<uint8_t>(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f));
|
||||||
// w is not scaled by brightness
|
|
||||||
target_color.w = orig_w;
|
|
||||||
|
|
||||||
float denom = (1.0f - new_smoothed);
|
float denom = (1.0f - new_smoothed);
|
||||||
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
|
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const {
|
|||||||
ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode");
|
ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case climate::CLIMATE_PRESET_HOME:
|
case climate::CLIMATE_PRESET_NONE:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() {
|
|||||||
if (traits_swing_both_)
|
if (traits_swing_both_)
|
||||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||||
traits.set_supported_presets({
|
traits.set_supported_presets({
|
||||||
climate::CLIMATE_PRESET_HOME,
|
climate::CLIMATE_PRESET_NONE,
|
||||||
});
|
});
|
||||||
if (traits_preset_eco_)
|
if (traits_preset_eco_)
|
||||||
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
|
traits.add_supported_preset(climate::CLIMATE_PRESET_ECO);
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/midea_dongle/midea_dongle.h"
|
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||||
#include "esphome/components/climate/climate.h"
|
#include "esphome/components/climate/climate.h"
|
||||||
|
#include "esphome/components/midea_dongle/midea_dongle.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
#include "midea_frame.h"
|
#include "midea_frame.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|||||||
@@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
optional<climate::ClimatePreset> PropertiesFrame::get_preset() const {
|
optional<climate::ClimatePreset> PropertiesFrame::get_preset() const {
|
||||||
if (this->get_eco_mode()) {
|
if (this->get_eco_mode())
|
||||||
return climate::CLIMATE_PRESET_ECO;
|
return climate::CLIMATE_PRESET_ECO;
|
||||||
} else if (this->get_sleep_mode()) {
|
if (this->get_sleep_mode())
|
||||||
return climate::CLIMATE_PRESET_SLEEP;
|
return climate::CLIMATE_PRESET_SLEEP;
|
||||||
} else if (this->get_turbo_mode()) {
|
if (this->get_turbo_mode())
|
||||||
return climate::CLIMATE_PRESET_BOOST;
|
return climate::CLIMATE_PRESET_BOOST;
|
||||||
} else {
|
return climate::CLIMATE_PRESET_NONE;
|
||||||
return climate::CLIMATE_PRESET_HOME;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
|
void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
|
||||||
|
this->clear_presets();
|
||||||
switch (preset) {
|
switch (preset) {
|
||||||
case climate::CLIMATE_PRESET_ECO:
|
case climate::CLIMATE_PRESET_ECO:
|
||||||
this->set_eco_mode(true);
|
this->set_eco_mode(true);
|
||||||
@@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PropertiesFrame::clear_presets() {
|
||||||
|
this->set_eco_mode(false);
|
||||||
|
this->set_sleep_mode(false);
|
||||||
|
this->set_turbo_mode(false);
|
||||||
|
this->set_freeze_protection_mode(false);
|
||||||
|
}
|
||||||
|
|
||||||
bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); }
|
bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); }
|
||||||
|
|
||||||
const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; };
|
const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; };
|
||||||
|
|
||||||
void PropertiesFrame::set_custom_preset(const std::string &preset) {
|
void PropertiesFrame::set_custom_preset(const std::string &preset) {
|
||||||
if (preset == MIDEA_FREEZE_PROTECTION_PRESET) {
|
this->clear_presets();
|
||||||
|
if (preset == MIDEA_FREEZE_PROTECTION_PRESET)
|
||||||
this->set_freeze_protection_mode(true);
|
this->set_freeze_protection_mode(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PropertiesFrame::is_custom_fan_mode() const {
|
bool PropertiesFrame::is_custom_fan_mode() const {
|
||||||
|
|||||||
@@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
|
|||||||
void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); }
|
void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); }
|
||||||
|
|
||||||
/* TURBO MODE */
|
/* TURBO MODE */
|
||||||
bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; }
|
bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; }
|
||||||
void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); }
|
void set_turbo_mode(bool state) {
|
||||||
|
this->set_bytemask_(18, 0x20, state);
|
||||||
|
this->set_bytemask_(20, 0x02, state);
|
||||||
|
}
|
||||||
|
|
||||||
/* FREEZE PROTECTION */
|
/* FREEZE PROTECTION */
|
||||||
bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; }
|
bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; }
|
||||||
@@ -112,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame {
|
|||||||
/* PRESET */
|
/* PRESET */
|
||||||
optional<climate::ClimatePreset> get_preset() const;
|
optional<climate::ClimatePreset> get_preset() const;
|
||||||
void set_preset(climate::ClimatePreset preset);
|
void set_preset(climate::ClimatePreset preset);
|
||||||
|
void clear_presets();
|
||||||
|
|
||||||
bool is_custom_preset() const;
|
bool is_custom_preset() const;
|
||||||
const std::string &get_custom_preset() const;
|
const std::string &get_custom_preset() const;
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
|
|||||||
root["act_t"] = this->get_action_state_topic();
|
root["act_t"] = this->get_action_state_topic();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.get_supports_fan_modes()) {
|
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||||
// fan_mode_command_topic
|
// fan_mode_command_topic
|
||||||
root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
|
root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
|
||||||
// fan_mode_state_topic
|
// fan_mode_state_topic
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
|
|
||||||
#ifdef USE_DEEP_SLEEP
|
|
||||||
#include "esphome/components/deep_sleep/deep_sleep_component.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace mqtt {
|
namespace mqtt {
|
||||||
|
|
||||||
@@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() {
|
|||||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
|
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
|
||||||
auto val = parse_float(state);
|
auto val = parse_float(state);
|
||||||
if (!val.has_value()) {
|
if (!val.has_value()) {
|
||||||
ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str());
|
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto call = this->number_->make_call();
|
auto call = this->number_->make_call();
|
||||||
@@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; }
|
|||||||
|
|
||||||
std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); }
|
std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); }
|
||||||
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||||
if (!this->number_->get_icon().empty())
|
const auto &traits = number_->traits;
|
||||||
root["icon"] = this->number_->get_icon();
|
// https://www.home-assistant.io/integrations/number.mqtt/
|
||||||
|
if (!traits.get_icon().empty())
|
||||||
|
root["icon"] = traits.get_icon();
|
||||||
|
root["min"] = traits.get_min_value();
|
||||||
|
root["max"] = traits.get_max_value();
|
||||||
|
root["step"] = traits.get_step();
|
||||||
|
|
||||||
|
config.command_topic = true;
|
||||||
}
|
}
|
||||||
bool MQTTNumberComponent::send_initial_state() {
|
bool MQTTNumberComponent::send_initial_state() {
|
||||||
if (this->number_->has_state()) {
|
if (this->number_->has_state()) {
|
||||||
@@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() {
|
|||||||
}
|
}
|
||||||
bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); }
|
bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); }
|
||||||
bool MQTTNumberComponent::publish_state(float value) {
|
bool MQTTNumberComponent::publish_state(float value) {
|
||||||
int8_t accuracy = this->number_->get_accuracy_decimals();
|
char buffer[64];
|
||||||
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
|
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||||
|
return this->publish(this->get_state_topic_(), buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mqtt
|
} // namespace mqtt
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from typing import Optional
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
@@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def setup_number_core_(var, config):
|
async def setup_number_core_(
|
||||||
|
var, config, *, min_value: float, max_value: float, step: Optional[float]
|
||||||
|
):
|
||||||
cg.add(var.set_name(config[CONF_NAME]))
|
cg.add(var.set_name(config[CONF_NAME]))
|
||||||
if CONF_INTERNAL in config:
|
if CONF_INTERNAL in config:
|
||||||
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
cg.add(var.set_internal(config[CONF_INTERNAL]))
|
||||||
|
|
||||||
cg.add(var.set_icon(config[CONF_ICON]))
|
cg.add(var.traits.set_icon(config[CONF_ICON]))
|
||||||
|
cg.add(var.traits.set_min_value(min_value))
|
||||||
|
cg.add(var.traits.set_max_value(max_value))
|
||||||
|
if step is not None:
|
||||||
|
cg.add(var.traits.set_step(step))
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_VALUE, []):
|
for conf in config.get(CONF_ON_VALUE, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
@@ -92,16 +99,24 @@ async def setup_number_core_(var, config):
|
|||||||
await mqtt.register_mqtt_component(mqtt_, config)
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
|
|
||||||
async def register_number(var, config):
|
async def register_number(
|
||||||
|
var, config, *, min_value: float, max_value: float, step: Optional[float] = None
|
||||||
|
):
|
||||||
if not CORE.has_id(config[CONF_ID]):
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
var = cg.Pvariable(config[CONF_ID], var)
|
var = cg.Pvariable(config[CONF_ID], var)
|
||||||
cg.add(cg.App.register_number(var))
|
cg.add(cg.App.register_number(var))
|
||||||
await setup_number_core_(var, config)
|
await setup_number_core_(
|
||||||
|
var, config, min_value=min_value, max_value=max_value, step=step
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def new_number(config):
|
async def new_number(
|
||||||
|
config, *, min_value: float, max_value: float, step: Optional[float] = None
|
||||||
|
):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await register_number(var, config)
|
await register_number(
|
||||||
|
var, config, min_value=min_value, max_value=max_value, step=step
|
||||||
|
)
|
||||||
return var
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,67 +8,38 @@ static const char *const TAG = "number";
|
|||||||
|
|
||||||
void NumberCall::perform() {
|
void NumberCall::perform() {
|
||||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||||
if (this->value_.has_value()) {
|
if (!this->value_.has_value() || isnan(*this->value_)) {
|
||||||
auto value = *this->value_;
|
ESP_LOGW(TAG, "No value set for NumberCall");
|
||||||
uint8_t accuracy = this->parent_->get_accuracy_decimals();
|
return;
|
||||||
float min_value = this->parent_->get_min_value();
|
|
||||||
if (value < min_value) {
|
|
||||||
ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(),
|
|
||||||
value_accuracy_to_string(min_value, accuracy).c_str());
|
|
||||||
this->value_.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float max_value = this->parent_->get_max_value();
|
|
||||||
if (value > max_value) {
|
|
||||||
ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(),
|
|
||||||
value_accuracy_to_string(max_value, accuracy).c_str());
|
|
||||||
this->value_.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str());
|
|
||||||
this->parent_->set(*this->value_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto &traits = this->parent_->traits;
|
||||||
|
auto value = *this->value_;
|
||||||
|
|
||||||
|
float min_value = traits.get_min_value();
|
||||||
|
if (value < min_value) {
|
||||||
|
ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float max_value = traits.get_max_value();
|
||||||
|
if (value > max_value) {
|
||||||
|
ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGD(TAG, " Value: %f", *this->value_);
|
||||||
|
this->parent_->control(*this->value_);
|
||||||
}
|
}
|
||||||
|
|
||||||
NumberCall &NumberCall::set_value(float value) {
|
|
||||||
this->value_ = value;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const optional<float> &NumberCall::get_value() const { return this->value_; }
|
|
||||||
|
|
||||||
NumberCall Number::make_call() { return NumberCall(this); }
|
|
||||||
|
|
||||||
void Number::publish_state(float state) {
|
void Number::publish_state(float state) {
|
||||||
this->has_state_ = true;
|
this->has_state_ = true;
|
||||||
this->state = state;
|
this->state = state;
|
||||||
ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state);
|
ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state);
|
||||||
this->state_callback_.call(state);
|
this->state_callback_.call(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t Number::update_interval() { return 0; }
|
|
||||||
Number::Number(const std::string &name) : Nameable(name), state(NAN) {}
|
|
||||||
Number::Number() : Number("") {}
|
|
||||||
|
|
||||||
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
|
||||||
this->state_callback_.add(std::move(callback));
|
this->state_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
void Number::set_icon(const std::string &icon) { this->icon_ = icon; }
|
|
||||||
std::string Number::get_icon() { return *this->icon_; }
|
|
||||||
int8_t Number::get_accuracy_decimals() {
|
|
||||||
// use printf %g to find number of digits based on step
|
|
||||||
char buf[32];
|
|
||||||
sprintf(buf, "%.5g", this->step_);
|
|
||||||
std::string str{buf};
|
|
||||||
size_t dot_pos = str.find('.');
|
|
||||||
if (dot_pos == std::string::npos)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return str.length() - dot_pos - 1;
|
|
||||||
}
|
|
||||||
float Number::get_state() const { return this->state; }
|
|
||||||
|
|
||||||
bool Number::has_state() const { return this->has_state_; }
|
|
||||||
|
|
||||||
uint32_t Number::hash_base() { return 2282307003UL; }
|
uint32_t Number::hash_base() { return 2282307003UL; }
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ namespace number {
|
|||||||
#define LOG_NUMBER(prefix, type, obj) \
|
#define LOG_NUMBER(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon().empty()) { \
|
if (!(obj)->traits.get_icon().empty()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,72 +19,56 @@ class Number;
|
|||||||
class NumberCall {
|
class NumberCall {
|
||||||
public:
|
public:
|
||||||
explicit NumberCall(Number *parent) : parent_(parent) {}
|
explicit NumberCall(Number *parent) : parent_(parent) {}
|
||||||
NumberCall &set_value(float value);
|
|
||||||
void perform();
|
void perform();
|
||||||
|
|
||||||
const optional<float> &get_value() const;
|
NumberCall &set_value(float value) {
|
||||||
|
value_ = value;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
const optional<float> &get_value() const { return value_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Number *const parent_;
|
Number *const parent_;
|
||||||
optional<float> value_;
|
optional<float> value_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NumberTraits {
|
||||||
|
public:
|
||||||
|
void set_min_value(float min_value) { min_value_ = min_value; }
|
||||||
|
float get_min_value() const { return min_value_; }
|
||||||
|
void set_max_value(float max_value) { max_value_ = max_value; }
|
||||||
|
float get_max_value() const { return max_value_; }
|
||||||
|
void set_step(float step) { step_ = step; }
|
||||||
|
float get_step() const { return step_; }
|
||||||
|
void set_icon(std::string icon) { icon_ = std::move(icon); }
|
||||||
|
const std::string &get_icon() const { return icon_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float min_value_ = NAN;
|
||||||
|
float max_value_ = NAN;
|
||||||
|
float step_ = NAN;
|
||||||
|
std::string icon_;
|
||||||
|
};
|
||||||
|
|
||||||
/** Base-class for all numbers.
|
/** Base-class for all numbers.
|
||||||
*
|
*
|
||||||
* A number can use publish_state to send out a new value.
|
* A number can use publish_state to send out a new value.
|
||||||
*/
|
*/
|
||||||
class Number : public Nameable {
|
class Number : public Nameable {
|
||||||
public:
|
public:
|
||||||
explicit Number();
|
|
||||||
explicit Number(const std::string &name);
|
|
||||||
|
|
||||||
/** Manually set the icon of this number. By default the number's default defined by icon() is used.
|
|
||||||
*
|
|
||||||
* @param icon The icon, for example "mdi:flash". "" to disable.
|
|
||||||
*/
|
|
||||||
void set_icon(const std::string &icon);
|
|
||||||
/// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead.
|
|
||||||
std::string get_icon();
|
|
||||||
|
|
||||||
/// Getter-syntax for .state.
|
|
||||||
float get_state() const;
|
|
||||||
|
|
||||||
/// Get the accuracy in decimals. Based on the step value.
|
|
||||||
int8_t get_accuracy_decimals();
|
|
||||||
|
|
||||||
/** Publish the current state to the front-end.
|
|
||||||
*/
|
|
||||||
void publish_state(float state);
|
|
||||||
|
|
||||||
NumberCall make_call();
|
|
||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
|
||||||
// (In most use cases you won't need these)
|
|
||||||
/// Add a callback that will be called every time the state changes.
|
|
||||||
void add_on_state_callback(std::function<void(float)> &&callback);
|
|
||||||
|
|
||||||
/** This member variable stores the last state.
|
|
||||||
*
|
|
||||||
* On startup, when no state is available yet, this is NAN (not-a-number) and the validity
|
|
||||||
* can be checked using has_state().
|
|
||||||
*
|
|
||||||
* This is exposed through a member variable for ease of use in esphome lambdas.
|
|
||||||
*/
|
|
||||||
float state;
|
float state;
|
||||||
|
|
||||||
|
void publish_state(float state);
|
||||||
|
|
||||||
|
NumberCall make_call() { return NumberCall(this); }
|
||||||
|
void set(float value) { make_call().set_value(value).perform(); }
|
||||||
|
|
||||||
|
void add_on_state_callback(std::function<void(float)> &&callback);
|
||||||
|
|
||||||
|
NumberTraits traits;
|
||||||
|
|
||||||
/// Return whether this number has gotten a full state yet.
|
/// Return whether this number has gotten a full state yet.
|
||||||
bool has_state() const;
|
bool has_state() const { return has_state_; }
|
||||||
|
|
||||||
/// Return with which interval the number is polled. Return 0 for non-polling mode.
|
|
||||||
virtual uint32_t update_interval();
|
|
||||||
|
|
||||||
void set_min_value(float min_value) { this->min_value_ = min_value; }
|
|
||||||
void set_max_value(float max_value) { this->max_value_ = max_value; }
|
|
||||||
void set_step(float step) { this->step_ = step; }
|
|
||||||
|
|
||||||
float get_min_value() const { return this->min_value_; }
|
|
||||||
float get_max_value() const { return this->max_value_; }
|
|
||||||
float get_step() const { return this->step_; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class NumberCall;
|
friend class NumberCall;
|
||||||
@@ -95,17 +79,12 @@ class Number : public Nameable {
|
|||||||
*
|
*
|
||||||
* @param value The value as validated by the NumberCall.
|
* @param value The value as validated by the NumberCall.
|
||||||
*/
|
*/
|
||||||
virtual void set(float value) = 0;
|
virtual void control(float value) = 0;
|
||||||
|
|
||||||
uint32_t hash_base() override;
|
uint32_t hash_base() override;
|
||||||
|
|
||||||
CallbackManager<void(float)> state_callback_;
|
CallbackManager<void(float)> state_callback_;
|
||||||
/// Override the icon advertised to Home Assistant, otherwise number's icon will be used.
|
|
||||||
optional<std::string> icon_;
|
|
||||||
bool has_state_{false};
|
bool has_state_{false};
|
||||||
float step_{1.0};
|
|
||||||
float min_value_{0};
|
|
||||||
float max_value_{100};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace number
|
} // namespace number
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ void PIDClimate::control(const climate::ClimateCall &call) {
|
|||||||
if (call.get_target_temperature().has_value())
|
if (call.get_target_temperature().has_value())
|
||||||
this->target_temperature = *call.get_target_temperature();
|
this->target_temperature = *call.get_target_temperature();
|
||||||
|
|
||||||
// If switching to non-auto mode, set output immediately
|
// If switching to off mode, set output immediately
|
||||||
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL)
|
if (this->mode == climate::CLIMATE_MODE_OFF)
|
||||||
this->handle_non_auto_mode_();
|
this->write_output_(0.0f);
|
||||||
|
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
@@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) {
|
|||||||
}
|
}
|
||||||
this->pid_computed_callback_.call();
|
this->pid_computed_callback_.call();
|
||||||
}
|
}
|
||||||
void PIDClimate::handle_non_auto_mode_() {
|
|
||||||
// in non-auto mode, switch directly to appropriate action
|
|
||||||
// - OFF mode -> Output at 0%
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
|
||||||
this->write_output_(0.0);
|
|
||||||
} else {
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void PIDClimate::update_pid_() {
|
void PIDClimate::update_pid_() {
|
||||||
float value;
|
float value;
|
||||||
if (isnan(this->current_temperature) || isnan(this->target_temperature)) {
|
if (isnan(this->current_temperature) || isnan(this->target_temperature)) {
|
||||||
@@ -135,7 +126,7 @@ void PIDClimate::update_pid_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||||
this->handle_non_auto_mode_();
|
this->write_output_(0.0);
|
||||||
} else {
|
} else {
|
||||||
this->write_output_(value);
|
this->write_output_(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component {
|
|||||||
bool supports_heat_() const { return this->heat_output_ != nullptr; }
|
bool supports_heat_() const { return this->heat_output_ != nullptr; }
|
||||||
|
|
||||||
void write_output_(float value);
|
void write_output_(float value);
|
||||||
void handle_non_auto_mode_();
|
|
||||||
|
|
||||||
/// The sensor used for getting the current temperature
|
/// The sensor used for getting the current temperature
|
||||||
sensor::Sensor *sensor_;
|
sensor::Sensor *sensor_;
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from esphome.const import (
|
|||||||
CONF_TOTAL,
|
CONF_TOTAL,
|
||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
ICON_PULSE,
|
ICON_PULSE,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
|
||||||
UNIT_PULSES,
|
UNIT_PULSES,
|
||||||
UNIT_PULSES_PER_MINUTE,
|
UNIT_PULSES_PER_MINUTE,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
@@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema(
|
|||||||
cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter,
|
cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter,
|
||||||
cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout,
|
cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout,
|
||||||
cv.Optional(CONF_TOTAL): sensor.sensor_schema(
|
cv.Optional(CONF_TOTAL): sensor.sensor_schema(
|
||||||
UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE
|
UNIT_PULSES,
|
||||||
|
ICON_PULSE,
|
||||||
|
0,
|
||||||
|
DEVICE_CLASS_EMPTY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_VOLTAGE,
|
DEVICE_CLASS_VOLTAGE,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
@@ -47,7 +47,8 @@ CONFIG_SCHEMA = (
|
|||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
0,
|
0,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
|
||||||
UNIT_HERTZ,
|
UNIT_HERTZ,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
@@ -54,7 +54,8 @@ CONFIG_SCHEMA = (
|
|||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
0,
|
0,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
UNIT_HERTZ,
|
UNIT_HERTZ,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
|
|||||||
bool initialized_{false};
|
bool initialized_{false};
|
||||||
std::vector<rmt_item32_t> rmt_temp_;
|
std::vector<rmt_item32_t> rmt_temp_;
|
||||||
esp_err_t error_code_{ESP_OK};
|
esp_err_t error_code_{ESP_OK};
|
||||||
|
bool inverted_{false};
|
||||||
#endif
|
#endif
|
||||||
uint8_t carrier_duty_percent_{50};
|
uint8_t carrier_duty_percent_{50};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace remote_transmitter {
|
|||||||
|
|
||||||
static const char *const TAG = "remote_transmitter";
|
static const char *const TAG = "remote_transmitter";
|
||||||
|
|
||||||
void RemoteTransmitterComponent::setup() {}
|
void RemoteTransmitterComponent::setup() { this->configure_rmt(); }
|
||||||
|
|
||||||
void RemoteTransmitterComponent::dump_config() {
|
void RemoteTransmitterComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Remote Transmitter...");
|
ESP_LOGCONFIG(TAG, "Remote Transmitter...");
|
||||||
@@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() {
|
|||||||
} else {
|
} else {
|
||||||
c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||||
c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
|
c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH;
|
||||||
|
this->inverted_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t error = rmt_config(&c);
|
esp_err_t error = rmt_config(&c);
|
||||||
@@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
|
|||||||
val -= item;
|
val -= item;
|
||||||
|
|
||||||
if (rmt_i % 2 == 0) {
|
if (rmt_i % 2 == 0) {
|
||||||
rmt_item.level0 = static_cast<uint32_t>(level);
|
rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
|
||||||
rmt_item.duration0 = static_cast<uint32_t>(item);
|
rmt_item.duration0 = static_cast<uint32_t>(item);
|
||||||
} else {
|
} else {
|
||||||
rmt_item.level1 = static_cast<uint32_t>(level);
|
rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
|
||||||
rmt_item.duration1 = static_cast<uint32_t>(item);
|
rmt_item.duration1 = static_cast<uint32_t>(item);
|
||||||
this->rmt_temp_.push_back(rmt_item);
|
this->rmt_temp_.push_back(rmt_item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ from esphome.const import (
|
|||||||
ICON_CURRENT_AC,
|
ICON_CURRENT_AC,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
ICON_FLASH,
|
ICON_FLASH,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
STATE_CLASS_NONE,
|
|
||||||
UNIT_AMPERE,
|
UNIT_AMPERE,
|
||||||
UNIT_DEGREES,
|
UNIT_DEGREES,
|
||||||
UNIT_EMPTY,
|
UNIT_EMPTY,
|
||||||
@@ -88,24 +88,36 @@ CONFIG_SCHEMA = (
|
|||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
UNIT_WATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
2,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE
|
UNIT_WATT_HOURS,
|
||||||
|
ICON_EMPTY,
|
||||||
|
2,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
2,
|
2,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
2,
|
2,
|
||||||
DEVICE_CLASS_ENERGY,
|
DEVICE_CLASS_ENERGY,
|
||||||
STATE_CLASS_NONE,
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from esphome.const import (
|
|||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERNAL,
|
CONF_INTERNAL,
|
||||||
|
CONF_LAST_RESET_TYPE,
|
||||||
CONF_ON_RAW_VALUE,
|
CONF_ON_RAW_VALUE,
|
||||||
CONF_ON_VALUE,
|
CONF_ON_VALUE,
|
||||||
CONF_ON_VALUE_RANGE,
|
CONF_ON_VALUE_RANGE,
|
||||||
@@ -30,6 +31,9 @@ from esphome.const import (
|
|||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_FORCE_UPDATE,
|
CONF_FORCE_UPDATE,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
|
LAST_RESET_TYPE_NEVER,
|
||||||
|
LAST_RESET_TYPE_NONE,
|
||||||
UNIT_EMPTY,
|
UNIT_EMPTY,
|
||||||
ICON_EMPTY,
|
ICON_EMPTY,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
@@ -79,6 +83,15 @@ STATE_CLASSES = {
|
|||||||
}
|
}
|
||||||
validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_")
|
validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_")
|
||||||
|
|
||||||
|
LastResetTypes = sensor_ns.enum("LastResetType")
|
||||||
|
LAST_RESET_TYPES = {
|
||||||
|
LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE,
|
||||||
|
LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER,
|
||||||
|
LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO,
|
||||||
|
}
|
||||||
|
validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_")
|
||||||
|
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
|
||||||
@@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend(
|
|||||||
cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals,
|
cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals,
|
||||||
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
cv.Optional(CONF_DEVICE_CLASS): device_class,
|
||||||
cv.Optional(CONF_STATE_CLASS): validate_state_class,
|
cv.Optional(CONF_STATE_CLASS): validate_state_class,
|
||||||
|
cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type,
|
||||||
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
|
cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
|
cv.Optional(CONF_EXPIRE_AFTER): cv.All(
|
||||||
cv.requires_component("mqtt"),
|
cv.requires_component("mqtt"),
|
||||||
@@ -202,6 +216,7 @@ def sensor_schema(
|
|||||||
accuracy_decimals_: int,
|
accuracy_decimals_: int,
|
||||||
device_class_: Optional[str] = DEVICE_CLASS_EMPTY,
|
device_class_: Optional[str] = DEVICE_CLASS_EMPTY,
|
||||||
state_class_: Optional[str] = STATE_CLASS_NONE,
|
state_class_: Optional[str] = STATE_CLASS_NONE,
|
||||||
|
last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE,
|
||||||
) -> cv.Schema:
|
) -> cv.Schema:
|
||||||
schema = SENSOR_SCHEMA
|
schema = SENSOR_SCHEMA
|
||||||
if unit_of_measurement_ != UNIT_EMPTY:
|
if unit_of_measurement_ != UNIT_EMPTY:
|
||||||
@@ -230,6 +245,14 @@ def sensor_schema(
|
|||||||
schema = schema.extend(
|
schema = schema.extend(
|
||||||
{cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class}
|
{cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class}
|
||||||
)
|
)
|
||||||
|
if last_reset_type_ != LAST_RESET_TYPE_NONE:
|
||||||
|
schema = schema.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(
|
||||||
|
CONF_LAST_RESET_TYPE, default=last_reset_type_
|
||||||
|
): validate_last_reset_type
|
||||||
|
}
|
||||||
|
)
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
@@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config):
|
|||||||
cg.add(var.set_icon(config[CONF_ICON]))
|
cg.add(var.set_icon(config[CONF_ICON]))
|
||||||
if CONF_ACCURACY_DECIMALS in config:
|
if CONF_ACCURACY_DECIMALS in config:
|
||||||
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS]))
|
||||||
|
if CONF_LAST_RESET_TYPE in config:
|
||||||
|
cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE]))
|
||||||
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
cg.add(var.set_force_update(config[CONF_FORCE_UPDATE]))
|
||||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||||
filters = await build_filters(config[CONF_FILTERS])
|
filters = await build_filters(config[CONF_FILTERS])
|
||||||
|
|||||||
@@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *last_reset_type_to_string(LastResetType last_reset_type) {
|
||||||
|
switch (last_reset_type) {
|
||||||
|
case LAST_RESET_TYPE_NEVER:
|
||||||
|
return "never";
|
||||||
|
case LAST_RESET_TYPE_AUTO:
|
||||||
|
return "auto";
|
||||||
|
case LAST_RESET_TYPE_NONE:
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Sensor::publish_state(float state) {
|
void Sensor::publish_state(float state) {
|
||||||
this->raw_state = state;
|
this->raw_state = state;
|
||||||
this->raw_callback_.call(state);
|
this->raw_callback_.call(state);
|
||||||
@@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) {
|
|||||||
ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
|
ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; }
|
||||||
std::string Sensor::get_unit_of_measurement() {
|
std::string Sensor::get_unit_of_measurement() {
|
||||||
if (this->unit_of_measurement_.has_value())
|
if (this->unit_of_measurement_.has_value())
|
||||||
return *this->unit_of_measurement_;
|
return *this->unit_of_measurement_;
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ namespace sensor {
|
|||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||||
} \
|
} \
|
||||||
ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \
|
ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \
|
||||||
|
if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \
|
||||||
|
(obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \
|
||||||
|
ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \
|
||||||
|
} \
|
||||||
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \
|
ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \
|
||||||
if (!(obj)->get_icon().empty()) { \
|
if (!(obj)->get_icon().empty()) { \
|
||||||
@@ -37,6 +41,20 @@ enum StateClass : uint8_t {
|
|||||||
|
|
||||||
const char *state_class_to_string(StateClass state_class);
|
const char *state_class_to_string(StateClass state_class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sensor last reset types
|
||||||
|
*/
|
||||||
|
enum LastResetType : uint8_t {
|
||||||
|
/// This sensor does not support resetting. ie, it is not accumulative
|
||||||
|
LAST_RESET_TYPE_NONE = 0,
|
||||||
|
/// This sensor is expected to never reset its value
|
||||||
|
LAST_RESET_TYPE_NEVER = 1,
|
||||||
|
/// This sensor may reset and Home Assistant will watch for this
|
||||||
|
LAST_RESET_TYPE_AUTO = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *last_reset_type_to_string(LastResetType last_reset_type);
|
||||||
|
|
||||||
/** Base-class for all sensors.
|
/** Base-class for all sensors.
|
||||||
*
|
*
|
||||||
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
||||||
@@ -155,6 +173,12 @@ class Sensor : public Nameable {
|
|||||||
*/
|
*/
|
||||||
virtual std::string device_class();
|
virtual std::string device_class();
|
||||||
|
|
||||||
|
// The Last reset type of this sensor
|
||||||
|
LastResetType last_reset_type{LAST_RESET_TYPE_NONE};
|
||||||
|
|
||||||
|
/// Manually set the Home Assistant last reset type for this sensor.
|
||||||
|
void set_last_reset_type(LastResetType last_reset_type);
|
||||||
|
|
||||||
/** A unique ID for this sensor, empty for no unique id. See unique ID requirements:
|
/** A unique ID for this sensor, empty for no unique id. See unique ID requirements:
|
||||||
* https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
|
* https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import esphome.config_validation as cv
|
|||||||
from esphome.components import number
|
from esphome.components import number
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
|
CONF_INITIAL_VALUE,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
CONF_MAX_VALUE,
|
CONF_MAX_VALUE,
|
||||||
CONF_MIN_VALUE,
|
CONF_MIN_VALUE,
|
||||||
CONF_OPTIMISTIC,
|
CONF_OPTIMISTIC,
|
||||||
|
CONF_RESTORE_VALUE,
|
||||||
CONF_STEP,
|
CONF_STEP,
|
||||||
)
|
)
|
||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
@@ -25,6 +27,17 @@ def validate_min_max(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def validate(config):
|
||||||
|
if CONF_LAMBDA in config:
|
||||||
|
if CONF_OPTIMISTIC in config:
|
||||||
|
raise cv.Invalid("optimistic cannot be used with lambda")
|
||||||
|
if CONF_INITIAL_VALUE in config:
|
||||||
|
raise cv.Invalid("initial_value cannot be used with lambda")
|
||||||
|
if CONF_RESTORE_VALUE in config:
|
||||||
|
raise cv.Invalid("restore_value cannot be used with lambda")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
number.NUMBER_SCHEMA.extend(
|
number.NUMBER_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@@ -33,31 +46,43 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||||
cv.Required(CONF_STEP): cv.positive_float,
|
cv.Required(CONF_STEP): cv.positive_float,
|
||||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
cv.Optional(CONF_OPTIMISTIC): cv.boolean,
|
||||||
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_INITIAL_VALUE): cv.float_,
|
||||||
|
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||||
}
|
}
|
||||||
).extend(cv.polling_component_schema("60s")),
|
).extend(cv.polling_component_schema("60s")),
|
||||||
validate_min_max,
|
validate_min_max,
|
||||||
|
validate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await number.register_number(var, config)
|
await number.register_number(
|
||||||
|
var,
|
||||||
|
config,
|
||||||
|
min_value=config[CONF_MIN_VALUE],
|
||||||
|
max_value=config[CONF_MAX_VALUE],
|
||||||
|
step=config[CONF_STEP],
|
||||||
|
)
|
||||||
|
|
||||||
if CONF_LAMBDA in config:
|
if CONF_LAMBDA in config:
|
||||||
template_ = await cg.process_lambda(
|
template_ = await cg.process_lambda(
|
||||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
config[CONF_LAMBDA], [], return_type=cg.optional.template(float)
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
cg.add(var.set_template(template_))
|
||||||
|
|
||||||
|
else:
|
||||||
|
if CONF_OPTIMISTIC in config:
|
||||||
|
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||||
|
if CONF_INITIAL_VALUE in config:
|
||||||
|
cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE]))
|
||||||
|
if CONF_RESTORE_VALUE in config:
|
||||||
|
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
|
||||||
|
|
||||||
if CONF_SET_ACTION in config:
|
if CONF_SET_ACTION in config:
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
|
var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION]
|
||||||
)
|
)
|
||||||
|
|
||||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
|
||||||
|
|
||||||
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
|
|
||||||
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
|
|
||||||
cg.add(var.set_step(config[CONF_STEP]))
|
|
||||||
|
|||||||
@@ -6,34 +6,50 @@ namespace template_ {
|
|||||||
|
|
||||||
static const char *const TAG = "template.number";
|
static const char *const TAG = "template.number";
|
||||||
|
|
||||||
TemplateNumber::TemplateNumber() : set_trigger_(new Trigger<float>()) {}
|
void TemplateNumber::setup() {
|
||||||
|
if (this->f_.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
float value;
|
||||||
|
if (!this->restore_value_) {
|
||||||
|
value = this->initial_value_;
|
||||||
|
} else {
|
||||||
|
this->pref_ = global_preferences.make_preference<float>(this->get_object_id_hash());
|
||||||
|
if (!this->pref_.load(&value)) {
|
||||||
|
if (!isnan(this->initial_value_))
|
||||||
|
value = this->initial_value_;
|
||||||
|
else
|
||||||
|
value = this->traits.get_min_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->publish_state(value);
|
||||||
|
}
|
||||||
|
|
||||||
void TemplateNumber::update() {
|
void TemplateNumber::update() {
|
||||||
if (!this->f_.has_value())
|
if (!this->f_.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto val = (*this->f_)();
|
auto val = (*this->f_)();
|
||||||
if (val.has_value()) {
|
if (!val.has_value())
|
||||||
this->publish_state(*val);
|
return;
|
||||||
}
|
|
||||||
|
this->publish_state(*val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateNumber::set(float value) {
|
void TemplateNumber::control(float value) {
|
||||||
this->set_trigger_->trigger(value);
|
this->set_trigger_->trigger(value);
|
||||||
|
|
||||||
if (this->optimistic_)
|
if (this->optimistic_)
|
||||||
this->publish_state(value);
|
this->publish_state(value);
|
||||||
|
|
||||||
|
if (this->restore_value_)
|
||||||
|
this->pref_.save(&value);
|
||||||
}
|
}
|
||||||
float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; }
|
|
||||||
void TemplateNumber::set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
|
||||||
void TemplateNumber::dump_config() {
|
void TemplateNumber::dump_config() {
|
||||||
LOG_NUMBER("", "Template Number", this);
|
LOG_NUMBER("", "Template Number", this);
|
||||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
|
||||||
Trigger<float> *TemplateNumber::get_set_trigger() const { return this->set_trigger_; };
|
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|||||||
@@ -3,27 +3,34 @@
|
|||||||
#include "esphome/components/number/number.h"
|
#include "esphome/components/number/number.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace template_ {
|
namespace template_ {
|
||||||
|
|
||||||
class TemplateNumber : public number::Number, public PollingComponent {
|
class TemplateNumber : public number::Number, public PollingComponent {
|
||||||
public:
|
public:
|
||||||
TemplateNumber();
|
void set_template(std::function<optional<float>()> &&f) { this->f_ = f; }
|
||||||
void set_template(std::function<optional<float>()> &&f);
|
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
Trigger<float> *get_set_trigger() const;
|
Trigger<float> *get_set_trigger() const { return set_trigger_; }
|
||||||
void set_optimistic(bool optimistic);
|
void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
|
||||||
|
void set_initial_value(float initial_value) { initial_value_ = initial_value; }
|
||||||
|
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void set(float value) override;
|
void control(float value) override;
|
||||||
bool optimistic_{false};
|
bool optimistic_{false};
|
||||||
Trigger<float> *set_trigger_;
|
float initial_value_{NAN};
|
||||||
|
bool restore_value_{false};
|
||||||
|
Trigger<float> *set_trigger_ = new Trigger<float>();
|
||||||
optional<std::function<optional<float>()>> f_;
|
optional<std::function<optional<float>()>> f_;
|
||||||
|
|
||||||
|
ESPPreferenceObject pref_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace template_
|
} // namespace template_
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ void CronTrigger::loop() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (this->last_check_.has_value()) {
|
if (this->last_check_.has_value()) {
|
||||||
if (*this->last_check_ >= time) {
|
if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) {
|
||||||
|
// We went back in time (a lot), probably caused by time synchronization
|
||||||
|
ESP_LOGW(TAG, "Time has jumped back!");
|
||||||
|
} else if (*this->last_check_ >= time) {
|
||||||
// already handled this one
|
// already handled this one
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, time
|
from esphome.components import sensor, time
|
||||||
from esphome.const import CONF_ID, CONF_TIME_ID
|
from esphome.const import (
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TIME_ID,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ICON_EMPTY,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_EMPTY,
|
||||||
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["time"]
|
DEPENDENCIES = ["time"]
|
||||||
|
|
||||||
@@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_(
|
|||||||
"TotalDailyEnergy", sensor.Sensor, cg.Component
|
"TotalDailyEnergy", sensor.Sensor, cg.Component
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
CONFIG_SCHEMA = (
|
||||||
{
|
sensor.sensor_schema(
|
||||||
cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
|
UNIT_EMPTY,
|
||||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
ICON_EMPTY,
|
||||||
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
|
0,
|
||||||
}
|
DEVICE_CLASS_ENERGY,
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
LAST_RESET_TYPE_AUTO,
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(TotalDailyEnergy),
|
||||||
|
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
|
cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ namespace esphome {
|
|||||||
namespace tuya {
|
namespace tuya {
|
||||||
|
|
||||||
static const char *const TAG = "tuya";
|
static const char *const TAG = "tuya";
|
||||||
static const int COMMAND_DELAY = 50;
|
static const int COMMAND_DELAY = 10;
|
||||||
|
static const int RECEIVE_TIMEOUT = 300;
|
||||||
|
|
||||||
void Tuya::setup() {
|
void Tuya::setup() {
|
||||||
this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::loop() {
|
void Tuya::loop() {
|
||||||
@@ -113,11 +114,19 @@ void Tuya::handle_char_(uint8_t c) {
|
|||||||
this->rx_message_.push_back(c);
|
this->rx_message_.push_back(c);
|
||||||
if (!this->validate_message_()) {
|
if (!this->validate_message_()) {
|
||||||
this->rx_message_.clear();
|
this->rx_message_.clear();
|
||||||
|
} else {
|
||||||
|
this->last_rx_char_timestamp_ = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
|
||||||
switch ((TuyaCommandType) command) {
|
TuyaCommandType command_type = (TuyaCommandType) command;
|
||||||
|
|
||||||
|
if (this->expected_response_.has_value() && this->expected_response_ == command_type) {
|
||||||
|
this->expected_response_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command_type) {
|
||||||
case TuyaCommandType::HEARTBEAT:
|
case TuyaCommandType::HEARTBEAT:
|
||||||
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
|
||||||
this->protocol_version_ = version;
|
this->protocol_version_ = version;
|
||||||
@@ -316,6 +325,25 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
|||||||
uint8_t version = 0;
|
uint8_t version = 0;
|
||||||
|
|
||||||
this->last_command_timestamp_ = millis();
|
this->last_command_timestamp_ = millis();
|
||||||
|
switch (command.cmd) {
|
||||||
|
case TuyaCommandType::HEARTBEAT:
|
||||||
|
this->expected_response_ = TuyaCommandType::HEARTBEAT;
|
||||||
|
break;
|
||||||
|
case TuyaCommandType::PRODUCT_QUERY:
|
||||||
|
this->expected_response_ = TuyaCommandType::PRODUCT_QUERY;
|
||||||
|
break;
|
||||||
|
case TuyaCommandType::CONF_QUERY:
|
||||||
|
this->expected_response_ = TuyaCommandType::CONF_QUERY;
|
||||||
|
break;
|
||||||
|
case TuyaCommandType::DATAPOINT_DELIVER:
|
||||||
|
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||||
|
break;
|
||||||
|
case TuyaCommandType::DATAPOINT_QUERY:
|
||||||
|
this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
|
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
|
||||||
version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
|
version, hexencode(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||||
@@ -331,9 +359,20 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::process_command_queue_() {
|
void Tuya::process_command_queue_() {
|
||||||
uint32_t delay = millis() - this->last_command_timestamp_;
|
uint32_t now = millis();
|
||||||
|
uint32_t delay = now - this->last_command_timestamp_;
|
||||||
|
|
||||||
|
if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
|
||||||
|
this->rx_message_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
|
||||||
|
this->expected_response_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
// Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly
|
||||||
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) {
|
if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
|
||||||
|
!this->expected_response_.has_value()) {
|
||||||
this->send_raw_command_(command_queue_.front());
|
this->send_raw_command_(command_queue_.front());
|
||||||
this->command_queue_.erase(command_queue_.begin());
|
this->command_queue_.erase(command_queue_.begin());
|
||||||
}
|
}
|
||||||
@@ -345,7 +384,7 @@ void Tuya::send_command_(const TuyaCommand &command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::send_empty_command_(TuyaCommandType command) {
|
void Tuya::send_empty_command_(TuyaCommandType command) {
|
||||||
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{0x04}});
|
send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tuya::send_wifi_status_() {
|
void Tuya::send_wifi_status_() {
|
||||||
|
|||||||
@@ -107,12 +107,14 @@ class Tuya : public Component, public uart::UARTDevice {
|
|||||||
int gpio_status_ = -1;
|
int gpio_status_ = -1;
|
||||||
int gpio_reset_ = -1;
|
int gpio_reset_ = -1;
|
||||||
uint32_t last_command_timestamp_ = 0;
|
uint32_t last_command_timestamp_ = 0;
|
||||||
|
uint32_t last_rx_char_timestamp_ = 0;
|
||||||
std::string product_ = "";
|
std::string product_ = "";
|
||||||
std::vector<TuyaDatapointListener> listeners_;
|
std::vector<TuyaDatapointListener> listeners_;
|
||||||
std::vector<TuyaDatapoint> datapoints_;
|
std::vector<TuyaDatapoint> datapoints_;
|
||||||
std::vector<uint8_t> rx_message_;
|
std::vector<uint8_t> rx_message_;
|
||||||
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
std::vector<uint8_t> ignore_mcu_update_on_datapoints_{};
|
||||||
std::vector<TuyaCommand> command_queue_;
|
std::vector<TuyaCommand> command_queue_;
|
||||||
|
optional<TuyaCommandType> expected_response_{};
|
||||||
uint8_t wifi_status_ = -1;
|
uint8_t wifi_status_ = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
|||||||
std::string WebServer::number_json(number::Number *obj, float value) {
|
std::string WebServer::number_json(number::Number *obj, float value) {
|
||||||
return json::build_json([obj, value](JsonObject &root) {
|
return json::build_json([obj, value](JsonObject &root) {
|
||||||
root["id"] = "number-" + obj->get_object_id();
|
root["id"] = "number-" + obj->get_object_id();
|
||||||
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
|
char buffer[64];
|
||||||
root["state"] = state;
|
snprintf(buffer, sizeof(buffer), "%f", value);
|
||||||
|
root["state"] = buffer;
|
||||||
root["value"] = value;
|
root["value"] = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "1.20.0b3"
|
__version__ = "1.20.4"
|
||||||
|
|
||||||
ESP_PLATFORM_ESP32 = "ESP32"
|
ESP_PLATFORM_ESP32 = "ESP32"
|
||||||
ESP_PLATFORM_ESP8266 = "ESP8266"
|
ESP_PLATFORM_ESP8266 = "ESP8266"
|
||||||
@@ -297,6 +297,7 @@ CONF_KEY = "key"
|
|||||||
CONF_LAMBDA = "lambda"
|
CONF_LAMBDA = "lambda"
|
||||||
CONF_LAST_CONFIDENCE = "last_confidence"
|
CONF_LAST_CONFIDENCE = "last_confidence"
|
||||||
CONF_LAST_FINGER_ID = "last_finger_id"
|
CONF_LAST_FINGER_ID = "last_finger_id"
|
||||||
|
CONF_LAST_RESET_TYPE = "last_reset_type"
|
||||||
CONF_LATITUDE = "latitude"
|
CONF_LATITUDE = "latitude"
|
||||||
CONF_LENGTH = "length"
|
CONF_LENGTH = "length"
|
||||||
CONF_LEVEL = "level"
|
CONF_LEVEL = "level"
|
||||||
@@ -785,3 +786,10 @@ STATE_CLASS_NONE = ""
|
|||||||
|
|
||||||
# The state represents a measurement in present time
|
# The state represents a measurement in present time
|
||||||
STATE_CLASS_MEASUREMENT = "measurement"
|
STATE_CLASS_MEASUREMENT = "measurement"
|
||||||
|
|
||||||
|
# This sensor does not support resetting. ie, it is not accumulative
|
||||||
|
LAST_RESET_TYPE_NONE = ""
|
||||||
|
# This sensor is expected to never reset its value
|
||||||
|
LAST_RESET_TYPE_NEVER = "never"
|
||||||
|
# This sensor may reset and Home Assistant will watch for this
|
||||||
|
LAST_RESET_TYPE_AUTO = "auto"
|
||||||
|
|||||||
@@ -1104,7 +1104,7 @@ def shorthand_input_pullup_pin(value):
|
|||||||
|
|
||||||
def shorthand_analog_pin(value):
|
def shorthand_analog_pin(value):
|
||||||
value = analog_pin(value)
|
value = analog_pin(value)
|
||||||
return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value})
|
return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value})
|
||||||
|
|
||||||
|
|
||||||
def validate_has_interrupt(value):
|
def validate_has_interrupt(value):
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ ifaddr==0.1.7
|
|||||||
platformio==5.1.1
|
platformio==5.1.1
|
||||||
esptool==2.8
|
esptool==2.8
|
||||||
click==7.1.2
|
click==7.1.2
|
||||||
esphome-dashboard==20210623.0
|
esphome-dashboard==20210728.0
|
||||||
|
|||||||
Reference in New Issue
Block a user