mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
52 Commits
2021.12.1
...
oral-b-bru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a85b7b3f84 | ||
|
|
a207ed08a9 | ||
|
|
f431c7402f | ||
|
|
4907e6f6d7 | ||
|
|
1ccee86705 | ||
|
|
542fb2175b | ||
|
|
6ec9cfb044 | ||
|
|
66e0ff8392 | ||
|
|
1fb0a7109d | ||
|
|
192eb49589 | ||
|
|
5d70ff702b | ||
|
|
a7b05db2a1 | ||
|
|
45e346cf1b | ||
|
|
80e2bfada3 | ||
|
|
16e7bd0388 | ||
|
|
b3fb35783e | ||
|
|
a79c6aa9e0 | ||
|
|
4bb58b2de9 | ||
|
|
4e10881331 | ||
|
|
cec4a81e14 | ||
|
|
da45923d05 | ||
|
|
31a61b598b | ||
|
|
9c0506592b | ||
|
|
beeb0c7c5a | ||
|
|
b2f05faee0 | ||
|
|
8375e1d64d | ||
|
|
cf5193d3e5 | ||
|
|
c490388e80 | ||
|
|
24ec5a6e9d | ||
|
|
6df1d5222d | ||
|
|
58fb7a02f6 | ||
|
|
3d51ac8df0 | ||
|
|
6fe4ff7f85 | ||
|
|
2253d4bc16 | ||
|
|
e5cc19de43 | ||
|
|
5404617d43 | ||
|
|
12467a18e6 | ||
|
|
1db7043a4d | ||
|
|
49932747b3 | ||
|
|
55db190875 | ||
|
|
71fe2f7ed3 | ||
|
|
ffc112c9d0 | ||
|
|
d3e48e296f | ||
|
|
14f6ae75ea | ||
|
|
c84efe64d3 | ||
|
|
10e89a7dbb | ||
|
|
ef44acbf10 | ||
|
|
06da540ab0 | ||
|
|
40c017fd54 | ||
|
|
f0bcf81a98 | ||
|
|
6a0b343289 | ||
|
|
90c3cb62b3 |
@@ -65,6 +65,7 @@ esphome/components/globals/* @esphome/core
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/havells_solar/* @sourabhjaiswal
|
||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||
esphome/components/hbridge/light/* @DotNetDann
|
||||
@@ -132,7 +133,7 @@ esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rgbct/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/safe_mode/* @paulmonigatti
|
||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||
esphome/components/scd4x/* @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
@@ -142,7 +143,7 @@ esphome/components/select/* @esphome/core
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/socket/* @esphome/core
|
||||
@@ -179,6 +180,7 @@ esphome/components/toshiba/* @kbx81
|
||||
esphome/components/tsl2591/* @wjcarpenter
|
||||
esphome/components/tuya/binary_sensor/* @jesserockz
|
||||
esphome/components/tuya/climate/* @jesserockz
|
||||
esphome/components/tuya/number/* @frankiboy1
|
||||
esphome/components/tuya/sensor/* @jesserockz
|
||||
esphome/components/tuya/switch/* @jesserockz
|
||||
esphome/components/tuya/text_sensor/* @dentra
|
||||
|
||||
@@ -145,6 +145,8 @@ def wrap_to_code(name, comp):
|
||||
if comp.config_schema is not None:
|
||||
conf_str = yaml_util.dump(conf)
|
||||
conf_str = conf_str.replace("//", "")
|
||||
# remove tailing \ to avoid multi-line comment warning
|
||||
conf_str = conf_str.replace("\\\n", "\n")
|
||||
cg.add(cg.LineComment(indent(conf_str)))
|
||||
await coro(conf)
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ void ADCSensor::dump_config() {
|
||||
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void ADCSensor::update() {
|
||||
float value_v = this->sample();
|
||||
ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
|
||||
// uncomment for even more debugging
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
@@ -546,7 +546,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
|
||||
size_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
@@ -855,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
|
||||
// uncomment for even more debugging
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str());
|
||||
ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
|
||||
#endif
|
||||
frame->msg = std::move(rx_buf_);
|
||||
// consume msg
|
||||
@@ -934,7 +935,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt
|
||||
size_t total_write_len = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
ESP_LOGVV(TAG, "Sending raw: %s",
|
||||
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
|
||||
#endif
|
||||
total_write_len += iov[i].iov_len;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ void BME280Component::update() {
|
||||
float pressure = this->read_pressure_(data, t_fine);
|
||||
float humidity = this->read_humidity_(data, t_fine);
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
|
||||
@@ -29,12 +29,11 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
WIFI_MESSAGE = """
|
||||
WIFI_CONFIG = """
|
||||
|
||||
# Do not forget to add your own wifi configuration before installing this configuration
|
||||
# wifi:
|
||||
# ssid: !secret wifi_ssid
|
||||
# password: !secret wifi_password
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
"""
|
||||
|
||||
|
||||
@@ -55,6 +54,6 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
|
||||
"esphome": {"name_add_mac_suffix": False},
|
||||
}
|
||||
p.write_text(
|
||||
dump(config) + WIFI_MESSAGE,
|
||||
dump(config) + WIFI_CONFIG,
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
@@ -41,15 +41,30 @@ EXT1_WAKEUP_MODES = {
|
||||
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
|
||||
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
|
||||
}
|
||||
WakeupCauseToRunDuration = deep_sleep_ns.struct("WakeupCauseToRunDuration")
|
||||
|
||||
CONF_WAKEUP_PIN_MODE = "wakeup_pin_mode"
|
||||
CONF_ESP32_EXT1_WAKEUP = "esp32_ext1_wakeup"
|
||||
CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||
CONF_DEFAULT = "default"
|
||||
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
|
||||
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
|
||||
|
||||
WAKEUP_CAUSES_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DEFAULT): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_TOUCH_WAKEUP_REASON): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_GPIO_WAKEUP_REASON): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeepSleepComponent),
|
||||
cv.Optional(CONF_RUN_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_RUN_DURATION): cv.Any(
|
||||
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
|
||||
cv.positive_time_period_milliseconds,
|
||||
),
|
||||
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_WAKEUP_PIN): cv.All(
|
||||
cv.only_on_esp32, pins.internal_gpio_input_pin_schema, validate_pin_number
|
||||
@@ -85,7 +100,28 @@ async def to_code(config):
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
|
||||
if CONF_RUN_DURATION in config:
|
||||
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
|
||||
run_duration_config = config[CONF_RUN_DURATION]
|
||||
if not isinstance(run_duration_config, dict):
|
||||
cg.add(var.set_run_duration(config[CONF_RUN_DURATION]))
|
||||
else:
|
||||
default_run_duration = run_duration_config[CONF_DEFAULT]
|
||||
wakeup_cause_to_run_duration = cg.StructInitializer(
|
||||
WakeupCauseToRunDuration,
|
||||
("default_cause", default_run_duration),
|
||||
(
|
||||
"touch_cause",
|
||||
run_duration_config.get(
|
||||
CONF_TOUCH_WAKEUP_REASON, default_run_duration
|
||||
),
|
||||
),
|
||||
(
|
||||
"gpio_cause",
|
||||
run_duration_config.get(
|
||||
CONF_GPIO_WAKEUP_REASON, default_run_duration
|
||||
),
|
||||
),
|
||||
)
|
||||
cg.add(var.set_run_duration(wakeup_cause_to_run_duration))
|
||||
|
||||
if CONF_ESP32_EXT1_WAKEUP in config:
|
||||
conf = config[CONF_ESP32_EXT1_WAKEUP]
|
||||
|
||||
@@ -13,12 +13,35 @@ static const char *const TAG = "deep_sleep";
|
||||
|
||||
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const {
|
||||
#ifdef USE_ESP32
|
||||
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
|
||||
switch (wakeup_cause) {
|
||||
case ESP_SLEEP_WAKEUP_EXT0:
|
||||
case ESP_SLEEP_WAKEUP_EXT1:
|
||||
return this->wakeup_cause_to_run_duration_->gpio_cause;
|
||||
case ESP_SLEEP_WAKEUP_TOUCHPAD:
|
||||
return this->wakeup_cause_to_run_duration_->touch_cause;
|
||||
default:
|
||||
return this->wakeup_cause_to_run_duration_->default_cause;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return this->run_duration_;
|
||||
}
|
||||
|
||||
void DeepSleepComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
||||
global_has_deep_sleep = true;
|
||||
|
||||
if (this->run_duration_.has_value())
|
||||
this->set_timeout(*this->run_duration_, [this]() { this->begin_sleep(); });
|
||||
const optional<uint32_t> run_duration = get_run_duration_();
|
||||
if (run_duration.has_value()) {
|
||||
ESP_LOGI(TAG, "Scheduling Deep Sleep to start in %u ms", *run_duration);
|
||||
this->set_timeout(*run_duration, [this]() { this->begin_sleep(); });
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Not scheduling Deep Sleep, as no run duration is configured.");
|
||||
}
|
||||
}
|
||||
void DeepSleepComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Deep Sleep...");
|
||||
@@ -33,6 +56,11 @@ void DeepSleepComponent::dump_config() {
|
||||
if (wakeup_pin_ != nullptr) {
|
||||
LOG_PIN(" Wakeup Pin: ", this->wakeup_pin_);
|
||||
}
|
||||
if (this->wakeup_cause_to_run_duration_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Default Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->default_cause);
|
||||
ESP_LOGCONFIG(TAG, " Touch Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->touch_cause);
|
||||
ESP_LOGCONFIG(TAG, " GPIO Wakeup Run Duration: %u ms", this->wakeup_cause_to_run_duration_->gpio_cause);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void DeepSleepComponent::loop() {
|
||||
@@ -49,6 +77,9 @@ void DeepSleepComponent::set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode) {
|
||||
}
|
||||
void DeepSleepComponent::set_ext1_wakeup(Ext1Wakeup ext1_wakeup) { this->ext1_wakeup_ = ext1_wakeup; }
|
||||
void DeepSleepComponent::set_touch_wakeup(bool touch_wakeup) { this->touch_wakeup_ = touch_wakeup; }
|
||||
void DeepSleepComponent::set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration) {
|
||||
wakeup_cause_to_run_duration_ = wakeup_cause_to_run_duration;
|
||||
}
|
||||
#endif
|
||||
void DeepSleepComponent::set_run_duration(uint32_t time_ms) { this->run_duration_ = time_ms; }
|
||||
void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
|
||||
@@ -32,6 +32,15 @@ struct Ext1Wakeup {
|
||||
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
||||
};
|
||||
|
||||
struct WakeupCauseToRunDuration {
|
||||
// Run duration if woken up by timer or any other reason besides those below.
|
||||
uint32_t default_cause;
|
||||
// Run duration if woken up by touch pads.
|
||||
uint32_t touch_cause;
|
||||
// Run duration if woken up by GPIO pins.
|
||||
uint32_t gpio_cause;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
template<typename... Ts> class EnterDeepSleepAction;
|
||||
@@ -59,6 +68,11 @@ class DeepSleepComponent : public Component {
|
||||
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
|
||||
|
||||
void set_touch_wakeup(bool touch_wakeup);
|
||||
|
||||
// Set the duration in ms for how long the code should run before entering
|
||||
// deep sleep mode, according to the cause the ESP32 has woken.
|
||||
void set_run_duration(WakeupCauseToRunDuration wakeup_cause_to_run_duration);
|
||||
|
||||
#endif
|
||||
/// Set a duration in ms for how long the code should run before entering deep sleep mode.
|
||||
void set_run_duration(uint32_t time_ms);
|
||||
@@ -75,12 +89,17 @@ class DeepSleepComponent : public Component {
|
||||
void prevent_deep_sleep();
|
||||
|
||||
protected:
|
||||
// Returns nullopt if no run duration is set. Otherwise, returns the run
|
||||
// duration before entering deep sleep.
|
||||
optional<uint32_t> get_run_duration_() const;
|
||||
|
||||
optional<uint64_t> sleep_duration_;
|
||||
#ifdef USE_ESP32
|
||||
InternalGPIOPin *wakeup_pin_;
|
||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||
optional<Ext1Wakeup> ext1_wakeup_;
|
||||
optional<bool> touch_wakeup_;
|
||||
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||
#endif
|
||||
optional<uint32_t> run_duration_;
|
||||
bool next_enter_deep_sleep_{false};
|
||||
|
||||
@@ -23,22 +23,24 @@ void DutyCycleSensor::dump_config() {
|
||||
}
|
||||
void DutyCycleSensor::update() {
|
||||
const uint32_t now = micros();
|
||||
const uint32_t last_interrupt = this->store_.last_interrupt; // Read the measurement taken by the interrupt
|
||||
uint32_t on_time = this->store_.on_time;
|
||||
|
||||
this->store_.on_time = 0; // Start new measurement, exactly aligned with the micros() reading
|
||||
this->store_.last_interrupt = now;
|
||||
|
||||
if (this->last_update_ != 0) {
|
||||
const bool level = this->store_.last_level;
|
||||
const uint32_t last_interrupt = this->store_.last_interrupt;
|
||||
uint32_t on_time = this->store_.on_time;
|
||||
|
||||
if (level)
|
||||
on_time += now - last_interrupt;
|
||||
|
||||
const float total_time = float(now - this->last_update_);
|
||||
|
||||
const float value = (on_time / total_time) * 100.0f;
|
||||
const float value = (on_time * 100.0f) / total_time;
|
||||
ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
|
||||
this->publish_state(value);
|
||||
}
|
||||
this->store_.on_time = 0;
|
||||
this->store_.last_interrupt = now;
|
||||
this->last_update_ = now;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,11 +42,11 @@ void arch_init() {
|
||||
// Idle task watchdog is disabled on ESP-IDF
|
||||
#elif defined(USE_ARDUINO)
|
||||
enableLoopWDT();
|
||||
// Disable idle task watchdog on the core we're using (Arduino pins the process to a core)
|
||||
#if CONFIG_ARDUINO_RUNNING_CORE == 0
|
||||
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
|
||||
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
|
||||
disableCore0WDT();
|
||||
#endif
|
||||
#if CONFIG_ARDUINO_RUNNING_CORE == 1
|
||||
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
|
||||
disableCore1WDT();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ _ESP_SDIO_PINS = {
|
||||
11: "Flash Command",
|
||||
}
|
||||
|
||||
_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
|
||||
_ESP32_STRAPPING_PINS = {0, 2, 4, 12, 15}
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -483,6 +483,7 @@ optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData
|
||||
}
|
||||
|
||||
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
this->scan_result_ = param;
|
||||
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
||||
this->address_[i] = param.bda[i];
|
||||
this->address_type_ = param.ble_addr_type;
|
||||
@@ -524,7 +525,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
||||
}
|
||||
for (auto &data : this->manufacturer_datas_) {
|
||||
ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str());
|
||||
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str());
|
||||
if (this->get_ibeacon().has_value()) {
|
||||
auto ibeacon = this->get_ibeacon().value();
|
||||
ESP_LOGVV(TAG, " iBeacon data:");
|
||||
@@ -537,10 +538,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
|
||||
for (auto &data : this->service_datas_) {
|
||||
ESP_LOGVV(TAG, " Service data:");
|
||||
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
|
||||
ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str());
|
||||
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
||||
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
||||
#endif
|
||||
}
|
||||
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
|
||||
@@ -97,6 +97,8 @@ class ESPBTDevice {
|
||||
|
||||
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
||||
|
||||
const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; }
|
||||
|
||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||
for (auto &it : this->manufacturer_datas_) {
|
||||
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
||||
@@ -121,6 +123,7 @@ class ESPBTDevice {
|
||||
std::vector<ESPBTUUID> service_uuids_;
|
||||
std::vector<ServiceData> manufacturer_datas_{};
|
||||
std::vector<ServiceData> service_datas_{};
|
||||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
|
||||
};
|
||||
|
||||
class ESP32BLETracker;
|
||||
|
||||
@@ -57,6 +57,9 @@ CONF_IDLE_FRAMERATE = "idle_framerate"
|
||||
CONF_JPEG_QUALITY = "jpeg_quality"
|
||||
CONF_VERTICAL_FLIP = "vertical_flip"
|
||||
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
|
||||
CONF_AEC2 = "aec2"
|
||||
CONF_AE_LEVEL = "ae_level"
|
||||
CONF_AEC_VALUE = "aec_value"
|
||||
CONF_SATURATION = "saturation"
|
||||
CONF_TEST_PATTERN = "test_pattern"
|
||||
|
||||
@@ -102,6 +105,9 @@ CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
||||
cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean,
|
||||
cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean,
|
||||
cv.Optional(CONF_AEC2, default=False): cv.boolean,
|
||||
cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param,
|
||||
cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200),
|
||||
cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -116,6 +122,9 @@ SETTERS = {
|
||||
CONF_JPEG_QUALITY: "set_jpeg_quality",
|
||||
CONF_VERTICAL_FLIP: "set_vertical_flip",
|
||||
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
|
||||
CONF_AEC2: "set_aec2",
|
||||
CONF_AE_LEVEL: "set_ae_level",
|
||||
CONF_AEC_VALUE: "set_aec_value",
|
||||
CONF_CONTRAST: "set_contrast",
|
||||
CONF_BRIGHTNESS: "set_brightness",
|
||||
CONF_SATURATION: "set_saturation",
|
||||
|
||||
@@ -26,6 +26,9 @@ void ESP32Camera::setup() {
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
s->set_vflip(s, this->vertical_flip_);
|
||||
s->set_hmirror(s, this->horizontal_mirror_);
|
||||
s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable
|
||||
s->set_ae_level(s, this->ae_level_); // -2 to 2
|
||||
s->set_aec_value(s, this->aec_value_); // 0 to 1200
|
||||
s->set_contrast(s, this->contrast_);
|
||||
s->set_brightness(s, this->brightness_);
|
||||
s->set_saturation(s, this->saturation_);
|
||||
@@ -111,9 +114,9 @@ void ESP32Camera::dump_config() {
|
||||
// ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb);
|
||||
// ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain);
|
||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec);
|
||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
|
||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
|
||||
// ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level);
|
||||
ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value);
|
||||
// ESP_LOGCONFIG(TAG, " AGC: %u", st.agc);
|
||||
// ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain);
|
||||
// ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling);
|
||||
@@ -250,6 +253,9 @@ void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraIm
|
||||
}
|
||||
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
|
||||
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
|
||||
void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; }
|
||||
void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; }
|
||||
void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; }
|
||||
void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; }
|
||||
void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; }
|
||||
void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; }
|
||||
|
||||
@@ -67,6 +67,9 @@ class ESP32Camera : public Component, public EntityBase {
|
||||
void set_power_down_pin(uint8_t pin);
|
||||
void set_vertical_flip(bool vertical_flip);
|
||||
void set_horizontal_mirror(bool horizontal_mirror);
|
||||
void set_aec2(bool aec2);
|
||||
void set_ae_level(int ae_level);
|
||||
void set_aec_value(uint32_t aec_value);
|
||||
void set_contrast(int contrast);
|
||||
void set_brightness(int brightness);
|
||||
void set_saturation(int saturation);
|
||||
@@ -91,6 +94,9 @@ class ESP32Camera : public Component, public EntityBase {
|
||||
camera_config_t config_{};
|
||||
bool vertical_flip_{true};
|
||||
bool horizontal_mirror_{true};
|
||||
bool aec2_{false};
|
||||
int ae_level_{0};
|
||||
uint32_t aec_value_{300};
|
||||
int contrast_{0};
|
||||
int brightness_{0};
|
||||
int saturation_{0};
|
||||
|
||||
@@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() {
|
||||
void ESP32ImprovComponent::process_incoming_data_() {
|
||||
uint8_t length = this->incoming_data_[1];
|
||||
|
||||
ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str());
|
||||
ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str());
|
||||
if (this->incoming_data_.size() - 3 == length) {
|
||||
this->set_error_(improv::ERROR_NONE);
|
||||
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
|
||||
|
||||
@@ -184,7 +184,9 @@ void EthernetComponent::start_connect_() {
|
||||
}
|
||||
|
||||
err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_ETH);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
|
||||
if (err != ESP_ERR_TCPIP_ADAPTER_DHCP_ALREADY_STOPPED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC stop error");
|
||||
}
|
||||
err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_ETH, &info);
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ void EZOSensor::loop() {
|
||||
if (buf[i] == ',')
|
||||
buf[i] = '\0';
|
||||
|
||||
float val = parse_number<float>((char *) &buf[1], sizeof(buf) - 2).value_or(0);
|
||||
float val = parse_number<float>((char *) &buf[1]).value_or(0);
|
||||
this->publish_state(val);
|
||||
}
|
||||
|
||||
|
||||
0
esphome/components/growatt_solar/__init__.py
Normal file
0
esphome/components/growatt_solar/__init__.py
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
69
esphome/components/growatt_solar/growatt_solar.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "growatt_solar.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
|
||||
static const char *const TAG = "growatt_solar";
|
||||
|
||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t MODBUS_REGISTER_COUNT = 33;
|
||||
|
||||
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
|
||||
|
||||
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
float value = encode_uint16(data[i * 2], data[i * 2 + 1]) * unit;
|
||||
sensor->publish_state(value);
|
||||
};
|
||||
|
||||
auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg1, size_t reg2, float unit) -> void {
|
||||
float value = ((encode_uint16(data[reg1 * 2], data[reg1 * 2 + 1]) << 16) +
|
||||
encode_uint16(data[reg2 * 2], data[reg2 * 2 + 1])) *
|
||||
unit;
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state(value);
|
||||
};
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||
|
||||
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
|
||||
}
|
||||
|
||||
void GrowattSolar::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GROWATT Solar:");
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
}
|
||||
|
||||
} // namespace growatt_solar
|
||||
} // namespace esphome
|
||||
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
73
esphome/components/growatt_solar/growatt_solar.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace growatt_solar {
|
||||
|
||||
static const float TWO_DEC_UNIT = 0.01;
|
||||
static const float ONE_DEC_UNIT = 0.1;
|
||||
|
||||
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void update() override;
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
|
||||
|
||||
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
|
||||
void set_grid_active_power_sensor(sensor::Sensor *sensor) { this->grid_active_power_sensor_ = sensor; }
|
||||
void set_pv_active_power_sensor(sensor::Sensor *sensor) { this->pv_active_power_sensor_ = sensor; }
|
||||
|
||||
void set_today_production_sensor(sensor::Sensor *sensor) { this->today_production_ = sensor; }
|
||||
void set_total_energy_production_sensor(sensor::Sensor *sensor) { this->total_energy_production_ = sensor; }
|
||||
void set_inverter_module_temp_sensor(sensor::Sensor *sensor) { this->inverter_module_temp_ = sensor; }
|
||||
|
||||
void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) {
|
||||
this->phases_[phase].voltage_sensor_ = voltage_sensor;
|
||||
}
|
||||
void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) {
|
||||
this->phases_[phase].current_sensor_ = current_sensor;
|
||||
}
|
||||
void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) {
|
||||
this->phases_[phase].active_power_sensor_ = active_power_sensor;
|
||||
}
|
||||
void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) {
|
||||
this->pvs_[pv].voltage_sensor_ = voltage_sensor;
|
||||
}
|
||||
void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) {
|
||||
this->pvs_[pv].current_sensor_ = current_sensor;
|
||||
}
|
||||
void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) {
|
||||
this->pvs_[pv].active_power_sensor_ = active_power_sensor;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct GrowattPhase {
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_sensor_{nullptr};
|
||||
} phases_[3];
|
||||
struct GrowattPV {
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_sensor_{nullptr};
|
||||
} pvs_[2];
|
||||
|
||||
sensor::Sensor *inverter_status_{nullptr};
|
||||
|
||||
sensor::Sensor *grid_frequency_sensor_{nullptr};
|
||||
sensor::Sensor *grid_active_power_sensor_{nullptr};
|
||||
|
||||
sensor::Sensor *pv_active_power_sensor_{nullptr};
|
||||
|
||||
sensor::Sensor *today_production_{nullptr};
|
||||
sensor::Sensor *total_energy_production_{nullptr};
|
||||
sensor::Sensor *inverter_module_temp_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace growatt_solar
|
||||
} // namespace esphome
|
||||
201
esphome/components/growatt_solar/sensor.py
Normal file
201
esphome/components/growatt_solar/sensor.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, modbus
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_CURRENT,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_CURRENT_AC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
CONF_PHASE_A = "phase_a"
|
||||
CONF_PHASE_B = "phase_b"
|
||||
CONF_PHASE_C = "phase_c"
|
||||
|
||||
CONF_ENERGY_PRODUCTION_DAY = "energy_production_day"
|
||||
CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production"
|
||||
CONF_TOTAL_GENERATION_TIME = "total_generation_time"
|
||||
CONF_TODAY_GENERATION_TIME = "today_generation_time"
|
||||
CONF_PV1 = "pv1"
|
||||
CONF_PV2 = "pv2"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_HOURS = "h"
|
||||
UNIT_KOHM = "kΩ"
|
||||
UNIT_MILLIAMPERE = "mA"
|
||||
|
||||
CONF_INVERTER_STATUS = "inverter_status"
|
||||
CONF_PV_ACTIVE_POWER = "pv_active_power"
|
||||
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
|
||||
|
||||
|
||||
AUTO_LOAD = ["modbus"]
|
||||
CODEOWNERS = ["@leeuwte"]
|
||||
|
||||
growatt_solar_ns = cg.esphome_ns.namespace("growatt_solar")
|
||||
GrowattSolar = growatt_solar_ns.class_(
|
||||
"GrowattSolar", cg.PollingComponent, modbus.ModbusDevice
|
||||
)
|
||||
|
||||
PHASE_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
PV_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
PHASE_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()}
|
||||
)
|
||||
PV_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GrowattSolar),
|
||||
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PV1): PV_SCHEMA,
|
||||
cv.Optional(CONF_PV2): PV_SCHEMA,
|
||||
cv.Optional(CONF_INVERTER_STATUS): sensor.sensor_schema(),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PV_ACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("10s"))
|
||||
.extend(modbus.modbus_device_schema(0x01))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await modbus.register_modbus_device(var, config)
|
||||
|
||||
if CONF_INVERTER_STATUS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
|
||||
cg.add(var.set_inverter_status_sensor(sens))
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_grid_frequency_sensor(sens))
|
||||
|
||||
if CONF_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER])
|
||||
cg.add(var.set_grid_active_power_sensor(sens))
|
||||
|
||||
if CONF_PV_ACTIVE_POWER in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PV_ACTIVE_POWER])
|
||||
cg.add(var.set_pv_active_power_sensor(sens))
|
||||
|
||||
if CONF_ENERGY_PRODUCTION_DAY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY])
|
||||
cg.add(var.set_today_production_sensor(sens))
|
||||
|
||||
if CONF_TOTAL_ENERGY_PRODUCTION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION])
|
||||
cg.add(var.set_total_energy_production_sensor(sens))
|
||||
|
||||
if CONF_INVERTER_MODULE_TEMP in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP])
|
||||
cg.add(var.set_inverter_module_temp_sensor(sens))
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
continue
|
||||
|
||||
phase_config = config[phase]
|
||||
for sensor_type in PHASE_SENSORS:
|
||||
if sensor_type in phase_config:
|
||||
sens = await sensor.new_sensor(phase_config[sensor_type])
|
||||
cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens))
|
||||
|
||||
for i, pv in enumerate([CONF_PV1, CONF_PV2]):
|
||||
if pv not in config:
|
||||
continue
|
||||
|
||||
pv_config = config[pv]
|
||||
for sensor_type in pv_config:
|
||||
if sensor_type in pv_config:
|
||||
sens = await sensor.new_sensor(pv_config[sensor_type])
|
||||
cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens))
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace i2c {
|
||||
@@ -40,6 +42,20 @@ class I2CBus {
|
||||
return writev(address, &buf, 1);
|
||||
}
|
||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
|
||||
|
||||
protected:
|
||||
void i2c_scan_() {
|
||||
for (uint8_t address = 8; address < 120; address++) {
|
||||
auto err = writev(address, nullptr, 0);
|
||||
if (err == ERROR_OK) {
|
||||
scan_results_.emplace_back(address, true);
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
scan_results_.emplace_back(address, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<uint8_t, bool>> scan_results_;
|
||||
bool scan_{false};
|
||||
};
|
||||
|
||||
} // namespace i2c
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "i2c_bus_arduino.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <Arduino.h>
|
||||
#include <cstring>
|
||||
|
||||
@@ -27,6 +28,10 @@ void ArduinoI2CBus::setup() {
|
||||
wire_->begin(sda_pin_, scl_pin_);
|
||||
wire_->setClock(frequency_);
|
||||
initialized_ = true;
|
||||
if (this->scan_) {
|
||||
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
|
||||
this->i2c_scan_();
|
||||
}
|
||||
}
|
||||
void ArduinoI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
@@ -45,22 +50,20 @@ void ArduinoI2CBus::dump_config() {
|
||||
break;
|
||||
}
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
||||
uint8_t found = 0;
|
||||
for (uint8_t address = 8; address < 120; address++) {
|
||||
auto err = writev(address, nullptr, 0);
|
||||
if (err == ERROR_OK) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
||||
found++;
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
||||
}
|
||||
}
|
||||
if (found == 0) {
|
||||
ESP_LOGI(TAG, "Results from i2c bus scan:");
|
||||
if (scan_results_.empty()) {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
} else {
|
||||
for (const auto &s : scan_results_) {
|
||||
if (s.second)
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||
else
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||
// logging is only enabled with vv level, if warnings are shown the caller
|
||||
// should log them
|
||||
|
||||
@@ -34,7 +34,6 @@ class ArduinoI2CBus : public I2CBus, public Component {
|
||||
|
||||
protected:
|
||||
TwoWire *wire_;
|
||||
bool scan_;
|
||||
uint8_t sda_pin_;
|
||||
uint8_t scl_pin_;
|
||||
uint32_t frequency_;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "i2c_bus_esp_idf.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace esphome {
|
||||
@@ -37,6 +38,10 @@ void IDFI2CBus::setup() {
|
||||
return;
|
||||
}
|
||||
initialized_ = true;
|
||||
if (this->scan_) {
|
||||
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
|
||||
this->i2c_scan_();
|
||||
}
|
||||
}
|
||||
void IDFI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
@@ -55,23 +60,20 @@ void IDFI2CBus::dump_config() {
|
||||
break;
|
||||
}
|
||||
if (this->scan_) {
|
||||
ESP_LOGI(TAG, "Scanning i2c bus for active devices...");
|
||||
uint8_t found = 0;
|
||||
for (uint8_t address = 8; address < 120; address++) {
|
||||
auto err = writev(address, nullptr, 0);
|
||||
|
||||
if (err == ERROR_OK) {
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", address);
|
||||
found++;
|
||||
} else if (err == ERROR_UNKNOWN) {
|
||||
ESP_LOGI(TAG, "Unknown error at address 0x%02X", address);
|
||||
}
|
||||
}
|
||||
if (found == 0) {
|
||||
ESP_LOGI(TAG, "Results from i2c bus scan:");
|
||||
if (scan_results_.empty()) {
|
||||
ESP_LOGI(TAG, "Found no i2c devices!");
|
||||
} else {
|
||||
for (const auto &s : scan_results_) {
|
||||
if (s.second)
|
||||
ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
|
||||
else
|
||||
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||
// logging is only enabled with vv level, if warnings are shown the caller
|
||||
// should log them
|
||||
|
||||
@@ -36,7 +36,6 @@ class IDFI2CBus : public I2CBus, public Component {
|
||||
|
||||
protected:
|
||||
i2c_port_t port_;
|
||||
bool scan_;
|
||||
uint8_t sda_pin_;
|
||||
bool sda_pullup_enabled_;
|
||||
uint8_t scl_pin_;
|
||||
|
||||
@@ -14,6 +14,7 @@ from esphome.const import (
|
||||
CONF_RESTORE_MODE,
|
||||
CONF_ON_TURN_OFF,
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_ON_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE,
|
||||
CONF_WARM_WHITE_COLOR_TEMPERATURE,
|
||||
@@ -37,6 +38,7 @@ from .types import ( # noqa
|
||||
AddressableLight,
|
||||
LightTurnOnTrigger,
|
||||
LightTurnOffTrigger,
|
||||
LightStateTrigger,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -69,6 +71,11 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightTurnOffTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): auto.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -151,6 +158,9 @@ async def setup_light_core_(light_var, output_var, config):
|
||||
for conf in config.get(CONF_ON_TURN_OFF, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
||||
await auto.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], light_var)
|
||||
await auto.build_automation(trigger, [], conf)
|
||||
|
||||
if CONF_COLOR_CORRECT in config:
|
||||
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
|
||||
|
||||
@@ -141,6 +141,13 @@ class LightTurnOffTrigger : public Trigger<> {
|
||||
}
|
||||
};
|
||||
|
||||
class LightStateTrigger : public Trigger<> {
|
||||
public:
|
||||
LightStateTrigger(LightState *a_light) {
|
||||
a_light->add_new_remote_values_callback([this]() { this->trigger(); });
|
||||
}
|
||||
};
|
||||
|
||||
// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet
|
||||
// due to the template. It's just a temporary warning anyway.
|
||||
void addressableset_warn_about_scale(const char *field);
|
||||
|
||||
@@ -41,6 +41,7 @@ LightTurnOnTrigger = light_ns.class_(
|
||||
LightTurnOffTrigger = light_ns.class_(
|
||||
"LightTurnOffTrigger", automation.Trigger.template()
|
||||
)
|
||||
LightStateTrigger = light_ns.class_("LightStateTrigger", automation.Trigger.template())
|
||||
|
||||
# Effects
|
||||
LightEffect = light_ns.class_("LightEffect")
|
||||
|
||||
@@ -24,6 +24,7 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
uint8_t gppu = pin < 8 ? mcp23x17_base::MCP23X17_GPPUA : mcp23x17_base::MCP23X17_GPPUB;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
this->update_reg(pin, true, iodir);
|
||||
this->update_reg(pin, false, gppu);
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
this->update_reg(pin, true, iodir);
|
||||
this->update_reg(pin, true, gppu);
|
||||
|
||||
@@ -23,7 +23,7 @@ void MD5Digest::get_hex(char *output) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MD5Digest::equals_bytes(const char *expected) {
|
||||
bool MD5Digest::equals_bytes(const uint8_t *expected) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
if (expected[i] != this->digest_[i]) {
|
||||
return false;
|
||||
@@ -33,18 +33,10 @@ bool MD5Digest::equals_bytes(const char *expected) {
|
||||
}
|
||||
|
||||
bool MD5Digest::equals_hex(const char *expected) {
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
auto high = parse_hex(expected[i * 2]);
|
||||
auto low = parse_hex(expected[i * 2 + 1]);
|
||||
if (!high.has_value() || !low.has_value()) {
|
||||
return false;
|
||||
}
|
||||
auto value = (*high << 4) | *low;
|
||||
if (value != this->digest_[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
uint8_t parsed[16];
|
||||
if (!parse_hex(expected, parsed, 16))
|
||||
return false;
|
||||
return equals_bytes(parsed);
|
||||
}
|
||||
|
||||
} // namespace md5
|
||||
|
||||
@@ -44,7 +44,7 @@ class MD5Digest {
|
||||
void get_hex(char *output);
|
||||
|
||||
/// Compare the digest against a provided byte-encoded digest (16 bytes).
|
||||
bool equals_bytes(const char *expected);
|
||||
bool equals_bytes(const uint8_t *expected);
|
||||
|
||||
/// Compare the digest against a provided hex-encoded digest (32 bytes).
|
||||
bool equals_hex(const char *expected);
|
||||
|
||||
@@ -181,7 +181,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
||||
this->flow_control_pin_->digital_write(false);
|
||||
waiting_for_response = address;
|
||||
last_send_ = millis();
|
||||
ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str());
|
||||
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str());
|
||||
}
|
||||
|
||||
// Helper function for lambdas
|
||||
@@ -202,7 +202,7 @@ void Modbus::send_raw(const std::vector<uint8_t> &payload) {
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
this->flow_control_pin_->digital_write(false);
|
||||
waiting_for_response = payload[0];
|
||||
ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str());
|
||||
ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str());
|
||||
last_send_ = millis();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@ CONF_REGISTER_COUNT = "register_count"
|
||||
CONF_REGISTER_TYPE = "register_type"
|
||||
CONF_RESPONSE_SIZE = "response_size"
|
||||
CONF_SKIP_UPDATES = "skip_updates"
|
||||
CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
|
||||
CONF_VALUE_TYPE = "value_type"
|
||||
CONF_WRITE_LAMBDA = "write_lambda"
|
||||
|
||||
@@ -25,6 +25,7 @@ from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
@@ -69,6 +70,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_MIN_VALUE, default=-16777215.0): cv.float_,
|
||||
cv.Optional(CONF_STEP, default=1): cv.positive_float,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
@@ -105,6 +107,7 @@ async def to_code(config):
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(parent.add_sensor_item(var))
|
||||
await add_modbus_base_properties(var, config, ModbusNumber)
|
||||
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace modbus_controller {
|
||||
static const char *const TAG = "modbus.number";
|
||||
|
||||
void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
float result = payload_to_float(data, *this);
|
||||
float result = payload_to_float(data, *this) / multiply_by_;
|
||||
|
||||
// Is there a lambda registered
|
||||
// call it with the pre converted value and the raw data array
|
||||
@@ -27,6 +27,7 @@ void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
|
||||
void ModbusNumber::control(float value) {
|
||||
std::vector<uint16_t> data;
|
||||
float write_value = value;
|
||||
// Is there are lambda configured?
|
||||
if (this->write_transform_func_.has_value()) {
|
||||
// data is passed by reference
|
||||
@@ -35,28 +36,32 @@ void ModbusNumber::control(float value) {
|
||||
auto val = (*this->write_transform_func_)(this, value, data);
|
||||
if (val.has_value()) {
|
||||
ESP_LOGV(TAG, "Value overwritten by lambda");
|
||||
value = val.value();
|
||||
write_value = val.value();
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Communication handled by lambda - exiting control");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
value = multiply_by_ * value;
|
||||
write_value = multiply_by_ * write_value;
|
||||
}
|
||||
|
||||
// lambda didn't set payload
|
||||
if (data.empty()) {
|
||||
data = float_to_payload(value, this->sensor_value_type);
|
||||
data = float_to_payload(write_value, this->sensor_value_type);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
|
||||
this->get_name().c_str(), this->start_address, this->register_count, value, value);
|
||||
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
|
||||
|
||||
// Create and send the write command
|
||||
auto write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
|
||||
this->register_count, data);
|
||||
|
||||
ModbusCommandItem write_cmd;
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]);
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
|
||||
this->register_count, data);
|
||||
}
|
||||
// publish new value
|
||||
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
|
||||
const std::vector<uint8_t> &data) {
|
||||
|
||||
@@ -35,6 +35,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
|
||||
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
@@ -42,6 +43,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
|
||||
optional<write_transform_func_t> write_transform_func_;
|
||||
ModbusController *parent_;
|
||||
float multiply_by_{1.0};
|
||||
bool use_write_multiple_{false};
|
||||
};
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -18,6 +18,7 @@ from .. import (
|
||||
|
||||
from ..const import (
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
@@ -36,6 +37,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(): cv.declare_id(ModbusOutput),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_,
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
validate_modbus_register,
|
||||
@@ -54,6 +56,7 @@ async def to_code(config):
|
||||
await output.register_output(var, config)
|
||||
cg.add(var.set_write_multiply(config[CONF_MULTIPLY]))
|
||||
parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||
cg.add(var.set_parent(parent))
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
|
||||
@@ -40,8 +40,14 @@ void ModbusOutput::write_state(float value) {
|
||||
this->start_address, this->register_count, value, original_value);
|
||||
|
||||
// Create and send the write command
|
||||
auto write_cmd =
|
||||
ModbusCommandItem::create_write_multiple_command(parent_, this->start_address, this->register_count, data);
|
||||
// Create and send the write command
|
||||
ModbusCommandItem write_cmd;
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]);
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
|
||||
this->register_count, data);
|
||||
}
|
||||
parent_->queue_command(write_cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor
|
||||
|
||||
using write_transform_func_t = std::function<optional<float>(ModbusOutput *, float, std::vector<uint16_t> &)>;
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
|
||||
protected:
|
||||
void write_state(float value) override;
|
||||
@@ -40,6 +41,7 @@ class ModbusOutput : public output::FloatOutput, public Component, public Sensor
|
||||
|
||||
ModbusController *parent_;
|
||||
float multiply_by_{1.0};
|
||||
bool use_write_multiple_;
|
||||
};
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -18,10 +18,10 @@ from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
|
||||
CONF_USE_WRITE_MULTIPLE = "use_write_multiple"
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
CODEOWNERS = ["@martgras"]
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ void ModbusSwitch::write_state(bool state) {
|
||||
}
|
||||
}
|
||||
if (!data.empty()) {
|
||||
ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str());
|
||||
ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str());
|
||||
cmd = ModbusCommandItem::create_custom_command(
|
||||
this->parent_, data,
|
||||
[this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||
|
||||
@@ -17,7 +17,7 @@ MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent
|
||||
|
||||
void MQTTButtonComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||
if (payload == "press") {
|
||||
if (payload == "PRESS") {
|
||||
this->button_->press();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
@@ -31,6 +31,7 @@ void MQTTButtonComponent::dump_config() {
|
||||
}
|
||||
|
||||
void MQTTButtonComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
config.state_topic = false;
|
||||
if (!this->button_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->button_->get_device_class();
|
||||
}
|
||||
|
||||
18
esphome/components/oralb_ble/__init__.py
Normal file
18
esphome/components/oralb_ble/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ['esp32_ble_tracker']
|
||||
|
||||
oralb_ble_ns = cg.esphome_ns.namespace('oralb_ble')
|
||||
OralbListener = oralb_ble_ns.class_('OralbListener', esp32_ble_tracker.ESPBTDeviceListener)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema({
|
||||
cv.GenerateID(): cv.declare_id(OralbListener),
|
||||
}).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
48
esphome/components/oralb_ble/oralb_ble.cpp
Normal file
48
esphome/components/oralb_ble/oralb_ble.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "oralb_ble.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace oralb_ble {
|
||||
|
||||
static const char *const TAG = "oralb_ble";
|
||||
|
||||
bool parse_oralb_data_byte(const esp32_ble_tracker::adv_data_t &adv_data, OralbParseResult &result) {
|
||||
result.state = adv_data[3];
|
||||
return true;
|
||||
}
|
||||
optional<OralbParseResult> parse_oralb(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
bool success = false;
|
||||
OralbParseResult result{};
|
||||
for (auto &it : device.get_manufacturer_datas()) {
|
||||
bool is_oralb = it.uuid.contains(0xDC, 0x00);
|
||||
if (!is_oralb)
|
||||
continue;
|
||||
|
||||
if (parse_oralb_data_byte(it.data, result))
|
||||
success = true;
|
||||
}
|
||||
if (!success)
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
bool OralbListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
auto res = parse_oralb(device);
|
||||
if (!res.has_value())
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "Got OralB (%s):", device.address_str().c_str());
|
||||
|
||||
if (res->state.has_value()) {
|
||||
ESP_LOGD(TAG, " State: %d", *res->state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace oralb_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
27
esphome/components/oralb_ble/oralb_ble.h
Normal file
27
esphome/components/oralb_ble/oralb_ble.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace oralb_ble {
|
||||
|
||||
struct OralbParseResult {
|
||||
optional<uint8_t> state;
|
||||
};
|
||||
|
||||
bool parse_oralb_data_byte(uint8_t data_type, const uint8_t *data, uint8_t data_length, OralbParseResult &result);
|
||||
|
||||
optional<OralbParseResult> parse_oralb(const esp32_ble_tracker::ESPBTDevice &device);
|
||||
|
||||
class OralbListener : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
};
|
||||
|
||||
} // namespace oralb_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
0
esphome/components/oralb_brush/__init__.py
Normal file
0
esphome/components/oralb_brush/__init__.py
Normal file
33
esphome/components/oralb_brush/oralb_brush.cpp
Normal file
33
esphome/components/oralb_brush/oralb_brush.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "oralb_brush.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace oralb_brush {
|
||||
|
||||
static const char *const TAG = "oralb_brush";
|
||||
|
||||
void OralbBrush::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "OralbBrush");
|
||||
LOG_SENSOR(" ", "State", this->state_);
|
||||
}
|
||||
|
||||
bool OralbBrush::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_)
|
||||
return false;
|
||||
|
||||
auto res = oralb_ble::parse_oralb(device);
|
||||
if (!res.has_value())
|
||||
return false;
|
||||
|
||||
if (res->state.has_value() && this->state_ != nullptr)
|
||||
this->state_->publish_state(*res->state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace oralb_brush
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
31
esphome/components/oralb_brush/oralb_brush.h
Normal file
31
esphome/components/oralb_brush/oralb_brush.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/oralb_ble/oralb_ble.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace oralb_brush {
|
||||
|
||||
class OralbBrush : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_state(sensor::Sensor *state) { state_ = state; }
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
sensor::Sensor *state_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace oralb_brush
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
36
esphome/components/oralb_brush/sensor.py
Normal file
36
esphome/components/oralb_brush/sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_STATE
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
AUTO_LOAD = ["oralb_ble"]
|
||||
|
||||
oralb_brush_ns = cg.esphome_ns.namespace("oralb_brush")
|
||||
OralbBrush = oralb_brush_ns.class_(
|
||||
"OralbBrush", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OralbBrush),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_STATE): sensor.sensor_schema(accuracy_decimals=0),
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
|
||||
if CONF_STATE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_STATE])
|
||||
cg.add(var.set_state(sens))
|
||||
@@ -277,6 +277,7 @@ void OTAComponent::handle_() {
|
||||
ssize_t read = this->client_->read(buf, requested);
|
||||
if (read == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
App.feed_wdt();
|
||||
delay(1);
|
||||
continue;
|
||||
}
|
||||
@@ -305,8 +306,9 @@ void OTAComponent::handle_() {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
// slow down OTA update to avoid getting killed by task watchdog (task_wdt)
|
||||
delay(10);
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +372,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) {
|
||||
ssize_t read = this->client_->read(buf + at, len - at);
|
||||
if (read == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
App.feed_wdt();
|
||||
delay(1);
|
||||
continue;
|
||||
}
|
||||
@@ -381,6 +384,7 @@ bool OTAComponent::readall_(uint8_t *buf, size_t len) {
|
||||
} else {
|
||||
at += read;
|
||||
}
|
||||
App.feed_wdt();
|
||||
delay(1);
|
||||
}
|
||||
|
||||
@@ -399,6 +403,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
||||
ssize_t written = this->client_->write(buf + at, len - at);
|
||||
if (written == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
App.feed_wdt();
|
||||
delay(1);
|
||||
continue;
|
||||
}
|
||||
@@ -407,6 +412,7 @@ bool OTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
||||
} else {
|
||||
at += written;
|
||||
}
|
||||
App.feed_wdt();
|
||||
delay(1);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -26,7 +26,7 @@ bool PN532Spi::write_data(const std::vector<uint8_t> &data) {
|
||||
delay(2);
|
||||
// First byte, communication mode: Write data
|
||||
this->write_byte(0x01);
|
||||
ESP_LOGV(TAG, "Writing data: %s", hexencode(data).c_str());
|
||||
ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str());
|
||||
this->write_array(data.data(), data.size());
|
||||
this->disable();
|
||||
|
||||
@@ -65,7 +65,7 @@ bool PN532Spi::read_data(std::vector<uint8_t> &data, uint8_t len) {
|
||||
this->read_array(data.data(), len);
|
||||
this->disable();
|
||||
data.insert(data.begin(), 0x01);
|
||||
ESP_LOGV(TAG, "Read data: %s", hexencode(data).c_str());
|
||||
ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
|
||||
std::vector<uint8_t> header(7);
|
||||
this->read_array(header.data(), 7);
|
||||
|
||||
ESP_LOGV(TAG, "Header data: %s", hexencode(header).c_str());
|
||||
ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str());
|
||||
|
||||
if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) {
|
||||
// invalid packet
|
||||
@@ -127,7 +127,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector<uint8_t> &data) {
|
||||
this->read_array(data.data(), len + 1);
|
||||
this->disable();
|
||||
|
||||
ESP_LOGV(TAG, "Response data: %s", hexencode(data).c_str());
|
||||
ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str());
|
||||
|
||||
uint8_t checksum = header[5] + header[6]; // TFI + Command response code
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class MideaData {
|
||||
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
|
||||
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
|
||||
bool check_compliment(const MideaData &rhs) const;
|
||||
std::string to_string() const { return hexencode(*this); }
|
||||
std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); }
|
||||
// compare only 40-bits
|
||||
bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); }
|
||||
enum MideaDataType : uint8_t {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@paulmonigatti"]
|
||||
CODEOWNERS = ["@paulmonigatti", "@jsuanet"]
|
||||
|
||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||
|
||||
36
esphome/components/safe_mode/button/__init__.py
Normal file
36
esphome/components/safe_mode/button/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import button
|
||||
from esphome.components.ota import OTAComponent
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_OTA,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ota"]
|
||||
|
||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||
SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
button.button_schema(
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
)
|
||||
.extend({cv.GenerateID(): cv.declare_id(SafeModeButton)})
|
||||
.extend({cv.GenerateID(CONF_OTA): cv.use_id(OTAComponent)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await button.register_button(var, config)
|
||||
|
||||
ota = await cg.get_variable(config[CONF_OTA])
|
||||
cg.add(var.set_ota(ota))
|
||||
25
esphome/components/safe_mode/button/safe_mode_button.cpp
Normal file
25
esphome/components/safe_mode/button/safe_mode_button.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "safe_mode_button.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
static const char *const TAG = "safe_mode.button";
|
||||
|
||||
void SafeModeButton::set_ota(ota::OTAComponent *ota) { this->ota_ = ota; }
|
||||
|
||||
void SafeModeButton::press_action() {
|
||||
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
||||
this->ota_->set_safe_mode_pending(true);
|
||||
|
||||
// Let MQTT settle a bit
|
||||
delay(100); // NOLINT
|
||||
App.safe_reboot();
|
||||
}
|
||||
|
||||
void SafeModeButton::dump_config() { LOG_BUTTON("", "Safe Mode Button", this); }
|
||||
|
||||
} // namespace safe_mode
|
||||
} // namespace esphome
|
||||
21
esphome/components/safe_mode/button/safe_mode_button.h
Normal file
21
esphome/components/safe_mode/button/safe_mode_button.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ota/ota_component.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace safe_mode {
|
||||
|
||||
class SafeModeButton : public button::Button, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_ota(ota::OTAComponent *ota);
|
||||
|
||||
protected:
|
||||
ota::OTAComponent *ota_;
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace safe_mode
|
||||
} // namespace esphome
|
||||
@@ -19,6 +19,7 @@ from esphome.const import (
|
||||
CONF_ON_RAW_VALUE,
|
||||
CONF_ON_VALUE,
|
||||
CONF_ON_VALUE_RANGE,
|
||||
CONF_QUANTILE,
|
||||
CONF_SEND_EVERY,
|
||||
CONF_SEND_FIRST_AT,
|
||||
CONF_STATE_CLASS,
|
||||
@@ -151,6 +152,7 @@ SensorPublishAction = sensor_ns.class_("SensorPublishAction", automation.Action)
|
||||
|
||||
# Filters
|
||||
Filter = sensor_ns.class_("Filter")
|
||||
QuantileFilter = sensor_ns.class_("QuantileFilter", Filter)
|
||||
MedianFilter = sensor_ns.class_("MedianFilter", Filter)
|
||||
MinFilter = sensor_ns.class_("MinFilter", Filter)
|
||||
MaxFilter = sensor_ns.class_("MaxFilter", Filter)
|
||||
@@ -285,6 +287,30 @@ async def filter_out_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(filter_id, config)
|
||||
|
||||
|
||||
QUANTILE_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_WINDOW_SIZE, default=5): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_SEND_EVERY, default=5): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int,
|
||||
cv.Optional(CONF_QUANTILE, default=0.9): cv.zero_to_one_float,
|
||||
}
|
||||
),
|
||||
validate_send_first_at,
|
||||
)
|
||||
|
||||
|
||||
@FILTER_REGISTRY.register("quantile", QuantileFilter, QUANTILE_SCHEMA)
|
||||
async def quantile_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(
|
||||
filter_id,
|
||||
config[CONF_WINDOW_SIZE],
|
||||
config[CONF_SEND_EVERY],
|
||||
config[CONF_SEND_FIRST_AT],
|
||||
config[CONF_QUANTILE],
|
||||
)
|
||||
|
||||
|
||||
MEDIAN_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "filter.h"
|
||||
#include "sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "sensor.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace sensor {
|
||||
@@ -66,6 +67,41 @@ optional<float> MedianFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// QuantileFilter
|
||||
QuantileFilter::QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile)
|
||||
: send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size), quantile_(quantile) {}
|
||||
void QuantileFilter::set_send_every(size_t send_every) { this->send_every_ = send_every; }
|
||||
void QuantileFilter::set_window_size(size_t window_size) { this->window_size_ = window_size; }
|
||||
void QuantileFilter::set_quantile(float quantile) { this->quantile_ = quantile; }
|
||||
optional<float> QuantileFilter::new_value(float value) {
|
||||
if (!std::isnan(value)) {
|
||||
while (this->queue_.size() >= this->window_size_) {
|
||||
this->queue_.pop_front();
|
||||
}
|
||||
this->queue_.push_back(value);
|
||||
ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f), quantile:%f", this, value, this->quantile_);
|
||||
}
|
||||
|
||||
if (++this->send_at_ >= this->send_every_) {
|
||||
this->send_at_ = 0;
|
||||
|
||||
float result = 0.0f;
|
||||
if (!this->queue_.empty()) {
|
||||
std::deque<float> quantile_queue = this->queue_;
|
||||
sort(quantile_queue.begin(), quantile_queue.end());
|
||||
|
||||
size_t queue_size = quantile_queue.size();
|
||||
size_t position = ceilf(queue_size * this->quantile_) - 1;
|
||||
ESP_LOGVV(TAG, "QuantileFilter(%p)::position: %d/%d", this, position, queue_size);
|
||||
result = quantile_queue[position];
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "QuantileFilter(%p)::new_value(%f) SENDING", this, result);
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// MinFilter
|
||||
MinFilter::MinFilter(size_t window_size, size_t send_every, size_t send_first_at)
|
||||
: send_every_(send_every), send_at_(send_every - send_first_at), window_size_(window_size) {}
|
||||
|
||||
@@ -42,6 +42,37 @@ class Filter {
|
||||
Sensor *parent_{nullptr};
|
||||
};
|
||||
|
||||
/** Simple quantile filter.
|
||||
*
|
||||
* Takes the quantile of the last <send_every> values and pushes it out every <send_every>.
|
||||
*/
|
||||
class QuantileFilter : public Filter {
|
||||
public:
|
||||
/** Construct a QuantileFilter.
|
||||
*
|
||||
* @param window_size The number of values that should be used in quantile calculation.
|
||||
* @param send_every After how many sensor values should a new one be pushed out.
|
||||
* @param send_first_at After how many values to forward the very first value. Defaults to the first value
|
||||
* on startup being published on the first *raw* value, so with no filter applied. Must be less than or equal to
|
||||
* send_every.
|
||||
* @param quantile float 0..1 to pick the requested quantile. Defaults to 0.9.
|
||||
*/
|
||||
explicit QuantileFilter(size_t window_size, size_t send_every, size_t send_first_at, float quantile);
|
||||
|
||||
optional<float> new_value(float value) override;
|
||||
|
||||
void set_send_every(size_t send_every);
|
||||
void set_window_size(size_t window_size);
|
||||
void set_quantile(float quantile);
|
||||
|
||||
protected:
|
||||
std::deque<float> queue_;
|
||||
size_t send_every_;
|
||||
size_t send_at_;
|
||||
size_t window_size_;
|
||||
float quantile_;
|
||||
};
|
||||
|
||||
/** Simple median filter.
|
||||
*
|
||||
* Takes the median of the last <send_every> values and pushes it out every <send_every>.
|
||||
|
||||
@@ -1 +1 @@
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
CODEOWNERS = ["@esphome/core", "@jsuanet"]
|
||||
|
||||
23
esphome/components/shutdown/button/__init__.py
Normal file
23
esphome/components/shutdown/button/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import button
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_POWER,
|
||||
)
|
||||
|
||||
shutdown_ns = cg.esphome_ns.namespace("shutdown")
|
||||
ShutdownButton = shutdown_ns.class_("ShutdownButton", button.Button, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
button.button_schema(entity_category=ENTITY_CATEGORY_CONFIG, icon=ICON_POWER)
|
||||
.extend({cv.GenerateID(): cv.declare_id(ShutdownButton)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await button.register_button(var, config)
|
||||
33
esphome/components/shutdown/button/shutdown_button.cpp
Normal file
33
esphome/components/shutdown/button/shutdown_button.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "shutdown_button.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace shutdown {
|
||||
|
||||
static const char *const TAG = "shutdown.button";
|
||||
|
||||
void ShutdownButton::dump_config() { LOG_BUTTON("", "Shutdown Button", this); }
|
||||
void ShutdownButton::press_action() {
|
||||
ESP_LOGI(TAG, "Shutting down...");
|
||||
// Let MQTT settle a bit
|
||||
delay(100); // NOLINT
|
||||
App.run_safe_shutdown_hooks();
|
||||
#ifdef USE_ESP8266
|
||||
ESP.deepSleep(0); // NOLINT(readability-static-accessed-through-instance)
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
esp_deep_sleep_start();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace shutdown
|
||||
} // namespace esphome
|
||||
18
esphome/components/shutdown/button/shutdown_button.h
Normal file
18
esphome/components/shutdown/button/shutdown_button.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace shutdown {
|
||||
|
||||
class ShutdownButton : public button::Button, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace shutdown
|
||||
} // namespace esphome
|
||||
@@ -32,14 +32,11 @@ void SPS30Component::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_firmware_version[4];
|
||||
if (!this->read_data_(raw_firmware_version, 4)) {
|
||||
if (!this->read_data_(&raw_firmware_version_, 1)) {
|
||||
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, " Firmware version v%0d.%02d", (raw_firmware_version[0] >> 8),
|
||||
uint16_t(raw_firmware_version[0] & 0xFF));
|
||||
/// Serial number identification
|
||||
if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) {
|
||||
this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED;
|
||||
@@ -59,6 +56,8 @@ void SPS30Component::setup() {
|
||||
this->serial_number_[i * 2 + 1] = uint16_t(uint16_t(raw_serial_number[i] & 0xFF));
|
||||
}
|
||||
ESP_LOGD(TAG, " Serial Number: '%s'", this->serial_number_);
|
||||
this->status_clear_warning();
|
||||
this->skipped_data_read_cycles_ = 0;
|
||||
this->start_continuous_measurement_();
|
||||
});
|
||||
}
|
||||
@@ -93,10 +92,17 @@ void SPS30Component::dump_config() {
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Serial Number: '%s'", this->serial_number_);
|
||||
LOG_SENSOR(" ", "PM1.0", this->pm_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM4", this->pm_4_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM10", this->pm_10_0_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Firmware version v%0d.%0d", (raw_firmware_version_ >> 8),
|
||||
uint16_t(raw_firmware_version_ & 0xFF));
|
||||
LOG_SENSOR(" ", "PM1.0 Weight Concentration", this->pm_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM2.5 Weight Concentration", this->pm_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM4 Weight Concentration", this->pm_4_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM10 Weight Concentration", this->pm_10_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM1.0 Number Concentration", this->pmc_1_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM2.5 Number Concentration", this->pmc_2_5_sensor_);
|
||||
LOG_SENSOR(" ", "PM4 Number Concentration", this->pmc_4_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM10 Number Concentration", this->pmc_10_0_sensor_);
|
||||
LOG_SENSOR(" ", "PM typical size", this->pm_size_sensor_);
|
||||
}
|
||||
|
||||
void SPS30Component::update() {
|
||||
@@ -123,8 +129,8 @@ void SPS30Component::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_read_status[1];
|
||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
||||
uint16_t raw_read_status;
|
||||
if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) {
|
||||
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
|
||||
this->skipped_data_read_cycles_++;
|
||||
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
|
||||
|
||||
@@ -33,6 +33,7 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
char serial_number_[17] = {0}; /// Terminating NULL character
|
||||
uint16_t raw_firmware_version_;
|
||||
bool start_continuous_measurement_();
|
||||
uint8_t skipped_data_read_cycles_ = 0;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ void TextSensor::add_on_raw_state_callback(std::function<void(std::string)> call
|
||||
std::string TextSensor::get_state() const { return this->state; }
|
||||
std::string TextSensor::get_raw_state() const { return this->raw_state; }
|
||||
void TextSensor::internal_send_state_to_frontend(const std::string &state) {
|
||||
this->state = this->raw_state;
|
||||
this->state = state;
|
||||
this->has_state_ = true;
|
||||
ESP_LOGD(TAG, "'%s': Sending state '%s'", this->name_.c_str(), state.c_str());
|
||||
this->callback_.call(state);
|
||||
|
||||
@@ -431,7 +431,8 @@ async def to_code(config):
|
||||
|
||||
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
|
||||
two_points_available = CONF_HEAT_ACTION in config and (
|
||||
CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config
|
||||
CONF_COOL_ACTION in config
|
||||
or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
|
||||
)
|
||||
|
||||
sens = await cg.get_variable(config[CONF_SENSOR])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "tlc59208f_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tlc59208f {
|
||||
|
||||
@@ -8,6 +8,8 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_INTENSITY,
|
||||
CONF_INVERTED,
|
||||
CONF_LENGTH,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
@@ -22,6 +24,8 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
|
||||
cv.Optional(CONF_INTENSITY, default=7): cv.All(
|
||||
cv.uint8_t, cv.Range(min=0, max=7)
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LENGTH, default=6): cv.All(cv.uint8_t, cv.Range(min=1, max=6)),
|
||||
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_DIO_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
@@ -39,6 +43,8 @@ async def to_code(config):
|
||||
cg.add(var.set_dio_pin(dio))
|
||||
|
||||
cg.add(var.set_intensity(config[CONF_INTENSITY]))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_length(config[CONF_LENGTH]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
|
||||
@@ -130,7 +130,9 @@ void TM1637Display::setup() {
|
||||
}
|
||||
void TM1637Display::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "TM1637:");
|
||||
ESP_LOGCONFIG(TAG, " INTENSITY: %d", this->intensity_);
|
||||
ESP_LOGCONFIG(TAG, " Intensity: %d", this->intensity_);
|
||||
ESP_LOGCONFIG(TAG, " Inverted: %d", this->inverted_);
|
||||
ESP_LOGCONFIG(TAG, " Length: %d", this->length_);
|
||||
LOG_PIN(" CLK Pin: ", this->clk_pin_);
|
||||
LOG_PIN(" DIO Pin: ", this->dio_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
@@ -173,8 +175,14 @@ void TM1637Display::display() {
|
||||
this->send_byte_(TM1637_I2C_COMM2);
|
||||
|
||||
// Write the data bytes
|
||||
for (auto b : this->buffer_) {
|
||||
this->send_byte_(b);
|
||||
if (this->inverted_) {
|
||||
for (int8_t i = this->length_ - 1; i >= 0; i--) {
|
||||
this->send_byte_(this->buffer_[i]);
|
||||
}
|
||||
} else {
|
||||
for (auto b : this->buffer_) {
|
||||
this->send_byte_(b);
|
||||
}
|
||||
}
|
||||
|
||||
this->stop_();
|
||||
@@ -241,14 +249,27 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
|
||||
}
|
||||
// Remap segments, for compatibility with MAX7219 segment definition which is
|
||||
// XABCDEFG, but TM1637 is // XGFEDCBA
|
||||
data = ((data & 0x80) ? 0x80 : 0) | // no move X
|
||||
((data & 0x40) ? 0x1 : 0) | // A
|
||||
((data & 0x20) ? 0x2 : 0) | // B
|
||||
((data & 0x10) ? 0x4 : 0) | // C
|
||||
((data & 0x8) ? 0x8 : 0) | // D
|
||||
((data & 0x4) ? 0x10 : 0) | // E
|
||||
((data & 0x2) ? 0x20 : 0) | // F
|
||||
((data & 0x1) ? 0x40 : 0); // G
|
||||
if (this->inverted_) {
|
||||
// XABCDEFG > XGCBAFED
|
||||
data = ((data & 0x80) ? 0x80 : 0) | // no move X
|
||||
((data & 0x40) ? 0x8 : 0) | // A
|
||||
((data & 0x20) ? 0x10 : 0) | // B
|
||||
((data & 0x10) ? 0x20 : 0) | // C
|
||||
((data & 0x8) ? 0x1 : 0) | // D
|
||||
((data & 0x4) ? 0x2 : 0) | // E
|
||||
((data & 0x2) ? 0x4 : 0) | // F
|
||||
((data & 0x1) ? 0x40 : 0); // G
|
||||
} else {
|
||||
// XABCDEFG > XGFEDCBA
|
||||
data = ((data & 0x80) ? 0x80 : 0) | // no move X
|
||||
((data & 0x40) ? 0x1 : 0) | // A
|
||||
((data & 0x20) ? 0x2 : 0) | // B
|
||||
((data & 0x10) ? 0x4 : 0) | // C
|
||||
((data & 0x8) ? 0x8 : 0) | // D
|
||||
((data & 0x4) ? 0x10 : 0) | // E
|
||||
((data & 0x2) ? 0x20 : 0) | // F
|
||||
((data & 0x1) ? 0x40 : 0); // G
|
||||
}
|
||||
if (*str == '.') {
|
||||
if (pos != start_pos)
|
||||
pos--;
|
||||
|
||||
@@ -41,6 +41,8 @@ class TM1637Display : public PollingComponent {
|
||||
uint8_t print(const char *str);
|
||||
|
||||
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; }
|
||||
void set_inverted(bool inverted) { this->inverted_ = inverted; }
|
||||
void set_length(uint8_t length) { this->length_ = length; }
|
||||
|
||||
void display();
|
||||
|
||||
@@ -62,6 +64,8 @@ class TM1637Display : public PollingComponent {
|
||||
GPIOPin *dio_pin_;
|
||||
GPIOPin *clk_pin_;
|
||||
uint8_t intensity_;
|
||||
uint8_t length_;
|
||||
bool inverted_;
|
||||
optional<tm1637_writer_t> writer_{};
|
||||
uint8_t buffer_[6] = {0};
|
||||
};
|
||||
|
||||
@@ -37,9 +37,9 @@ void TuyaLight::setup() {
|
||||
}
|
||||
if (rgb_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
auto red = parse_hex(datapoint.value_string, 0, 2);
|
||||
auto green = parse_hex(datapoint.value_string, 2, 2);
|
||||
auto blue = parse_hex(datapoint.value_string, 4, 2);
|
||||
auto red = parse_hex<uint8_t>(datapoint.value_string.substr(0, 2));
|
||||
auto green = parse_hex<uint8_t>(datapoint.value_string.substr(2, 2));
|
||||
auto blue = parse_hex<uint8_t>(datapoint.value_string.substr(4, 2));
|
||||
if (red.has_value() && green.has_value() && blue.has_value()) {
|
||||
auto call = this->state_->make_call();
|
||||
call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255);
|
||||
@@ -48,9 +48,9 @@ void TuyaLight::setup() {
|
||||
});
|
||||
} else if (hsv_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
auto hue = parse_hex(datapoint.value_string, 0, 4);
|
||||
auto saturation = parse_hex(datapoint.value_string, 4, 4);
|
||||
auto value = parse_hex(datapoint.value_string, 8, 4);
|
||||
auto hue = parse_hex<uint16_t>(datapoint.value_string.substr(0, 4));
|
||||
auto saturation = parse_hex<uint16_t>(datapoint.value_string.substr(4, 4));
|
||||
auto value = parse_hex<uint16_t>(datapoint.value_string.substr(8, 4));
|
||||
if (hue.has_value() && saturation.has_value() && value.has_value()) {
|
||||
float red, green, blue;
|
||||
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
|
||||
|
||||
54
esphome/components/tuya/number/__init__.py
Normal file
54
esphome/components/tuya/number/__init__.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NUMBER_DATAPOINT,
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_STEP,
|
||||
)
|
||||
from .. import tuya_ns, CONF_TUYA_ID, Tuya
|
||||
|
||||
DEPENDENCIES = ["tuya"]
|
||||
CODEOWNERS = ["@frankiboy1"]
|
||||
|
||||
TuyaNumber = tuya_ns.class_("TuyaNumber", number.Number, cg.Component)
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
raise cv.Invalid("max_value must be greater than min_value")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
number.NUMBER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TuyaNumber),
|
||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||
cv.Required(CONF_NUMBER_DATAPOINT): cv.uint8_t,
|
||||
cv.Required(CONF_MAX_VALUE): cv.float_,
|
||||
cv.Required(CONF_MIN_VALUE): cv.float_,
|
||||
cv.Required(CONF_STEP): cv.positive_float,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await number.register_number(
|
||||
var,
|
||||
config,
|
||||
min_value=config[CONF_MIN_VALUE],
|
||||
max_value=config[CONF_MAX_VALUE],
|
||||
step=config[CONF_STEP],
|
||||
)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(paren))
|
||||
|
||||
cg.add(var.set_number_id(config[CONF_NUMBER_DATAPOINT]))
|
||||
38
esphome/components/tuya/number/tuya_number.cpp
Normal file
38
esphome/components/tuya/number/tuya_number.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "tuya_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
static const char *const TAG = "tuya.number";
|
||||
|
||||
void TuyaNumber::setup() {
|
||||
this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) {
|
||||
if (datapoint.type == TuyaDatapointType::INTEGER) {
|
||||
ESP_LOGV(TAG, "MCU reported number %u is: %d", datapoint.id, datapoint.value_int);
|
||||
this->publish_state(datapoint.value_int);
|
||||
} else if (datapoint.type == TuyaDatapointType::ENUM) {
|
||||
ESP_LOGV(TAG, "MCU reported number %u is: %u", datapoint.id, datapoint.value_enum);
|
||||
this->publish_state(datapoint.value_enum);
|
||||
}
|
||||
this->type_ = datapoint.type;
|
||||
});
|
||||
}
|
||||
|
||||
void TuyaNumber::control(float value) {
|
||||
ESP_LOGV(TAG, "Setting number %u: %f", this->number_id_, value);
|
||||
if (this->type_ == TuyaDatapointType::INTEGER) {
|
||||
this->parent_->set_integer_datapoint_value(this->number_id_, value);
|
||||
} else if (this->type_ == TuyaDatapointType::ENUM) {
|
||||
this->parent_->set_enum_datapoint_value(this->number_id_, value);
|
||||
}
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void TuyaNumber::dump_config() {
|
||||
LOG_NUMBER("", "Tuya Number", this);
|
||||
ESP_LOGCONFIG(TAG, " Number has datapoint ID %u", this->number_id_);
|
||||
}
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
27
esphome/components/tuya/number/tuya_number.h
Normal file
27
esphome/components/tuya/number/tuya_number.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/tuya/tuya.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace tuya {
|
||||
|
||||
class TuyaNumber : public number::Number, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_number_id(uint8_t number_id) { this->number_id_ = number_id; }
|
||||
|
||||
void set_tuya_parent(Tuya *parent) { this->parent_ = parent; }
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
|
||||
Tuya *parent_;
|
||||
uint8_t number_id_{0};
|
||||
TuyaDatapointType type_{};
|
||||
};
|
||||
|
||||
} // namespace tuya
|
||||
} // namespace esphome
|
||||
@@ -14,7 +14,7 @@ void TuyaTextSensor::setup() {
|
||||
this->publish_state(datapoint.value_string);
|
||||
break;
|
||||
case TuyaDatapointType::RAW: {
|
||||
std::string data = hexencode(datapoint.value_raw);
|
||||
std::string data = format_hex_pretty(datapoint.value_raw);
|
||||
ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str());
|
||||
this->publish_state(data);
|
||||
break;
|
||||
|
||||
@@ -34,7 +34,7 @@ void Tuya::dump_config() {
|
||||
}
|
||||
for (auto &info : this->datapoints_) {
|
||||
if (info.type == TuyaDatapointType::RAW)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str());
|
||||
else if (info.type == TuyaDatapointType::BOOLEAN)
|
||||
ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
|
||||
else if (info.type == TuyaDatapointType::INTEGER)
|
||||
@@ -104,7 +104,7 @@ bool Tuya::validate_message_() {
|
||||
// valid message
|
||||
const uint8_t *message_data = data + 6;
|
||||
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
|
||||
hexencode(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
this->handle_command_(command, version, message_data, length);
|
||||
|
||||
// return false to reset rx buffer
|
||||
@@ -253,7 +253,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::RAW:
|
||||
datapoint.value_raw = std::vector<uint8_t>(data, data + data_len);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, hexencode(datapoint.value_raw).c_str());
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
|
||||
break;
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
if (data_len != 1) {
|
||||
@@ -348,7 +348,7 @@ void Tuya::send_raw_command_(TuyaCommand command) {
|
||||
}
|
||||
|
||||
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, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
|
||||
|
||||
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
|
||||
if (!command.payload.empty())
|
||||
@@ -526,7 +526,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
|
||||
}
|
||||
|
||||
void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
|
||||
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str());
|
||||
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
|
||||
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
|
||||
if (!datapoint.has_value()) {
|
||||
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome import core, pins
|
||||
from esphome.components import display, spi
|
||||
from esphome.const import (
|
||||
CONF_BUSY_PIN,
|
||||
@@ -10,6 +10,7 @@ from esphome.const import (
|
||||
CONF_LAMBDA,
|
||||
CONF_MODEL,
|
||||
CONF_PAGES,
|
||||
CONF_RESET_DURATION,
|
||||
CONF_RESET_PIN,
|
||||
)
|
||||
|
||||
@@ -95,6 +96,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_FULL_UPDATE_EVERY): cv.uint32_t,
|
||||
cv.Optional(CONF_RESET_DURATION): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=core.TimePeriod(milliseconds=500)),
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
@@ -135,3 +140,5 @@ async def to_code(config):
|
||||
cg.add(var.set_busy_pin(reset))
|
||||
if CONF_FULL_UPDATE_EVERY in config:
|
||||
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||
if CONF_RESET_DURATION in config:
|
||||
cg.add(var.set_reset_duration(config[CONF_RESET_DURATION]))
|
||||
|
||||
@@ -16,6 +16,7 @@ class WaveshareEPaper : public PollingComponent,
|
||||
float get_setup_priority() const override;
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; }
|
||||
void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; }
|
||||
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
@@ -45,13 +46,14 @@ class WaveshareEPaper : public PollingComponent,
|
||||
void reset_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(200); // NOLINT
|
||||
delay(reset_duration_); // NOLINT
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(200); // NOLINT
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t get_buffer_length_();
|
||||
uint32_t reset_duration_{200};
|
||||
|
||||
void start_command_();
|
||||
void end_command_();
|
||||
|
||||
@@ -390,8 +390,6 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
#ifdef USE_BUTTON
|
||||
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (button::Button *obj : App.get_buttons()) {
|
||||
if (obj->is_internal())
|
||||
continue;
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
|
||||
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address) {
|
||||
if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) {
|
||||
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size());
|
||||
ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str());
|
||||
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -274,12 +274,12 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
|
||||
memcpy(mac_address + 4, mac_reverse + 1, 1);
|
||||
memcpy(mac_address + 5, mac_reverse, 1);
|
||||
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
|
||||
ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str());
|
||||
ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str());
|
||||
ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str());
|
||||
ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str());
|
||||
ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str());
|
||||
ESP_LOGVV(TAG, " Tag : %s", hexencode(vector.tag, vector.tagsize).c_str());
|
||||
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str());
|
||||
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
|
||||
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
|
||||
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
|
||||
ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str());
|
||||
ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str());
|
||||
mbedtls_ccm_free(&ctx);
|
||||
return false;
|
||||
}
|
||||
@@ -295,7 +295,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
|
||||
raw[0] &= ~0x08;
|
||||
|
||||
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed.");
|
||||
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(),
|
||||
ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(),
|
||||
static_cast<int>(raw[4]));
|
||||
|
||||
mbedtls_ccm_free(&ctx);
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgd1";
|
||||
|
||||
void XiaomiCGD1::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi CGD1");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgdk2";
|
||||
|
||||
void XiaomiCGDK2::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi CGDK2");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgg1";
|
||||
|
||||
void XiaomiCGG1::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi CGG1");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_lywsd03mmc";
|
||||
|
||||
void XiaomiLYWSD03MMC::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
||||
@@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_mhoc401";
|
||||
|
||||
void XiaomiMHOC401::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi MHOC401");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str());
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2021.12.0-dev"
|
||||
__version__ = "2022.1.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
@@ -402,6 +402,7 @@ CONF_NUM_CHIPS = "num_chips"
|
||||
CONF_NUM_LEDS = "num_leds"
|
||||
CONF_NUM_SCANS = "num_scans"
|
||||
CONF_NUMBER = "number"
|
||||
CONF_NUMBER_DATAPOINT = "number_datapoint"
|
||||
CONF_OFF_MODE = "off_mode"
|
||||
CONF_OFFSET = "offset"
|
||||
CONF_ON = "on"
|
||||
@@ -518,6 +519,7 @@ CONF_PULLDOWN = "pulldown"
|
||||
CONF_PULLUP = "pullup"
|
||||
CONF_PULSE_LENGTH = "pulse_length"
|
||||
CONF_QOS = "qos"
|
||||
CONF_QUANTILE = "quantile"
|
||||
CONF_RADON = "radon"
|
||||
CONF_RADON_LONG_TERM = "radon_long_term"
|
||||
CONF_RANDOM = "random"
|
||||
@@ -539,6 +541,7 @@ CONF_REFERENCE_TEMPERATURE = "reference_temperature"
|
||||
CONF_REFRESH = "refresh"
|
||||
CONF_REPEAT = "repeat"
|
||||
CONF_REPOSITORY = "repository"
|
||||
CONF_RESET_DURATION = "reset_duration"
|
||||
CONF_RESET_PIN = "reset_pin"
|
||||
CONF_RESIZE = "resize"
|
||||
CONF_RESOLUTION = "resolution"
|
||||
@@ -831,6 +834,7 @@ UNIT_MINUTE = "min"
|
||||
UNIT_OHM = "Ω"
|
||||
UNIT_PARTS_PER_BILLION = "ppb"
|
||||
UNIT_PARTS_PER_MILLION = "ppm"
|
||||
UNIT_PASCAL = "Pa"
|
||||
UNIT_PERCENT = "%"
|
||||
UNIT_PULSES = "pulses"
|
||||
UNIT_PULSES_PER_MINUTE = "pulses/min"
|
||||
|
||||
@@ -37,6 +37,7 @@ void Application::setup() {
|
||||
|
||||
component->call();
|
||||
this->scheduler.process_to_add();
|
||||
this->feed_wdt();
|
||||
if (component->can_proceed())
|
||||
continue;
|
||||
|
||||
@@ -46,14 +47,15 @@ void Application::setup() {
|
||||
do {
|
||||
uint32_t new_app_state = STATUS_LED_WARNING;
|
||||
this->scheduler.call();
|
||||
this->feed_wdt();
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
this->components_[j]->call();
|
||||
new_app_state |= this->components_[j]->get_component_state();
|
||||
this->app_state_ |= new_app_state;
|
||||
this->feed_wdt();
|
||||
}
|
||||
this->app_state_ = new_app_state;
|
||||
yield();
|
||||
this->feed_wdt();
|
||||
} while (!component->can_proceed());
|
||||
}
|
||||
|
||||
@@ -65,6 +67,7 @@ void Application::loop() {
|
||||
uint32_t new_app_state = 0;
|
||||
|
||||
this->scheduler.call();
|
||||
this->feed_wdt();
|
||||
for (Component *component : this->looping_components_) {
|
||||
{
|
||||
WarnIfComponentBlockingGuard guard{component};
|
||||
|
||||
@@ -244,38 +244,6 @@ std::string to_string(long double val) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
optional<int> parse_hex(const char chr) {
|
||||
int out = chr;
|
||||
if (out >= '0' && out <= '9')
|
||||
return (out - '0');
|
||||
if (out >= 'A' && out <= 'F')
|
||||
return (10 + (out - 'A'));
|
||||
if (out >= 'a' && out <= 'f')
|
||||
return (10 + (out - 'a'));
|
||||
return {};
|
||||
}
|
||||
|
||||
optional<int> parse_hex(const std::string &str, size_t start, size_t length) {
|
||||
if (str.length() < start) {
|
||||
return {};
|
||||
}
|
||||
size_t end = start + length;
|
||||
if (str.length() < end) {
|
||||
return {};
|
||||
}
|
||||
int out = 0;
|
||||
for (size_t i = start; i < end; i++) {
|
||||
char chr = str[i];
|
||||
auto digit = parse_hex(chr);
|
||||
if (!digit.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr);
|
||||
return {};
|
||||
}
|
||||
out = (out << 4) | *digit;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
uint32_t fnv1_hash(const std::string &str) {
|
||||
uint32_t hash = 2166136261UL;
|
||||
for (char c : str) {
|
||||
@@ -355,22 +323,6 @@ std::string str_sprintf(const char *fmt, ...) {
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string hexencode(const uint8_t *data, uint32_t len) {
|
||||
char buf[20];
|
||||
std::string res;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (i + 1 != len) {
|
||||
sprintf(buf, "%02X.", data[i]);
|
||||
} else {
|
||||
sprintf(buf, "%02X ", data[i]);
|
||||
}
|
||||
res += buf;
|
||||
}
|
||||
sprintf(buf, "(%u)", len);
|
||||
res += buf;
|
||||
return res;
|
||||
}
|
||||
|
||||
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) {
|
||||
float max_color_value = std::max(std::max(red, green), blue);
|
||||
float min_color_value = std::min(std::min(red, green), blue);
|
||||
@@ -445,6 +397,8 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Strings
|
||||
|
||||
std::string str_truncate(const std::string &str, size_t length) {
|
||||
return str.length() > length ? str.substr(0, length) : str;
|
||||
}
|
||||
@@ -468,4 +422,53 @@ std::string str_sanitize(const std::string &str) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Parsing & formatting
|
||||
|
||||
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
|
||||
uint8_t val;
|
||||
size_t chars = std::min(length, 2 * count);
|
||||
for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) {
|
||||
if (*str >= '0' && *str <= '9')
|
||||
val = *str - '0';
|
||||
else if (*str >= 'A' && *str <= 'F')
|
||||
val = 10 + (*str - 'A');
|
||||
else if (*str >= 'a' && *str <= 'f')
|
||||
val = 10 + (*str - 'a');
|
||||
else
|
||||
return 0;
|
||||
data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val;
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
|
||||
std::string format_hex(const uint8_t *data, size_t length) {
|
||||
std::string ret;
|
||||
ret.resize(length * 2);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4);
|
||||
ret[2 * i + 1] = format_hex_char(data[i] & 0x0F);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex(std::vector<uint8_t> data) { return format_hex(data.data(), data.size()); }
|
||||
|
||||
static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; }
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length) {
|
||||
if (length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
ret.resize(3 * length - 1);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4);
|
||||
ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F);
|
||||
if (i != length - 1)
|
||||
ret[3 * i + 2] = '.';
|
||||
}
|
||||
if (length > 4)
|
||||
return ret + " (" + to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex_pretty(std::vector<uint8_t> data) { return format_hex_pretty(data.data(), data.size()); }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
@@ -19,10 +20,6 @@
|
||||
#define ALWAYS_INLINE __attribute__((always_inline))
|
||||
#define PACKED __attribute__((packed))
|
||||
|
||||
#define xSemaphoreWait(semaphore, wait_time) \
|
||||
xSemaphoreTake(semaphore, wait_time); \
|
||||
xSemaphoreGive(semaphore);
|
||||
|
||||
namespace esphome {
|
||||
|
||||
/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
|
||||
@@ -49,8 +46,6 @@ std::string to_string(unsigned long long val); // NOLINT
|
||||
std::string to_string(float val);
|
||||
std::string to_string(double val);
|
||||
std::string to_string(long double val);
|
||||
optional<int> parse_hex(const std::string &str, size_t start, size_t length);
|
||||
optional<int> parse_hex(char chr);
|
||||
|
||||
/// Compare string a to string b (ignoring case) and return whether they are equal.
|
||||
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
|
||||
@@ -190,10 +185,6 @@ enum ParseOnOffState {
|
||||
|
||||
ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr);
|
||||
|
||||
// Encode raw data to a human-readable string (for debugging)
|
||||
std::string hexencode(const uint8_t *data, uint32_t len);
|
||||
template<typename T> std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); }
|
||||
|
||||
// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
|
||||
template<int...> struct seq {}; // NOLINT
|
||||
template<int N, int... S> struct gens : gens<N - 1, N - 1, S...> {}; // NOLINT
|
||||
@@ -370,9 +361,9 @@ std::string str_sanitize(const std::string &str);
|
||||
/// @name Parsing & formatting
|
||||
///@{
|
||||
|
||||
/// Parse an unsigned decimal number (requires null-terminated string).
|
||||
/// Parse an unsigned decimal number from a null-terminated string.
|
||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
|
||||
optional<T> parse_number(const char *str, size_t len) {
|
||||
optional<T> parse_number(const char *str) {
|
||||
char *end = nullptr;
|
||||
unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int)
|
||||
if (end == str || *end != '\0' || value > std::numeric_limits<T>::max())
|
||||
@@ -382,11 +373,11 @@ optional<T> parse_number(const char *str, size_t len) {
|
||||
/// Parse an unsigned decimal number.
|
||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_unsigned<T>::value), int> = 0>
|
||||
optional<T> parse_number(const std::string &str) {
|
||||
return parse_number<T>(str.c_str(), str.length() + 1);
|
||||
return parse_number<T>(str.c_str());
|
||||
}
|
||||
/// Parse a signed decimal number (requires null-terminated string).
|
||||
/// Parse a signed decimal number from a null-terminated string.
|
||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
|
||||
optional<T> parse_number(const char *str, size_t len) {
|
||||
optional<T> parse_number(const char *str) {
|
||||
char *end = nullptr;
|
||||
signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int)
|
||||
if (end == str || *end != '\0' || value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max())
|
||||
@@ -396,11 +387,10 @@ optional<T> parse_number(const char *str, size_t len) {
|
||||
/// Parse a signed decimal number.
|
||||
template<typename T, enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), int> = 0>
|
||||
optional<T> parse_number(const std::string &str) {
|
||||
return parse_number<T>(str.c_str(), str.length() + 1);
|
||||
return parse_number<T>(str.c_str());
|
||||
}
|
||||
/// Parse a decimal floating-point number (requires null-terminated string).
|
||||
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
|
||||
optional<T> parse_number(const char *str, size_t len) {
|
||||
/// Parse a decimal floating-point number from a null-terminated string.
|
||||
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0> optional<T> parse_number(const char *str) {
|
||||
char *end = nullptr;
|
||||
float value = ::strtof(str, &end);
|
||||
if (end == str || *end != '\0' || value == HUGE_VALF)
|
||||
@@ -410,7 +400,102 @@ optional<T> parse_number(const char *str, size_t len) {
|
||||
/// Parse a decimal floating-point number.
|
||||
template<typename T, enable_if_t<(std::is_same<T, float>::value), int> = 0>
|
||||
optional<T> parse_number(const std::string &str) {
|
||||
return parse_number<T>(str.c_str(), str.length() + 1);
|
||||
return parse_number<T>(str.c_str());
|
||||
}
|
||||
|
||||
/** Parse bytes from a hex-encoded string into a byte array.
|
||||
*
|
||||
* When \p len is less than \p 2*count, the result is written to the back of \p data (i.e. this function treats \p str
|
||||
* as if it were padded with zeros at the front).
|
||||
*
|
||||
* @param str String to read from.
|
||||
* @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed.
|
||||
* @param data Byte array to write to.
|
||||
* @param count Length of \p data.
|
||||
* @return The number of characters parsed from \p str.
|
||||
*/
|
||||
size_t parse_hex(const char *str, size_t len, uint8_t *data, size_t count);
|
||||
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data.
|
||||
inline bool parse_hex(const char *str, uint8_t *data, size_t count) {
|
||||
return parse_hex(str, strlen(str), data, count) == 2 * count;
|
||||
}
|
||||
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data.
|
||||
inline bool parse_hex(const std::string &str, uint8_t *data, size_t count) {
|
||||
return parse_hex(str.c_str(), str.length(), data, count) == 2 * count;
|
||||
}
|
||||
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data.
|
||||
inline bool parse_hex(const char *str, std::vector<uint8_t> &data, size_t count) {
|
||||
data.resize(count);
|
||||
return parse_hex(str, strlen(str), data.data(), count) == 2 * count;
|
||||
}
|
||||
/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data.
|
||||
inline bool parse_hex(const std::string &str, std::vector<uint8_t> &data, size_t count) {
|
||||
data.resize(count);
|
||||
return parse_hex(str.c_str(), str.length(), data.data(), count) == 2 * count;
|
||||
}
|
||||
/** Parse a hex-encoded string into an unsigned integer.
|
||||
*
|
||||
* @param str String to read from, starting with the most significant byte.
|
||||
* @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed.
|
||||
*/
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
||||
optional<T> parse_hex(const char *str, size_t len) {
|
||||
T val = 0;
|
||||
if (len > 2 * sizeof(T) || parse_hex(str, len, reinterpret_cast<uint8_t *>(&val), sizeof(T)) == 0)
|
||||
return {};
|
||||
return convert_big_endian(val);
|
||||
}
|
||||
/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<T> parse_hex(const char *str) {
|
||||
return parse_hex<T>(str, strlen(str));
|
||||
}
|
||||
/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<T> parse_hex(const std::string &str) {
|
||||
return parse_hex<T>(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||
std::string format_hex(const uint8_t *data, size_t length);
|
||||
/// Format the vector \p data in lowercased hex.
|
||||
std::string format_hex(std::vector<uint8_t> data);
|
||||
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
|
||||
val = convert_big_endian(val);
|
||||
return format_hex(reinterpret_cast<uint8_t *>(&val), sizeof(T));
|
||||
}
|
||||
|
||||
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length);
|
||||
/// Format the vector \p data in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(std::vector<uint8_t> data);
|
||||
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
|
||||
val = convert_big_endian(val);
|
||||
return format_hex_pretty(reinterpret_cast<uint8_t *>(&val), sizeof(T));
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Number manipulation
|
||||
///@{
|
||||
|
||||
/// Remap a number from one range to another.
|
||||
template<typename T, typename U> T remap(U value, U min, U max, T min_out, T max_out) {
|
||||
return (value - min) * (max_out - min_out) / (max - min) + min_out;
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Deprecated functions
|
||||
///@{
|
||||
|
||||
ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1")
|
||||
inline std::string hexencode(const uint8_t *data, uint32_t len) { return format_hex_pretty(data, len); }
|
||||
|
||||
template<typename T>
|
||||
ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1")
|
||||
std::string hexencode(const T &data) {
|
||||
return hexencode(data.data(), data.size());
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
@@ -27,7 +27,7 @@ import tornado.process
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
|
||||
from esphome import const, platformio_api, util
|
||||
from esphome import const, platformio_api, util, yaml_util
|
||||
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
|
||||
from esphome.storage_json import (
|
||||
EsphomeStorageJSON,
|
||||
@@ -836,6 +836,28 @@ class LogoutHandler(BaseHandler):
|
||||
self.redirect("./login")
|
||||
|
||||
|
||||
class SecretKeysRequestHandler(BaseHandler):
|
||||
@authenticated
|
||||
def get(self):
|
||||
|
||||
filename = None
|
||||
|
||||
for secret_filename in const.SECRETS_FILES:
|
||||
relative_filename = settings.rel_path(secret_filename)
|
||||
if os.path.isfile(relative_filename):
|
||||
filename = relative_filename
|
||||
break
|
||||
|
||||
if filename is None:
|
||||
self.send_error(404)
|
||||
return
|
||||
|
||||
secret_keys = list(yaml_util.load_yaml(filename, clear_secrets=False))
|
||||
|
||||
self.set_header("content-type", "application/json")
|
||||
self.write(json.dumps(secret_keys))
|
||||
|
||||
|
||||
def get_base_frontend_path():
|
||||
if ENV_DEV not in os.environ:
|
||||
import esphome_dashboard
|
||||
@@ -939,6 +961,7 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
(f"{rel}static/(.*)", StaticFileHandler, {"path": get_static_path()}),
|
||||
(f"{rel}devices", ListDevicesHandler),
|
||||
(f"{rel}import", ImportRequestHandler),
|
||||
(f"{rel}secret_keys", SecretKeysRequestHandler),
|
||||
],
|
||||
**app_settings,
|
||||
)
|
||||
|
||||
@@ -45,9 +45,9 @@ OTA_BIG = r""" ____ _______
|
||||
|
||||
BASE_CONFIG = """esphome:
|
||||
name: {name}
|
||||
platform: {platform}
|
||||
board: {board}
|
||||
"""
|
||||
|
||||
LOGGER_API_CONFIG = """
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
@@ -55,6 +55,18 @@ logger:
|
||||
api:
|
||||
"""
|
||||
|
||||
ESP8266_CONFIG = """
|
||||
esp8266:
|
||||
board: {board}
|
||||
"""
|
||||
|
||||
ESP32_CONFIG = """
|
||||
esp32:
|
||||
board: {board}
|
||||
framework:
|
||||
type: arduino
|
||||
"""
|
||||
|
||||
|
||||
def sanitize_double_quotes(value):
|
||||
return value.replace("\\", "\\\\").replace('"', '\\"')
|
||||
@@ -71,6 +83,14 @@ def wizard_file(**kwargs):
|
||||
|
||||
config = BASE_CONFIG.format(**kwargs)
|
||||
|
||||
config += (
|
||||
ESP8266_CONFIG.format(**kwargs)
|
||||
if kwargs["platform"] == "ESP8266"
|
||||
else ESP32_CONFIG.format(**kwargs)
|
||||
)
|
||||
|
||||
config += LOGGER_API_CONFIG
|
||||
|
||||
# Configure API
|
||||
if "password" in kwargs:
|
||||
config += f" password: \"{kwargs['password']}\"\n"
|
||||
@@ -86,12 +106,11 @@ def wizard_file(**kwargs):
|
||||
config += "\n\nwifi:\n"
|
||||
|
||||
if "ssid" in kwargs:
|
||||
# pylint: disable=consider-using-f-string
|
||||
config += """ ssid: "{ssid}"
|
||||
password: "{psk}"
|
||||
""".format(
|
||||
**kwargs
|
||||
)
|
||||
if kwargs["ssid"].startswith("!secret"):
|
||||
template = " ssid: {ssid}\n password: {psk}\n"
|
||||
else:
|
||||
template = """ ssid: "{ssid}"\n password: "{psk}"\n"""
|
||||
config += template.format(**kwargs)
|
||||
else:
|
||||
config += """ # ssid: "My SSID"
|
||||
# password: "mypassword"
|
||||
@@ -141,7 +160,6 @@ if get_bool_env(ENV_QUICKWIZARD):
|
||||
def sleep(time):
|
||||
pass
|
||||
|
||||
|
||||
else:
|
||||
from time import sleep
|
||||
|
||||
|
||||
@@ -329,9 +329,10 @@ ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda)
|
||||
ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force)
|
||||
|
||||
|
||||
def load_yaml(fname):
|
||||
_SECRET_VALUES.clear()
|
||||
_SECRET_CACHE.clear()
|
||||
def load_yaml(fname, clear_secrets=True):
|
||||
if clear_secrets:
|
||||
_SECRET_VALUES.clear()
|
||||
_SECRET_CACHE.clear()
|
||||
return _load_yaml_internal(fname)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user