1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Jesse Hills
a85b7b3f84 Code tidy 2021-12-22 15:58:02 +13:00
Jesse Hills
a207ed08a9 Merge branch 'dev' into pr/ciB89/1424-1 2021-12-22 15:49:07 +13:00
jsuanet
f431c7402f Add shutdown and safe_mode button (#2918)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Jos Suanet <jos@suanet.net>
2021-12-20 22:25:36 +01:00
Oxan van Leeuwen
4907e6f6d7 Fix MQTT button press action (#2917) 2021-12-21 08:19:20 +13:00
Jonas De Kegel
1ccee86705 Fix tm1637 bootloop (#2929) 2021-12-20 18:06:04 +01:00
Jonas De Kegel
542fb2175b Support inverted tm1637 display (#2878)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-20 09:30:35 +01:00
Frank Langtind
6ec9cfb044 Add Tuya Number support (#2765) 2021-12-20 14:35:10 +13:00
Benny de Leeuw
66e0ff8392 Add growatt modbus sensor (#2922)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-20 14:30:23 +13:00
Martin
1fb0a7109d Modbus: use multiply for publishing number (#2916) 2021-12-15 22:38:23 +13:00
sveip
192eb49589 ESP32 CAM add Automatic Exposure Control option (#2892)
Co-authored-by: Peter <psv@tsat.net>
Co-authored-by: Carlos Garcia Saura <CarlosGS@users.noreply.github.com>
2021-12-15 07:46:43 +13:00
Petr Vraník
5d70ff702b quantile filter support (#2900)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: pvranik <petr.vranik@mgm-tp.com>
2021-12-15 07:43:42 +13:00
jddonovan
a7b05db2a1 Adding Pascal unit to constants (#2914) 2021-12-15 07:39:50 +13:00
wilberforce
45e346cf1b Allow button POST on press from web server (#2913) 2021-12-14 15:08:01 +13:00
dependabot[bot]
80e2bfada3 Bump black from 21.11b1 to 21.12b0 (#2879)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-13 19:15:49 +01:00
Martin
16e7bd0388 fix multi-line comment warning/error (#2891) 2021-12-13 19:15:22 +01:00
Oxan van Leeuwen
b3fb35783e Set text sensor state property to filter output (#2893) 2021-12-13 15:21:09 +13:00
myhomeiot
a79c6aa9e0 Added access to ble_scan_result_evt_param as get_scan_result (#2854) 2021-12-13 13:08:18 +13:00
Martin
4bb58b2de9 Add gpio 12 to strapping pin list (#2902) 2021-12-13 11:03:08 +13:00
Jesse Hills
4e10881331 Log the actual value in modbus number (#2901) 2021-12-13 10:28:19 +13:00
Ben Owen
cec4a81e14 Add reset_duration option for waveshare epaper HAT rev 2.1 (#1481)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-13 10:27:11 +13:00
Carlos Garcia Saura
da45923d05 Turn verbose a debug statement in bme280 (#2906) 2021-12-13 09:32:37 +13:00
Carlos Garcia Saura
31a61b598b Reduce timing noise in duty_cycle (#2881) 2021-12-13 09:30:47 +13:00
tony
9c0506592b Add light.on_state trigger (#2868) 2021-12-13 09:19:57 +13:00
Oxan van Leeuwen
beeb0c7c5a Introduce hex parsing & formatting helper functions (#2882) 2021-12-13 09:15:23 +13:00
Martin
b2f05faee0 Move i2c scan to setup (#2869)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
2021-12-13 09:12:50 +13:00
Jesse Hills
8375e1d64d Bump esphome-dashboard to 20211211.0 (#2904) 2021-12-11 21:03:41 +13:00
Keith Burzinski
cf5193d3e5 Fix for two points setting when fan_only_cooling is disabled (#2903)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Keith Burzinski <kburzinski@kbx-mbp2021.ad.kbx81.net>
2021-12-11 20:03:22 +13:00
Guillermo Ruffino
c490388e80 Modbus number/output use write single (#2896)
Co-authored-by: Martin <25747549+martgras@users.noreply.github.com>
2021-12-10 09:44:43 +13:00
Jesse Hills
24ec5a6e9d Fix published state for modbus number (#2894) 2021-12-10 09:32:34 +13:00
Oxan van Leeuwen
6df1d5222d Drop unused xSemaphoreWait define (#2888) 2021-12-08 12:46:36 +13:00
Jesse Hills
58fb7a02f6 Bump esphome-dashboard to 20211208.0 (#2887) 2021-12-08 12:42:50 +13:00
Jesse Hills
3d51ac8df0 Use new platform component config blocks for wizard (#2885) 2021-12-08 09:22:03 +13:00
Oxan van Leeuwen
6fe4ff7f85 Drop len parameter from parse_number() (#2883) 2021-12-08 08:46:25 +13:00
Yuval Brik
2253d4bc16 Support different run duration for non-timer wakeup (#2861) 2021-12-06 23:30:27 +01:00
Carlos Garcia Saura
e5cc19de43 Feed watchdog while setting up OTA (#2876) 2021-12-06 23:26:06 +01:00
Jesse Hills
5404617d43 Bump esphome-dashboard to 20211207.0 (#2877) 2021-12-07 07:41:40 +13:00
Oxan van Leeuwen
12467a18e6 Feed watchdog when no component loops (#2857) 2021-12-07 07:24:20 +13:00
Jesse Hills
1db7043a4d Allow wizard to specify secrets (#2875) 2021-12-06 20:58:51 +13:00
Jesse Hills
49932747b3 Adopt using wifi secrets that should exist at this point (#2874) 2021-12-06 20:57:56 +13:00
Jesse Hills
55db190875 Add endpoint to fetch secrets keys (#2873) 2021-12-06 20:15:34 +13:00
Massimiliano Ravelli
71fe2f7ed3 Ignore already stopped dhcp for ethernet (#2862)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2021-12-06 20:01:50 +13:00
Oxan van Leeuwen
ffc112c9d0 Don't disable idle task WDT when it's not enabled (#2856) 2021-12-06 20:01:14 +13:00
Oxan van Leeuwen
d3e48e296f Fix MCP23x17 not disabling pullup after config change (#2855) 2021-12-06 19:59:50 +13:00
Martin
14f6ae75ea SPS30 : fix i2c read size (#2866) 2021-12-06 19:58:26 +13:00
Carlos Garcia Saura
c84efe64d3 ADC: Turn verbose the debugging "got voltage" (#2863) 2021-12-06 19:56:53 +13:00
Martin
10e89a7dbb tlc59208f : fix compilation error (#2867) 2021-12-06 19:54:46 +13:00
Jesse Hills
ef44acbf10 Bump esphome-dashboard to 20211206.0 (#2870) 2021-12-06 19:42:49 +13:00
dependabot[bot]
06da540ab0 Bump pylint from 2.12.1 to 2.12.2 (#2858)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-12-05 13:29:08 +01:00
Oxan van Leeuwen
40c017fd54 Update ota_component.cpp (#2852) 2021-12-03 07:52:56 +13:00
Jesse Hills
f0bcf81a98 Add a simple helper to remap values (#2850) 2021-12-02 09:23:11 +01:00
Jesse Hills
6a0b343289 Bump version to 2022.1.0-dev 2021-12-02 19:38:49 +13:00
Ian
90c3cb62b3 Add tracker for OralB toothbrushes
The OralB toothbrushes expose some of their information in their bluetooth advertisement data.

This data lets us see the state (idle, running), brush mode (daily clean, tongue, whitening, etc.), pressure and some other bits of data.

This component lets you expose that data with config as follows:

```
esp32_ble_tracker:

sensor:
  - platform: oralb_brush
    mac_address: 00:00:00:00:00:00
    state:
      name: "Toothbrush State"
```

Checkout https://github.com/zewelor/bt-mqtt-gateway/blob/master/workers/toothbrush_homeassistant.py and https://esphome.io/components/esp32_ble_tracker.html for more information.
2020-05-03 15:23:21 +01:00
110 changed files with 1608 additions and 247 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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",
)

View File

@@ -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]

View File

@@ -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) {

View File

@@ -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};

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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__)

View File

@@ -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 &param) {
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 &param) {

View File

@@ -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;

View File

@@ -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",

View File

@@ -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; }

View File

@@ -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};

View File

@@ -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_);

View File

@@ -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");

View File

@@ -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);
}

View 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

View 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

View 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 = ""
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))

View File

@@ -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

View File

@@ -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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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_;

View File

@@ -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]))

View File

@@ -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);

View File

@@ -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")

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -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],

View File

@@ -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) {

View File

@@ -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

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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"]

View File

@@ -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) {

View File

@@ -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();
}

View 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)

View 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

View 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

View 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

View 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

View 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))

View File

@@ -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;

View File

@@ -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++) {

View File

@@ -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 {

View File

@@ -1,5 +1,5 @@
import esphome.codegen as cg
CODEOWNERS = ["@paulmonigatti"]
CODEOWNERS = ["@paulmonigatti", "@jsuanet"]
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")

View 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))

View 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

View 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

View File

@@ -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(
{

View File

@@ -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) {}

View File

@@ -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>.

View File

@@ -1 +1 @@
CODEOWNERS = ["@esphome/core"]
CODEOWNERS = ["@esphome/core", "@jsuanet"]

View 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)

View 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

View 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

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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])

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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--;

View File

@@ -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};
};

View File

@@ -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);

View 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]))

View 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

View 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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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]))

View File

@@ -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_();

View File

@@ -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;

View File

@@ -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);

View File

@@ -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_);

View File

@@ -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_);

View File

@@ -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_);

View File

@@ -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_);

View File

@@ -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_);

View File

@@ -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"

View File

@@ -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};

View File

@@ -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

View File

@@ -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());
}
///@}

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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