Merge branch 'dev' into nrf52_core

This commit is contained in:
tomaszduda23 2024-10-14 20:14:15 +02:00 committed by GitHub
commit 737a5f4bc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
125 changed files with 6405 additions and 5080 deletions

View file

@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache/restore@v4.0.2 uses: actions/cache/restore@v4.1.1
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length

View file

@ -46,7 +46,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.0.2 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
# yamllint disable-line rule:line-length # yamllint disable-line rule:line-length
@ -306,20 +306,22 @@ jobs:
- name: Cache platformio - name: Cache platformio
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.0.2 uses: actions/cache@v4.1.1
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio - name: Cache platformio
if: github.ref != 'refs/heads/dev' if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.0.2 uses: actions/cache/restore@v4.1.1
with: with:
path: ~/.platformio path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }} key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-14 run: |
sudo apt-get update
sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |
@ -401,7 +403,9 @@ jobs:
file: ${{ fromJson(needs.list-components.outputs.components) }} file: ${{ fromJson(needs.list-components.outputs.components) }}
steps: steps:
- name: Install dependencies - name: Install dependencies
run: sudo apt-get install libsdl2-dev run: |
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7
@ -455,7 +459,9 @@ jobs:
run: echo ${{ matrix.components }} run: echo ${{ matrix.components }}
- name: Install dependencies - name: Install dependencies
run: sudo apt-get install libsdl2-dev run: |
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.1.7 uses: actions/checkout@v4.1.7

View file

@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests - name: Upload digests
uses: actions/upload-artifact@v4.4.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: digests-${{ steps.sanitize.outputs.name }} name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests path: /tmp/digests

View file

@ -162,6 +162,7 @@ esphome/components/gps/* @coogle
esphome/components/graph/* @synco esphome/components/graph/* @synco
esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/graphical_display_menu/* @MrMDavidson
esphome/components/gree/* @orestismers esphome/components/gree/* @orestismers
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
esphome/components/grove_tb6612fng/* @max246 esphome/components/grove_tb6612fng/* @max246
esphome/components/growatt_solar/* @leeuwte esphome/components/growatt_solar/* @leeuwte
esphome/components/gt911/* @clydebarrow @jesserockz esphome/components/gt911/* @clydebarrow @jesserockz
@ -282,6 +283,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
esphome/components/mpl3115a2/* @kbickar esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta esphome/components/ms8607/* @e28eta
esphome/components/nau7802/* @cujomalainey
esphome/components/network/* @esphome/core esphome/components/network/* @esphome/core
esphome/components/nextion/* @edwardtfn @senexcrenshaw esphome/components/nextion/* @edwardtfn @senexcrenshaw
esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw
@ -290,6 +292,7 @@ esphome/components/nextion/switch/* @senexcrenshaw
esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81 esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra esphome/components/noblex/* @AGalfra
esphome/components/npi19/* @bakerkj
esphome/components/nrf52/* @tomaszduda23 esphome/components/nrf52/* @tomaszduda23
esphome/components/number/* @esphome/core esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb esphome/components/one_wire/* @ssieb
@ -406,6 +409,7 @@ esphome/components/tca9555/* @mobrembski
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
esphome/components/tee501/* @Stock-M esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/tem3200/* @bakerkj
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat esphome/components/template/event/* @nohat

View file

@ -9,7 +9,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@ -195,9 +195,8 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_READY, []): for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if mqtt_id := config.get(CONF_MQTT_ID): if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View file

@ -25,7 +25,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TIMING, CONF_TIMING,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_BATTERY, DEVICE_CLASS_BATTERY,
DEVICE_CLASS_BATTERY_CHARGING, DEVICE_CLASS_BATTERY_CHARGING,
DEVICE_CLASS_CARBON_MONOXIDE, DEVICE_CLASS_CARBON_MONOXIDE,
@ -543,9 +543,8 @@ async def setup_binary_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_binary_sensor(var, config): async def register_binary_sensor(var, config):

View file

@ -11,7 +11,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_PRESS, CONF_ON_PRESS,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART, DEVICE_CLASS_RESTART,
@ -97,9 +97,8 @@ async def setup_button_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_button(var, config): async def register_button(var, config):

View file

@ -43,7 +43,7 @@ from esphome.const import (
CONF_TEMPERATURE_STEP, CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VISUAL, CONF_VISUAL,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@ -408,9 +408,8 @@ async def setup_climate_core_(var, config):
trigger, [(ClimateCall.operator("ref"), "x")], conf trigger, [(ClimateCall.operator("ref"), "x")], conf
) )
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_climate(var, config): async def register_climate(var, config):

View file

@ -17,7 +17,7 @@ from esphome.const import (
CONF_TILT_COMMAND_TOPIC, CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC, CONF_TILT_STATE_TOPIC,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_AWNING, DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND, DEVICE_CLASS_BLIND,
DEVICE_CLASS_CURTAIN, DEVICE_CLASS_CURTAIN,
@ -137,10 +137,6 @@ async def setup_cover_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
@ -156,6 +152,9 @@ async def setup_cover_core_(var, config):
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None: if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic)) cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_cover(var, config): async def register_cover(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View file

@ -244,7 +244,7 @@ void CSE7766Component::dump_config() {
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_); LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_); LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_); LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
this->check_uart_settings(4800); this->check_uart_settings(4800, 1, uart::UART_CONFIG_PARITY_EVEN);
} }
} // namespace cse7766 } // namespace cse7766

View file

@ -1,11 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen from esphome.components import i2c, touchscreen
from esphome.const import CONF_INTERRUPT_PIN, CONF_ID, CONF_RESET_PIN import esphome.config_validation as cv
from .. import cst816_ns from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import cst816_ns
CST816Touchscreen = cst816_ns.class_( CST816Touchscreen = cst816_ns.class_(
"CST816Touchscreen", "CST816Touchscreen",
@ -14,11 +13,14 @@ CST816Touchscreen = cst816_ns.class_(
) )
CST816ButtonListener = cst816_ns.class_("CST816ButtonListener") CST816ButtonListener = cst816_ns.class_("CST816ButtonListener")
CONF_SKIP_PROBE = "skip_probe"
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(CST816Touchscreen), cv.GenerateID(): cv.declare_id(CST816Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SKIP_PROBE, default=False): cv.boolean,
} }
).extend(i2c.i2c_device_schema(0x15)) ).extend(i2c.i2c_device_schema(0x15))
@ -28,6 +30,7 @@ async def to_code(config):
await touchscreen.register_touchscreen(var, config) await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
cg.add(var.set_skip_probe(config[CONF_SKIP_PROBE]))
if interrupt_pin := config.get(CONF_INTERRUPT_PIN): if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin))) cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN): if reset_pin := config.get(CONF_RESET_PIN):

View file

@ -8,11 +8,7 @@ void CST816Touchscreen::continue_setup_() {
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
} }
if (!this->read_byte(REG_CHIP_ID, &this->chip_id_)) { if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) {
this->mark_failed();
esph_log_e(TAG, "Failed to read chip id");
return;
}
switch (this->chip_id_) { switch (this->chip_id_) {
case CST820_CHIP_ID: case CST820_CHIP_ID:
case CST826_CHIP_ID: case CST826_CHIP_ID:
@ -23,17 +19,22 @@ void CST816Touchscreen::continue_setup_() {
break; break;
default: default:
this->mark_failed(); this->mark_failed();
esph_log_e(TAG, "Unknown chip ID 0x%02X", this->chip_id_); this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
return; return;
} }
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);
} else if (!this->skip_probe_) {
this->status_set_error("Failed to read chip id");
this->mark_failed();
return;
}
if (this->x_raw_max_ == this->x_raw_min_) { if (this->x_raw_max_ == this->x_raw_min_) {
this->x_raw_max_ = this->display_->get_native_width(); this->x_raw_max_ = this->display_->get_native_width();
} }
if (this->y_raw_max_ == this->y_raw_min_) { if (this->y_raw_max_ == this->y_raw_min_) {
this->y_raw_max_ = this->display_->get_native_height(); this->y_raw_max_ = this->display_->get_native_height();
} }
esph_log_config(TAG, "CST816 Touchscreen setup complete"); ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
} }
void CST816Touchscreen::update_button_state_(bool state) { void CST816Touchscreen::update_button_state_(bool state) {
@ -45,7 +46,7 @@ void CST816Touchscreen::update_button_state_(bool state) {
} }
void CST816Touchscreen::setup() { void CST816Touchscreen::setup() {
esph_log_config(TAG, "Setting up CST816 Touchscreen..."); ESP_LOGCONFIG(TAG, "Setting up CST816 Touchscreen...");
if (this->reset_pin_ != nullptr) { if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup(); this->reset_pin_->setup();
this->reset_pin_->digital_write(true); this->reset_pin_->digital_write(true);
@ -73,7 +74,7 @@ void CST816Touchscreen::update_touches() {
uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]); uint16_t x = encode_uint16(data[REG_XPOS_HIGH] & 0xF, data[REG_XPOS_LOW]);
uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]); uint16_t y = encode_uint16(data[REG_YPOS_HIGH] & 0xF, data[REG_YPOS_LOW]);
esph_log_v(TAG, "Read touch %d/%d", x, y); ESP_LOGV(TAG, "Read touch %d/%d", x, y);
if (x >= this->x_raw_max_) { if (x >= this->x_raw_max_) {
this->update_button_state_(true); this->update_button_state_(true);
} else { } else {

View file

@ -45,6 +45,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
void set_skip_probe(bool skip_probe) { this->skip_probe_ = skip_probe; }
protected: protected:
void continue_setup_(); void continue_setup_();
@ -53,6 +54,7 @@ class CST816Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
InternalGPIOPin *interrupt_pin_{}; InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{}; GPIOPin *reset_pin_{};
uint8_t chip_id_{}; uint8_t chip_id_{};
bool skip_probe_{}; // if set, do not expect to be able to probe the controller on the i2c bus.
std::vector<CST816ButtonListener *> button_listeners_; std::vector<CST816ButtonListener *> button_listeners_;
bool button_touched_{}; bool button_touched_{};
}; };

View file

@ -18,7 +18,7 @@ from esphome.const import (
CONF_TIME_ID, CONF_TIME_ID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
CONF_YEAR, CONF_YEAR,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -138,9 +138,8 @@ async def setup_datetime_core_(var, config):
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_VALUE, []): for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)

View file

@ -36,7 +36,8 @@ std::string DebugComponent::get_reset_reason_() {
break; break;
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case SW_RESET: case SW_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_SYS_RESET: case RTC_SW_SYS_RESET:
#endif #endif
reset_reason = "Software Reset Digital Core"; reset_reason = "Software Reset Digital Core";
@ -72,14 +73,16 @@ std::string DebugComponent::get_reset_reason_() {
case TGWDT_CPU_RESET: case TGWDT_CPU_RESET:
reset_reason = "Timer Group Reset CPU"; reset_reason = "Timer Group Reset CPU";
break; break;
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case TG0WDT_CPU_RESET: case TG0WDT_CPU_RESET:
reset_reason = "Timer Group 0 Reset CPU"; reset_reason = "Timer Group 0 Reset CPU";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32) #if defined(USE_ESP32_VARIANT_ESP32)
case SW_CPU_RESET: case SW_CPU_RESET:
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case RTC_SW_CPU_RESET: case RTC_SW_CPU_RESET:
#endif #endif
reset_reason = "Software Reset CPU"; reset_reason = "Software Reset CPU";
@ -98,27 +101,32 @@ std::string DebugComponent::get_reset_reason_() {
case RTCWDT_RTC_RESET: case RTCWDT_RTC_RESET:
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module"; reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
break; break;
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
defined(USE_ESP32_VARIANT_ESP32C6)
case TG1WDT_CPU_RESET: case TG1WDT_CPU_RESET:
reset_reason = "Timer Group 1 Reset CPU"; reset_reason = "Timer Group 1 Reset CPU";
break; break;
case SUPER_WDT_RESET: case SUPER_WDT_RESET:
reset_reason = "Super Watchdog Reset Digital Core And RTC Module"; reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
break; break;
case GLITCH_RTC_RESET:
reset_reason = "Glitch Reset Digital Core And RTC Module";
break;
case EFUSE_RESET: case EFUSE_RESET:
reset_reason = "eFuse Reset Digital Core"; reset_reason = "eFuse Reset Digital Core";
break; break;
#endif #endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
case GLITCH_RTC_RESET:
reset_reason = "Glitch Reset Digital Core And RTC Module";
break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
case USB_UART_CHIP_RESET: case USB_UART_CHIP_RESET:
reset_reason = "USB UART Reset Digital Core"; reset_reason = "USB UART Reset Digital Core";
break; break;
case USB_JTAG_CHIP_RESET: case USB_JTAG_CHIP_RESET:
reset_reason = "USB JTAG Reset Digital Core"; reset_reason = "USB JTAG Reset Digital Core";
break; break;
#endif
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
case POWER_GLITCH_RESET: case POWER_GLITCH_RESET:
reset_reason = "Power Glitch Reset Digital Core And RTC Module"; reset_reason = "Power Glitch Reset Digital Core And RTC Module";
break; break;

View file

@ -22,7 +22,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
size_t buffer_size = this->get_buffer_size_(); size_t buffer_size = this->get_buffer_size_();
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); RAMAllocator<uint8_t> allocator(this->use_psram_ ? 0 : RAMAllocator<uint8_t>::ALLOC_INTERNAL);
this->buf_ = allocator.allocate(buffer_size); this->buf_ = allocator.allocate(buffer_size);
if (this->buf_ == nullptr) { if (this->buf_ == nullptr) {
ESP_LOGE(TAG, "Cannot allocate LED buffer!"); ESP_LOGE(TAG, "Cannot allocate LED buffer!");
@ -37,7 +37,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
return; return;
} }
ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE); RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL);
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 +
1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset 1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset

View file

@ -45,6 +45,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; } void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
void set_use_psram(bool use_psram) { this->use_psram_ = use_psram; }
/// Set a maximum refresh rate in µs as some lights do not like being updated too often. /// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
@ -75,6 +76,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint16_t num_leds_; uint16_t num_leds_;
bool is_rgbw_; bool is_rgbw_;
bool is_wrgb_; bool is_wrgb_;
bool use_psram_;
rmt_item32_t bit0_, bit1_, reset_; rmt_item32_t bit0_, bit1_, reset_;
RGBOrder rgb_order_; RGBOrder rgb_order_;

View file

@ -55,7 +55,7 @@ CHIPSETS = {
"SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0), "SM16703": LEDStripTimings(300, 900, 900, 300, 0, 0),
} }
CONF_USE_PSRAM = "use_psram"
CONF_IS_WRGB = "is_wrgb" CONF_IS_WRGB = "is_wrgb"
CONF_BIT0_HIGH = "bit0_high" CONF_BIT0_HIGH = "bit0_high"
CONF_BIT0_LOW = "bit0_low" CONF_BIT0_LOW = "bit0_low"
@ -77,6 +77,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
cv.Optional(CONF_USE_PSRAM, default=True): cv.boolean,
cv.Inclusive( cv.Inclusive(
CONF_BIT0_HIGH, CONF_BIT0_HIGH,
"custom", "custom",
@ -145,6 +146,7 @@ async def to_code(config):
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
cg.add( cg.add(
var.set_rmt_channel( var.set_rmt_channel(

View file

@ -1,6 +1,6 @@
from esphome import automation from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import mqtt from esphome.components import mqtt, web_server
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEVICE_CLASS, CONF_DEVICE_CLASS,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_EVENT, CONF_ON_EVENT,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER,
DEVICE_CLASS_BUTTON, DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL, DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
@ -40,7 +41,10 @@ EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( EVENT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{ {
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
cv.GenerateID(): cv.declare_id(Event), cv.GenerateID(): cv.declare_id(Event),
@ -51,6 +55,7 @@ EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
} }
), ),
} }
)
) )
_UNDEF = object() _UNDEF = object()
@ -97,6 +102,9 @@ async def setup_event_core_(var, config, *, event_types: list[str]):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_event(var, config, *, event_types: list[str]): async def register_event(var, config, *, event_types: list[str]):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):

View file

@ -25,7 +25,7 @@ from esphome.const import (
CONF_SPEED_LEVEL_STATE_TOPIC, CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_STATE_TOPIC, CONF_SPEED_STATE_TOPIC,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@ -218,9 +218,8 @@ async def setup_fan_core_(var, config):
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None: if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic)) cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
for conf in config.get(CONF_ON_STATE, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View file

@ -98,13 +98,13 @@ def validate_pillow_installed(value):
except ImportError as err: except ImportError as err:
raise cv.Invalid( raise cv.Invalid(
"Please install the pillow python package to use this feature. " "Please install the pillow python package to use this feature. "
'(pip install "pillow==10.2.0")' '(pip install "pillow==10.4.0")'
) from err ) from err
if version.parse(PIL.__version__) != version.parse("10.2.0"): if version.parse(PIL.__version__) != version.parse("10.4.0"):
raise cv.Invalid( raise cv.Invalid(
"Please update your pillow installation to 10.2.0. " "Please update your pillow installation to 10.4.0. "
'(pip install "pillow==10.2.0")' '(pip install "pillow==10.4.0")'
) )
return value return value

View file

@ -0,0 +1,88 @@
#include "grove_gas_mc_v2.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome {
namespace grove_gas_mc_v2 {
static const char *const TAG = "grove_gas_mc_v2";
// I2C Commands for Grove Gas Multichannel V2 Sensor
// Taken from:
// https://github.com/Seeed-Studio/Seeed_Arduino_MultiGas/blob/master/src/Multichannel_Gas_GroveGasMultichannelV2.h
static const uint8_t GROVE_GAS_MC_V2_HEAT_ON = 0xFE;
static const uint8_t GROVE_GAS_MC_V2_HEAT_OFF = 0xFF;
static const uint8_t GROVE_GAS_MC_V2_READ_GM102B = 0x01;
static const uint8_t GROVE_GAS_MC_V2_READ_GM302B = 0x03;
static const uint8_t GROVE_GAS_MC_V2_READ_GM502B = 0x05;
static const uint8_t GROVE_GAS_MC_V2_READ_GM702B = 0x07;
bool GroveGasMultichannelV2Component::read_sensor_(uint8_t address, sensor::Sensor *sensor) {
if (sensor == nullptr) {
return true;
}
uint32_t value = 0;
if (!this->read_bytes(address, (uint8_t *) &value, 4)) {
ESP_LOGW(TAG, "Reading Grove Gas Sensor data failed!");
this->error_code_ = COMMUNICATION_FAILED;
this->status_set_warning();
return false;
}
sensor->publish_state(value);
return true;
}
void GroveGasMultichannelV2Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up Grove Multichannel Gas Sensor V2...");
// Before reading sensor values, must preheat sensor
if (!(this->write_bytes(GROVE_GAS_MC_V2_HEAT_ON, {}))) {
this->mark_failed();
this->error_code_ = APP_START_FAILED;
}
}
void GroveGasMultichannelV2Component::update() {
// Read from each of the gas sensors
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM102B, this->nitrogen_dioxide_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM302B, this->ethanol_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM502B, this->tvoc_sensor_))
return;
if (!this->read_sensor_(GROVE_GAS_MC_V2_READ_GM702B, this->carbon_monoxide_sensor_))
return;
this->status_clear_warning();
}
void GroveGasMultichannelV2Component::dump_config() {
ESP_LOGCONFIG(TAG, "Grove Multichannel Gas Sensor V2");
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "Nitrogen Dioxide", this->nitrogen_dioxide_sensor_)
LOG_SENSOR(" ", "Ethanol", this->ethanol_sensor_)
LOG_SENSOR(" ", "Carbon Monoxide", this->carbon_monoxide_sensor_)
LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_)
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICATION_FAILED:
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
break;
case APP_INVALID:
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
break;
case APP_START_FAILED:
ESP_LOGW(TAG, "Sensor reported APP start failed.");
break;
case UNKNOWN:
default:
ESP_LOGW(TAG, "Unknown setup error!");
break;
}
}
}
} // namespace grove_gas_mc_v2
} // namespace esphome

View file

@ -0,0 +1,39 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
namespace esphome {
namespace grove_gas_mc_v2 {
class GroveGasMultichannelV2Component : public PollingComponent, public i2c::I2CDevice {
SUB_SENSOR(tvoc)
SUB_SENSOR(carbon_monoxide)
SUB_SENSOR(nitrogen_dioxide)
SUB_SENSOR(ethanol)
public:
/// Setup the sensor and test for a connection.
void setup() override;
/// Schedule temperature+pressure readings.
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
enum ErrorCode {
UNKNOWN,
COMMUNICATION_FAILED,
APP_INVALID,
APP_START_FAILED,
} error_code_{UNKNOWN};
bool read_sensor_(uint8_t address, sensor::Sensor *sensor);
};
} // namespace grove_gas_mc_v2
} // namespace esphome

View file

@ -0,0 +1,77 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_CARBON_MONOXIDE,
CONF_ETHANOL,
CONF_ID,
CONF_NITROGEN_DIOXIDE,
CONF_TVOC,
DEVICE_CLASS_CARBON_MONOXIDE,
DEVICE_CLASS_NITROGEN_DIOXIDE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
ICON_AIR_FILTER,
ICON_FLASK_ROUND_BOTTOM,
ICON_GAS_CYLINDER,
ICON_MOLECULE_CO,
STATE_CLASS_MEASUREMENT,
UNIT_MICROGRAMS_PER_CUBIC_METER,
UNIT_PARTS_PER_MILLION,
)
CODEOWNERS = ["@YorkshireIoT"]
DEPENDENCIES = ["i2c"]
grove_gas_mc_v2_ns = cg.esphome_ns.namespace("grove_gas_mc_v2")
GroveGasMultichannelV2Component = grove_gas_mc_v2_ns.class_(
"GroveGasMultichannelV2Component", cg.PollingComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GroveGasMultichannelV2Component),
cv.Optional(CONF_TVOC): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_AIR_FILTER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CARBON_MONOXIDE): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_MOLECULE_CO,
accuracy_decimals=0,
device_class=DEVICE_CLASS_CARBON_MONOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_NITROGEN_DIOXIDE): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER,
icon=ICON_GAS_CYLINDER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_NITROGEN_DIOXIDE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ETHANOL): sensor.sensor_schema(
unit_of_measurement=UNIT_PARTS_PER_MILLION,
icon=ICON_FLASK_ROUND_BOTTOM,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x08))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
for key in [CONF_TVOC, CONF_CARBON_MONOXIDE, CONF_NITROGEN_DIOXIDE, CONF_ETHANOL]:
if sensor_config := config.get(key):
sensor_ = await sensor.new_sensor(sensor_config)
cg.add(getattr(var, f"set_{key}_sensor")(sensor_))

View file

@ -89,6 +89,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
void dump_config() override; void dump_config() override;
void setup() override; void setup() override;
void on_shutdown() override { this->command(ILI9XXX_SLPIN); }
display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; }
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,

View file

@ -79,6 +79,50 @@ Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
return color_off; return color_off;
} }
} }
#ifdef USE_LVGL
lv_img_dsc_t *Image::get_lv_img_dsc() {
// lazily construct lvgl image_dsc.
if (this->dsc_.data != this->data_start_) {
this->dsc_.data = this->data_start_;
this->dsc_.header.always_zero = 0;
this->dsc_.header.reserved = 0;
this->dsc_.header.w = this->width_;
this->dsc_.header.h = this->height_;
this->dsc_.data_size = image_type_to_width_stride(this->dsc_.header.w * this->dsc_.header.h, this->get_type());
switch (this->get_type()) {
case IMAGE_TYPE_BINARY:
this->dsc_.header.cf = LV_IMG_CF_ALPHA_1BIT;
break;
case IMAGE_TYPE_GRAYSCALE:
this->dsc_.header.cf = LV_IMG_CF_ALPHA_8BIT;
break;
case IMAGE_TYPE_RGB24:
this->dsc_.header.cf = LV_IMG_CF_RGB888;
break;
case IMAGE_TYPE_RGB565:
#if LV_COLOR_DEPTH == 16
this->dsc_.header.cf = this->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
#else
this->dsc_.header.cf = LV_IMG_CF_RGB565;
#endif
break;
case image::IMAGE_TYPE_RGBA:
#if LV_COLOR_DEPTH == 32
this->dsc_.header.cf = LV_IMG_CF_TRUE_COLOR;
#else
this->dsc_.header.cf = LV_IMG_CF_RGBA8888;
#endif
break;
}
}
return &this->dsc_;
}
#endif // USE_LVGL
bool Image::get_binary_pixel_(int x, int y) const { bool Image::get_binary_pixel_(int x, int y) const {
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
const uint32_t pos = x + y * width_8; const uint32_t pos = x + y * width_8;

View file

@ -1,6 +1,15 @@
#pragma once #pragma once
#include "esphome/core/color.h" #include "esphome/core/color.h"
#include "esphome/components/display/display_buffer.h" #include "esphome/components/display/display.h"
#ifdef USE_LVGL
// required for clang-tidy
#ifndef LV_CONF_H
#define LV_CONF_SKIP 1 // NOLINT
#endif // LV_CONF_H
#include <lvgl.h>
#endif // USE_LVGL
namespace esphome { namespace esphome {
namespace image { namespace image {
@ -37,7 +46,7 @@ class Image : public display::BaseImage {
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const; Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
int get_width() const override; int get_width() const override;
int get_height() const override; int get_height() const override;
const uint8_t *get_data_start() { return this->data_start_; } const uint8_t *get_data_start() const { return this->data_start_; }
ImageType get_type() const; ImageType get_type() const;
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
@ -45,6 +54,9 @@ class Image : public display::BaseImage {
void set_transparency(bool transparent) { transparent_ = transparent; } void set_transparency(bool transparent) { transparent_ = transparent; }
bool has_transparency() const { return transparent_; } bool has_transparency() const { return transparent_; }
#ifdef USE_LVGL
lv_img_dsc_t *get_lv_img_dsc();
#endif
protected: protected:
bool get_binary_pixel_(int x, int y) const; bool get_binary_pixel_(int x, int y) const;
Color get_rgb24_pixel_(int x, int y) const; Color get_rgb24_pixel_(int x, int y) const;
@ -57,6 +69,9 @@ class Image : public display::BaseImage {
ImageType type_; ImageType type_;
const uint8_t *data_start_; const uint8_t *data_start_;
bool transparent_; bool transparent_;
#ifdef USE_LVGL
lv_img_dsc_t dsc_{};
#endif
}; };
} // namespace image } // namespace image

View file

@ -7,7 +7,8 @@
extern "C" { extern "C" {
uint8_t temprature_sens_read(); uint8_t temprature_sens_read();
} }
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "driver/temp_sensor.h" #include "driver/temp_sensor.h"
#endif // USE_ESP32_VARIANT #endif // USE_ESP32_VARIANT
#endif // USE_ESP32 #endif // USE_ESP32
@ -34,7 +35,8 @@ void InternalTemperatureSensor::update() {
ESP_LOGV(TAG, "Raw temperature value: %d", raw); ESP_LOGV(TAG, "Raw temperature value: %d", raw);
temperature = (raw - 32) / 1.8f; temperature = (raw - 32) / 1.8f;
success = (raw != 128); success = (raw != 128);
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
temp_sensor_set_config(tsens); temp_sensor_set_config(tsens);
temp_sensor_start(); temp_sensor_start();

View file

@ -3,27 +3,39 @@ import esphome.codegen as cg
from esphome.components import mqtt, power_supply, web_server from esphome.components import mqtt, power_supply, web_server
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_BLUE,
CONF_BRIGHTNESS,
CONF_COLD_WHITE,
CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_COLD_WHITE_COLOR_TEMPERATURE,
CONF_COLOR_BRIGHTNESS,
CONF_COLOR_CORRECT, CONF_COLOR_CORRECT,
CONF_COLOR_MODE,
CONF_COLOR_TEMPERATURE,
CONF_DEFAULT_TRANSITION_LENGTH, CONF_DEFAULT_TRANSITION_LENGTH,
CONF_EFFECTS, CONF_EFFECTS,
CONF_FLASH_TRANSITION_LENGTH, CONF_FLASH_TRANSITION_LENGTH,
CONF_GAMMA_CORRECT, CONF_GAMMA_CORRECT,
CONF_GREEN,
CONF_ID, CONF_ID,
CONF_INITIAL_STATE,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_ON_STATE, CONF_ON_STATE,
CONF_ON_TURN_OFF, CONF_ON_TURN_OFF,
CONF_ON_TURN_ON, CONF_ON_TURN_ON,
CONF_POWER_SUPPLY, CONF_POWER_SUPPLY,
CONF_RED,
CONF_RESTORE_MODE, CONF_RESTORE_MODE,
CONF_STATE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WARM_WHITE,
CONF_WARM_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
CONF_WHITE,
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
from .automation import light_control_to_code # noqa from .automation import LIGHT_STATE_SCHEMA
from .effects import ( from .effects import (
ADDRESSABLE_EFFECTS, ADDRESSABLE_EFFECTS,
BINARY_EFFECTS, BINARY_EFFECTS,
@ -35,8 +47,10 @@ from .effects import (
from .types import ( # noqa from .types import ( # noqa
AddressableLight, AddressableLight,
AddressableLightState, AddressableLightState,
ColorMode,
LightOutput, LightOutput,
LightState, LightState,
LightStateRTCState,
LightStateTrigger, LightStateTrigger,
LightTurnOffTrigger, LightTurnOffTrigger,
LightTurnOnTrigger, LightTurnOnTrigger,
@ -85,6 +99,7 @@ LIGHT_SCHEMA = (
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LightStateTrigger),
} }
), ),
cv.Optional(CONF_INITIAL_STATE): LIGHT_STATE_SCHEMA,
} }
) )
) )
@ -145,6 +160,22 @@ async def setup_light_core_(light_var, output_var, config):
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE])) cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
if (initial_state_config := config.get(CONF_INITIAL_STATE)) is not None:
initial_state = LightStateRTCState(
initial_state_config.get(CONF_COLOR_MODE, ColorMode.UNKNOWN),
initial_state_config.get(CONF_STATE, False),
initial_state_config.get(CONF_BRIGHTNESS, 1.0),
initial_state_config.get(CONF_COLOR_BRIGHTNESS, 1.0),
initial_state_config.get(CONF_RED, 1.0),
initial_state_config.get(CONF_GREEN, 1.0),
initial_state_config.get(CONF_BLUE, 1.0),
initial_state_config.get(CONF_WHITE, 1.0),
initial_state_config.get(CONF_COLOR_TEMPERATURE, 1.0),
initial_state_config.get(CONF_COLD_WHITE, 1.0),
initial_state_config.get(CONF_WARM_WHITE, 1.0),
)
cg.add(light_var.set_initial_state(initial_state))
if ( if (
default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH) default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH)
) is not None: ) is not None:
@ -181,9 +212,8 @@ async def setup_light_core_(light_var, output_var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, light_var) mqtt_ = cg.new_Pvariable(mqtt_id, light_var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(light_var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, light_var, config)
async def register_light(output_var, config): async def register_light(output_var, config):

View file

@ -1,41 +1,42 @@
from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation
from esphome.const import ( from esphome.const import (
CONF_ID,
CONF_COLOR_MODE,
CONF_TRANSITION_LENGTH,
CONF_STATE,
CONF_FLASH_LENGTH,
CONF_EFFECT,
CONF_BRIGHTNESS,
CONF_COLOR_BRIGHTNESS,
CONF_RED,
CONF_GREEN,
CONF_BLUE, CONF_BLUE,
CONF_WHITE, CONF_BRIGHTNESS,
CONF_COLOR_TEMPERATURE, CONF_BRIGHTNESS_LIMITS,
CONF_COLD_WHITE, CONF_COLD_WHITE,
CONF_WARM_WHITE, CONF_COLOR_BRIGHTNESS,
CONF_COLOR_MODE,
CONF_COLOR_TEMPERATURE,
CONF_EFFECT,
CONF_FLASH_LENGTH,
CONF_GREEN,
CONF_ID,
CONF_LIMIT_MODE,
CONF_MAX_BRIGHTNESS,
CONF_MIN_BRIGHTNESS,
CONF_RANGE_FROM, CONF_RANGE_FROM,
CONF_RANGE_TO, CONF_RANGE_TO,
CONF_BRIGHTNESS_LIMITS, CONF_RED,
CONF_LIMIT_MODE, CONF_STATE,
CONF_MIN_BRIGHTNESS, CONF_TRANSITION_LENGTH,
CONF_MAX_BRIGHTNESS, CONF_WARM_WHITE,
CONF_WHITE,
) )
from .types import ( from .types import (
ColorMode,
COLOR_MODES, COLOR_MODES,
LIMIT_MODES, LIMIT_MODES,
DimRelativeAction,
ToggleAction,
LightState,
LightControlAction,
AddressableLightState, AddressableLightState,
AddressableSet, AddressableSet,
LightIsOnCondition, ColorMode,
DimRelativeAction,
LightControlAction,
LightIsOffCondition, LightIsOffCondition,
LightIsOnCondition,
LightState,
ToggleAction,
) )
@ -62,18 +63,10 @@ async def light_toggle_to_code(config, action_id, template_arg, args):
return var return var
LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema( LIGHT_STATE_SCHEMA = cv.Schema(
{ {
cv.Required(CONF_ID): cv.use_id(LightState),
cv.Optional(CONF_COLOR_MODE): cv.enum(COLOR_MODES, upper=True, space="_"), cv.Optional(CONF_COLOR_MODE): cv.enum(COLOR_MODES, upper=True, space="_"),
cv.Optional(CONF_STATE): cv.templatable(cv.boolean), cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
cv.Exclusive(CONF_TRANSITION_LENGTH, "transformer"): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Exclusive(CONF_FLASH_LENGTH, "transformer"): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string),
cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage), cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage), cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage),
cv.Optional(CONF_RED): cv.templatable(cv.percentage), cv.Optional(CONF_RED): cv.templatable(cv.percentage),
@ -85,6 +78,20 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema(
cv.Optional(CONF_WARM_WHITE): cv.templatable(cv.percentage), cv.Optional(CONF_WARM_WHITE): cv.templatable(cv.percentage),
} }
) )
LIGHT_CONTROL_ACTION_SCHEMA = LIGHT_STATE_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.use_id(LightState),
cv.Exclusive(CONF_TRANSITION_LENGTH, "transformer"): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Exclusive(CONF_FLASH_LENGTH, "transformer"): cv.templatable(
cv.positive_time_period_milliseconds
),
cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string),
}
)
LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id( LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id(
{ {
cv.Required(CONF_ID): cv.use_id(LightState), cv.Required(CONF_ID): cv.use_id(LightState),

View file

@ -1,59 +1,59 @@
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation
from esphome.const import ( from esphome.const import (
CONF_NAME,
CONF_LAMBDA,
CONF_UPDATE_INTERVAL,
CONF_TRANSITION_LENGTH,
CONF_COLORS,
CONF_STATE,
CONF_DURATION,
CONF_BRIGHTNESS,
CONF_COLOR_MODE,
CONF_COLOR_BRIGHTNESS,
CONF_RED,
CONF_GREEN,
CONF_BLUE,
CONF_WHITE,
CONF_COLOR_TEMPERATURE,
CONF_COLD_WHITE,
CONF_WARM_WHITE,
CONF_ALPHA, CONF_ALPHA,
CONF_BLUE,
CONF_BRIGHTNESS,
CONF_COLD_WHITE,
CONF_COLOR_BRIGHTNESS,
CONF_COLOR_MODE,
CONF_COLOR_TEMPERATURE,
CONF_COLORS,
CONF_DURATION,
CONF_GREEN,
CONF_INTENSITY, CONF_INTENSITY,
CONF_SPEED, CONF_LAMBDA,
CONF_WIDTH,
CONF_NUM_LEDS,
CONF_RANDOM,
CONF_SEQUENCE,
CONF_MAX_BRIGHTNESS, CONF_MAX_BRIGHTNESS,
CONF_MIN_BRIGHTNESS, CONF_MIN_BRIGHTNESS,
CONF_NAME,
CONF_NUM_LEDS,
CONF_RANDOM,
CONF_RED,
CONF_SEQUENCE,
CONF_SPEED,
CONF_STATE,
CONF_TRANSITION_LENGTH,
CONF_UPDATE_INTERVAL,
CONF_WARM_WHITE,
CONF_WHITE,
CONF_WIDTH,
) )
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.util import Registry from esphome.util import Registry
from .types import ( from .types import (
ColorMode,
COLOR_MODES, COLOR_MODES,
AddressableColorWipeEffect,
AddressableColorWipeEffectColor,
AddressableFireworksEffect,
AddressableFlickerEffect,
AddressableLambdaLightEffect,
AddressableLightRef,
AddressableRainbowLightEffect,
AddressableRandomTwinkleEffect,
AddressableScanEffect,
AddressableTwinkleEffect,
AutomationLightEffect,
Color,
ColorMode,
FlickerLightEffect,
LambdaLightEffect, LambdaLightEffect,
LightColorValues,
PulseLightEffect, PulseLightEffect,
RandomLightEffect, RandomLightEffect,
StrobeLightEffect, StrobeLightEffect,
StrobeLightEffectColor, StrobeLightEffectColor,
LightColorValues,
AddressableLightRef,
AddressableLambdaLightEffect,
FlickerLightEffect,
AddressableRainbowLightEffect,
AddressableColorWipeEffect,
AddressableColorWipeEffectColor,
AddressableScanEffect,
AddressableTwinkleEffect,
AddressableRandomTwinkleEffect,
AddressableFireworksEffect,
AddressableFlickerEffect,
AutomationLightEffect,
Color,
) )
CONF_ADD_LED_INTERVAL = "add_led_interval" CONF_ADD_LED_INTERVAL = "add_led_interval"

View file

@ -1,6 +1,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "light_state.h"
#include "light_output.h" #include "light_output.h"
#include "light_state.h"
#include "transformers.h" #include "transformers.h"
namespace esphome { namespace esphome {
@ -16,21 +17,6 @@ LightCall LightState::turn_off() { return this->make_call().set_state(false); }
LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); } LightCall LightState::toggle() { return this->make_call().set_state(!this->remote_values.is_on()); }
LightCall LightState::make_call() { return LightCall(this); } LightCall LightState::make_call() { return LightCall(this); }
struct LightStateRTCState {
ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
float brightness{1.0f};
float color_brightness{1.0f};
float red{1.0f};
float green{1.0f};
float blue{1.0f};
float white{1.0f};
float color_temp{1.0f};
float cold_white{1.0f};
float warm_white{1.0f};
uint32_t effect{0};
};
void LightState::setup() { void LightState::setup() {
ESP_LOGCONFIG(TAG, "Setting up light '%s'...", this->get_name().c_str()); ESP_LOGCONFIG(TAG, "Setting up light '%s'...", this->get_name().c_str());
@ -48,6 +34,9 @@ void LightState::setup() {
auto call = this->make_call(); auto call = this->make_call();
LightStateRTCState recovered{}; LightStateRTCState recovered{};
if (this->initial_state_.has_value()) {
recovered = *this->initial_state_;
}
switch (this->restore_mode_) { switch (this->restore_mode_) {
case LIGHT_RESTORE_DEFAULT_OFF: case LIGHT_RESTORE_DEFAULT_OFF:
case LIGHT_RESTORE_DEFAULT_ON: case LIGHT_RESTORE_DEFAULT_ON:
@ -175,6 +164,7 @@ void LightState::set_flash_transition_length(uint32_t flash_transition_length) {
uint32_t LightState::get_flash_transition_length() const { return this->flash_transition_length_; } uint32_t LightState::get_flash_transition_length() const { return this->flash_transition_length_; }
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; } void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
bool LightState::supports_effects() { return !this->effects_.empty(); } bool LightState::supports_effects() { return !this->effects_.empty(); }
const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; } const std::vector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
void LightState::add_effects(const std::vector<LightEffect *> &effects) { void LightState::add_effects(const std::vector<LightEffect *> &effects) {

View file

@ -28,6 +28,35 @@ enum LightRestoreMode {
LIGHT_RESTORE_AND_ON, LIGHT_RESTORE_AND_ON,
}; };
struct LightStateRTCState {
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
float blue, float white, float color_temp, float cold_white, float warm_white)
: color_mode(color_mode),
state(state),
brightness(brightness),
color_brightness(color_brightness),
red(red),
green(green),
blue(blue),
white(white),
color_temp(color_temp),
cold_white(cold_white),
warm_white(warm_white) {}
LightStateRTCState() = default;
ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
float brightness{1.0f};
float color_brightness{1.0f};
float red{1.0f};
float green{1.0f};
float blue{1.0f};
float white{1.0f};
float color_temp{1.0f};
float cold_white{1.0f};
float warm_white{1.0f};
uint32_t effect{0};
};
/** This class represents the communication layer between the front-end MQTT layer and the /** This class represents the communication layer between the front-end MQTT layer and the
* hardware output layer. * hardware output layer.
*/ */
@ -116,6 +145,9 @@ class LightState : public EntityBase, public Component {
/// Set the restore mode of this light /// Set the restore mode of this light
void set_restore_mode(LightRestoreMode restore_mode); void set_restore_mode(LightRestoreMode restore_mode);
/// Set the initial state of this light
void set_initial_state(const LightStateRTCState &initial_state);
/// Return whether the light has any effects that meet the trait requirements. /// Return whether the light has any effects that meet the trait requirements.
bool supports_effects(); bool supports_effects();
@ -212,6 +244,8 @@ class LightState : public EntityBase, public Component {
float gamma_correct_{}; float gamma_correct_{};
/// Restore mode of the light. /// Restore mode of the light.
LightRestoreMode restore_mode_; LightRestoreMode restore_mode_;
/// Initial state of the light.
optional<LightStateRTCState> initial_state_{};
/// List of effects for this light. /// List of effects for this light.
std::vector<LightEffect *> effects_; std::vector<LightEffect *> effects_;

View file

@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome import automation from esphome import automation
import esphome.codegen as cg
# Base # Base
light_ns = cg.esphome_ns.namespace("light") light_ns = cg.esphome_ns.namespace("light")
@ -12,6 +12,8 @@ AddressableLightRef = AddressableLight.operator("ref")
Color = cg.esphome_ns.class_("Color") Color = cg.esphome_ns.class_("Color")
LightColorValues = light_ns.class_("LightColorValues") LightColorValues = light_ns.class_("LightColorValues")
LightStateRTCState = light_ns.struct("LightStateRTCState")
# Color modes # Color modes
ColorMode = light_ns.enum("ColorMode", is_class=True) ColorMode = light_ns.enum("ColorMode", is_class=True)
COLOR_MODES = { COLOR_MODES = {

View file

@ -9,7 +9,7 @@ from esphome.const import (
CONF_ON_LOCK, CONF_ON_LOCK,
CONF_ON_UNLOCK, CONF_ON_UNLOCK,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@ -66,9 +66,8 @@ async def setup_lock_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_lock(var, config): async def register_lock(var, config):

View file

@ -53,7 +53,7 @@ from .types import (
lv_style_t, lv_style_t,
lvgl_ns, lvgl_ns,
) )
from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties from .widgets import Widget, add_widgets, lv_scr_act, set_obj_properties, styles_used
from .widgets.animimg import animimg_spec from .widgets.animimg import animimg_spec
from .widgets.arc import arc_spec from .widgets.arc import arc_spec
from .widgets.button import button_spec from .widgets.button import button_spec
@ -280,6 +280,8 @@ async def to_code(config):
for comp in helpers.lvgl_components_required: for comp in helpers.lvgl_components_required:
CORE.add_define(f"USE_LVGL_{comp.upper()}") CORE.add_define(f"USE_LVGL_{comp.upper()}")
if "transform_angle" in styles_used:
add_define("LV_COLOR_SCREEN_TRANSP", "1")
for use in helpers.lv_uses: for use in helpers.lv_uses:
add_define(f"LV_USE_{use.upper()}") add_define(f"LV_USE_{use.upper()}")
lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME) lv_conf_h_file = CORE.relative_src_path(LV_CONF_FILENAME)

View file

@ -452,6 +452,7 @@ CONF_OFFSET_Y = "offset_y"
CONF_ONE_CHECKED = "one_checked" CONF_ONE_CHECKED = "one_checked"
CONF_ONE_LINE = "one_line" CONF_ONE_LINE = "one_line"
CONF_ON_SELECT = "on_select" CONF_ON_SELECT = "on_select"
CONF_OPA = "opa"
CONF_NEXT = "next" CONF_NEXT = "next"
CONF_PAD_ROW = "pad_row" CONF_PAD_ROW = "pad_row"
CONF_PAD_COLUMN = "pad_column" CONF_PAD_COLUMN = "pad_column"

View file

@ -31,7 +31,6 @@ from .defines import (
literal, literal,
) )
from .helpers import esphome_fonts_used, lv_fonts_used, requires_component from .helpers import esphome_fonts_used, lv_fonts_used, requires_component
from .lvcode import lv_expr
from .types import lv_font_t, lv_gradient_t, lv_img_t from .types import lv_font_t, lv_gradient_t, lv_img_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER") opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@ -243,6 +242,8 @@ def pixels_or_percent_validator(value):
"""A length in one axis - either a number (pixels) or a percentage""" """A length in one axis - either a number (pixels) or a percentage"""
if value == SCHEMA_EXTRACT: if value == SCHEMA_EXTRACT:
return ["pixels", "..%"] return ["pixels", "..%"]
if isinstance(value, str) and value.lower().endswith("px"):
value = cv.int_(value[:-2])
value = cv.Any(cv.int_, cv.percentage)(value) value = cv.Any(cv.int_, cv.percentage)(value)
if isinstance(value, int): if isinstance(value, int):
return value return value
@ -330,7 +331,7 @@ def image_validator(value):
lv_image = LValidator( lv_image = LValidator(
image_validator, image_validator,
lv_img_t, lv_img_t,
retmapper=lambda x: lv_expr.img_from(MockObj(x)), retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(),
requires="image", requires="image",
) )
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal) lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)

View file

@ -356,49 +356,6 @@ bool lv_is_pre_initialise() {
return false; return false;
} }
#ifdef USE_LVGL_IMAGE
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc) {
if (img_dsc == nullptr)
img_dsc = new lv_img_dsc_t(); // NOLINT
img_dsc->header.always_zero = 0;
img_dsc->header.reserved = 0;
img_dsc->header.w = src->get_width();
img_dsc->header.h = src->get_height();
img_dsc->data = src->get_data_start();
img_dsc->data_size = image_type_to_width_stride(img_dsc->header.w * img_dsc->header.h, src->get_type());
switch (src->get_type()) {
case image::IMAGE_TYPE_BINARY:
img_dsc->header.cf = LV_IMG_CF_ALPHA_1BIT;
break;
case image::IMAGE_TYPE_GRAYSCALE:
img_dsc->header.cf = LV_IMG_CF_ALPHA_8BIT;
break;
case image::IMAGE_TYPE_RGB24:
img_dsc->header.cf = LV_IMG_CF_RGB888;
break;
case image::IMAGE_TYPE_RGB565:
#if LV_COLOR_DEPTH == 16
img_dsc->header.cf = src->has_transparency() ? LV_IMG_CF_TRUE_COLOR_CHROMA_KEYED : LV_IMG_CF_TRUE_COLOR;
#else
img_dsc->header.cf = LV_IMG_CF_RGB565;
#endif
break;
case image::IMAGE_TYPE_RGBA:
#if LV_COLOR_DEPTH == 32
img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;
#else
img_dsc->header.cf = LV_IMG_CF_RGBA8888;
#endif
break;
}
return img_dsc;
}
#endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj) { void lv_animimg_stop(lv_obj_t *obj) {
auto *animg = (lv_animimg_t *) obj; auto *animg = (lv_animimg_t *) obj;

View file

@ -102,10 +102,6 @@ class FontEngine {
lv_font_t lv_font_{}; lv_font_t lv_font_{};
}; };
#endif // USE_LVGL_FONT #endif // USE_LVGL_FONT
#ifdef USE_LVGL_IMAGE
lv_img_dsc_t *lv_img_from(image::Image *src, lv_img_dsc_t *img_dsc = nullptr);
#endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG #ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj); void lv_animimg_stop(lv_obj_t *obj);
#endif // USE_LVGL_ANIMIMG #endif // USE_LVGL_ANIMIMG

View file

@ -34,13 +34,13 @@ async def to_code(config):
widget = widget[0] widget = widget[0]
await wait_for_widgets() await wait_for_widgets()
async with LambdaContext([(cg.std_string, "text_value")]) as control: async with LambdaContext([(cg.std_string, "text_value")]) as control:
await widget.set_property("text", "text_value.c_str())") await widget.set_property("text", "text_value.c_str()")
lv.event_send(widget.obj, API_EVENT, None) lv.event_send(widget.obj, API_EVENT, cg.nullptr)
control.add(textvar.publish_state(widget.get_value())) control.add(textvar.publish_state(widget.get_value()))
async with LambdaContext(EVENT_ARG) as lamb: async with LambdaContext(EVENT_ARG) as lamb:
lv_add(textvar.publish_state(widget.get_value())) lv_add(textvar.publish_state(widget.get_value()))
async with LvContext(paren): async with LvContext(paren):
widget.var.set_control_lambda(await control.get_lambda()) lv_add(textvar.set_control_lambda(await control.get_lambda()))
lv_add( lv_add(
paren.add_event_cb( paren.add_event_cb(
widget.obj, widget.obj,

View file

@ -52,6 +52,7 @@ from ..types import LV_STATE, LvType, WidgetType, lv_coord_t, lv_obj_t, lv_obj_t
EVENT_LAMB = "event_lamb__" EVENT_LAMB = "event_lamb__"
theme_widget_map = {} theme_widget_map = {}
styles_used = set()
class LvScrActType(WidgetType): class LvScrActType(WidgetType):
@ -158,6 +159,7 @@ class Widget:
def set_style(self, prop, value, state): def set_style(self, prop, value, state):
if value is None: if value is None:
return return
styles_used.add(prop)
lv.call(f"obj_set_style_{prop}", self.obj, value, state) lv.call(f"obj_set_style_{prop}", self.obj, value, state)
def __type_base(self): def __type_base(self):

View file

@ -2,13 +2,12 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_DURATION, CONF_ID from esphome.const import CONF_DURATION, CONF_ID
from esphome.cpp_generator import MockObj
from ..automation import action_to_code from ..automation import action_to_code
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
from ..helpers import lvgl_components_required from ..helpers import lvgl_components_required
from ..lv_validation import lv_image, lv_milliseconds from ..lv_validation import lv_image, lv_milliseconds
from ..lvcode import lv, lv_expr from ..lvcode import lv
from ..types import LvType, ObjUpdateAction, void_ptr from ..types import LvType, ObjUpdateAction, void_ptr
from . import Widget, WidgetType, get_widgets from . import Widget, WidgetType, get_widgets
from .img import CONF_IMAGE from .img import CONF_IMAGE
@ -63,7 +62,7 @@ class AnimimgType(WidgetType):
if CONF_SRC in config: if CONF_SRC in config:
for x in config[CONF_SRC]: for x in config[CONF_SRC]:
await cg.get_variable(x) await cg.get_variable(x)
srcs = [lv_expr.img_from(MockObj(x)) for x in config[CONF_SRC]] srcs = [await lv_image.process(x) for x in config[CONF_SRC]]
src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs) src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
count = len(config[CONF_SRC]) count = len(config[CONF_SRC])
lv.animimg_set_src(w.obj, src_id, count) lv.animimg_set_src(w.obj, src_id, count)
@ -73,7 +72,7 @@ class AnimimgType(WidgetType):
lv.animimg_start(w.obj) lv.animimg_start(w.obj)
def get_uses(self): def get_uses(self):
return CONF_IMAGE, CONF_LABEL return "img", CONF_IMAGE, CONF_LABEL
animimg_spec = AnimimgType() animimg_spec = AnimimgType()

View file

@ -6,6 +6,8 @@ from ..defines import (
CONF_DIR, CONF_DIR,
CONF_INDICATOR, CONF_INDICATOR,
CONF_MAIN, CONF_MAIN,
CONF_SCROLLBAR,
CONF_SELECTED,
CONF_SELECTED_INDEX, CONF_SELECTED_INDEX,
CONF_SYMBOL, CONF_SYMBOL,
DIRECTIONS, DIRECTIONS,
@ -23,7 +25,9 @@ CONF_DROPDOWN_LIST = "dropdown_list"
lv_dropdown_t = LvSelect("lv_dropdown_t") lv_dropdown_t = LvSelect("lv_dropdown_t")
lv_dropdown_list_t = LvType("lv_dropdown_list_t") lv_dropdown_list_t = LvType("lv_dropdown_list_t")
dropdown_list_spec = WidgetType(CONF_DROPDOWN_LIST, lv_dropdown_list_t, (CONF_MAIN,)) dropdown_list_spec = WidgetType(
CONF_DROPDOWN_LIST, lv_dropdown_list_t, (CONF_MAIN, CONF_SELECTED, CONF_SCROLLBAR)
)
DROPDOWN_BASE_SCHEMA = cv.Schema( DROPDOWN_BASE_SCHEMA = cv.Schema(
{ {

View file

@ -20,6 +20,7 @@ from ..defines import (
CONF_END_VALUE, CONF_END_VALUE,
CONF_INDICATOR, CONF_INDICATOR,
CONF_MAIN, CONF_MAIN,
CONF_OPA,
CONF_PIVOT_X, CONF_PIVOT_X,
CONF_PIVOT_Y, CONF_PIVOT_Y,
CONF_SRC, CONF_SRC,
@ -35,10 +36,11 @@ from ..lv_validation import (
lv_color, lv_color,
lv_float, lv_float,
lv_image, lv_image,
opacity,
requires_component, requires_component,
size, size,
) )
from ..lvcode import LocalVariable, lv, lv_assign, lv_expr from ..lvcode import LocalVariable, lv, lv_assign, lv_expr, lv_obj
from ..types import LvType, ObjUpdateAction from ..types import LvType, ObjUpdateAction
from . import Widget, WidgetType, get_widgets from . import Widget, WidgetType, get_widgets
from .arc import CONF_ARC from .arc import CONF_ARC
@ -76,6 +78,7 @@ INDICATOR_LINE_SCHEMA = cv.Schema(
cv.Optional(CONF_COLOR, default=0): lv_color, cv.Optional(CONF_COLOR, default=0): lv_color,
cv.Optional(CONF_R_MOD, default=0): size, cv.Optional(CONF_R_MOD, default=0): size,
cv.Optional(CONF_VALUE): lv_float, cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_IMG_SCHEMA = cv.Schema( INDICATOR_IMG_SCHEMA = cv.Schema(
@ -84,6 +87,7 @@ INDICATOR_IMG_SCHEMA = cv.Schema(
cv.Required(CONF_PIVOT_X): pixels, cv.Required(CONF_PIVOT_X): pixels,
cv.Required(CONF_PIVOT_Y): pixels, cv.Required(CONF_PIVOT_Y): pixels,
cv.Optional(CONF_VALUE): lv_float, cv.Optional(CONF_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_ARC_SCHEMA = cv.Schema( INDICATOR_ARC_SCHEMA = cv.Schema(
@ -94,6 +98,7 @@ INDICATOR_ARC_SCHEMA = cv.Schema(
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
cv.Optional(CONF_END_VALUE): lv_float, cv.Optional(CONF_END_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
) )
INDICATOR_TICKS_SCHEMA = cv.Schema( INDICATOR_TICKS_SCHEMA = cv.Schema(
@ -218,9 +223,7 @@ class MeterType(WidgetType):
for indicator in scale_conf.get(CONF_INDICATORS, ()): for indicator in scale_conf.get(CONF_INDICATORS, ()):
(t, v) = next(iter(indicator.items())) (t, v) = next(iter(indicator.items()))
iid = v[CONF_ID] iid = v[CONF_ID]
ivar = cg.new_variable( ivar = cg.Pvariable(iid, cg.nullptr, type_=lv_meter_indicator_t)
iid, cg.nullptr, type_=lv_meter_indicator_t_ptr
)
# Enable getting the meter to which this belongs. # Enable getting the meter to which this belongs.
wid = Widget.create(iid, var, obj_spec, v) wid = Widget.create(iid, var, obj_spec, v)
wid.obj = ivar wid.obj = ivar
@ -268,9 +271,7 @@ class MeterType(WidgetType):
v[CONF_PIVOT_Y], v[CONF_PIVOT_Y],
), ),
) )
start_value = await get_start_value(v) await set_indicator_values(var, ivar, v)
end_value = await get_end_value(v)
set_indicator_values(var, ivar, start_value, end_value)
meter_spec = MeterType() meter_spec = MeterType()
@ -285,21 +286,22 @@ meter_spec = MeterType()
cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_VALUE, CONF_VALUE): lv_float,
cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float, cv.Exclusive(CONF_START_VALUE, CONF_VALUE): lv_float,
cv.Optional(CONF_END_VALUE): lv_float, cv.Optional(CONF_END_VALUE): lv_float,
cv.Optional(CONF_OPA): opacity,
} }
), ),
) )
async def indicator_update_to_code(config, action_id, template_arg, args): async def indicator_update_to_code(config, action_id, template_arg, args):
widget = await get_widgets(config) widget = await get_widgets(config)
start_value = await get_start_value(config)
end_value = await get_end_value(config)
async def set_value(w: Widget): async def set_value(w: Widget):
set_indicator_values(w.var, w.obj, start_value, end_value) await set_indicator_values(w.var, w.obj, config)
return await action_to_code(widget, set_value, action_id, template_arg, args) return await action_to_code(widget, set_value, action_id, template_arg, args)
def set_indicator_values(meter, indicator, start_value, end_value): async def set_indicator_values(meter, indicator, config):
start_value = await get_start_value(config)
end_value = await get_end_value(config)
if start_value is not None: if start_value is not None:
if end_value is None: if end_value is None:
lv.meter_set_indicator_value(meter, indicator, start_value) lv.meter_set_indicator_value(meter, indicator, start_value)
@ -307,3 +309,6 @@ def set_indicator_values(meter, indicator, start_value, end_value):
lv.meter_set_indicator_start_value(meter, indicator, start_value) lv.meter_set_indicator_start_value(meter, indicator, start_value)
if end_value is not None: if end_value is not None:
lv.meter_set_indicator_end_value(meter, indicator, end_value) lv.meter_set_indicator_end_value(meter, indicator, end_value)
if (opa := config.get(CONF_OPA)) is not None:
lv_assign(indicator.opa, await opacity.process(opa))
lv_obj.invalidate(meter)

View file

View file

@ -0,0 +1,323 @@
#include "nau7802.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace nau7802 {
static const char *const TAG = "nau7802";
// Only define what we need
static const uint8_t READ_BIT = 0x01;
static const uint8_t PU_CTRL_REG = 0x00;
static const uint8_t PU_CTRL_REGISTER_RESET = 0x01;
static const uint8_t PU_CTRL_POWERUP_DIGITAL = 0x02;
static const uint8_t PU_CTRL_POWERUP_ANALOG = 0x04;
static const uint8_t PU_CTRL_POWERUP_READY = 0x08;
static const uint8_t PU_CTRL_CYCLE_START = 0x10;
static const uint8_t PU_CTRL_CYCLE_READY = 0x20;
static const uint8_t PU_CTRL_AVDD_EXTERNAL = 0x80;
static const uint8_t CTRL1_REG = 0x01;
static const uint8_t CTRL1_LDO_SHIFT = 3;
static const uint8_t CTRL1_LDO_MASK = (0x7 << CTRL1_LDO_SHIFT);
static const uint8_t CTRL1_GAIN_MASK = 0x7;
static const uint8_t CTRL2_REG = 0x02;
static const uint8_t CTRL2_CRS_SHIFT = 4;
static const uint8_t CTRL2_CRS_MASK = (0x7 << CTRL2_CRS_SHIFT);
static const uint8_t CTRL2_CALS = 0x04;
static const uint8_t CTRL2_CAL_ERR = 0x08;
static const uint8_t CTRL2_GAIN_CALIBRATION = 0x03;
static const uint8_t CTRL2_CONFIG_MASK = 0xF0;
static const uint8_t OCAL1_B2_REG = 0x03;
static const uint8_t GCAL1_B3_REG = 0x06;
static const uint8_t GCAL1_FRACTIONAL = 23;
// only need the first data register for sequential read method
static const uint8_t ADCO_B2_REG = 0x12;
static const uint8_t ADC_REG = 0x15;
static const uint8_t ADC_CHPS_DISABLE = 0x30;
static const uint8_t PGA_REG = 0x1B;
static const uint8_t PGA_LDOMODE_ESR = 0x40;
static const uint8_t POWER_REG = 0x1C;
static const uint8_t POWER_PGA_CAP_EN = 0x80;
static const uint8_t DEVICE_REV = 0x1F;
void NAU7802Sensor::setup() {
i2c::I2CRegister pu_ctrl = this->reg(PU_CTRL_REG);
ESP_LOGCONFIG(TAG, "Setting up NAU7802 '%s'...", this->name_.c_str());
uint8_t rev;
if (this->read_register(DEVICE_REV | READ_BIT, &rev, 1)) {
ESP_LOGE(TAG, "Failed I2C read during setup()");
this->mark_failed();
return;
}
ESP_LOGI(TAG, "Setting up NAU7802 Rev %d", rev);
// reset
pu_ctrl |= PU_CTRL_REGISTER_RESET;
delay(10);
pu_ctrl &= ~PU_CTRL_REGISTER_RESET;
// power up digital hw
pu_ctrl |= PU_CTRL_POWERUP_DIGITAL;
delay(1);
if (!(pu_ctrl.get() & PU_CTRL_POWERUP_READY)) {
ESP_LOGE(TAG, "Failed to reset sensor during setup()");
this->mark_failed();
return;
}
uint32_t gcal = (uint32_t) (round(this->gain_calibration_ * (1 << GCAL1_FRACTIONAL)));
this->write_value_(OCAL1_B2_REG, 3, this->offset_calibration_);
this->write_value_(GCAL1_B3_REG, 4, gcal);
// turn on AFE
pu_ctrl |= PU_CTRL_POWERUP_ANALOG;
auto f = std::bind(&NAU7802Sensor::complete_setup_, this);
this->set_timeout(600, f);
}
void NAU7802Sensor::complete_setup_() {
i2c::I2CRegister pu_ctrl = this->reg(PU_CTRL_REG);
i2c::I2CRegister ctrl1 = this->reg(CTRL1_REG);
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
pu_ctrl |= PU_CTRL_CYCLE_START;
// set gain
ctrl1 &= ~CTRL1_GAIN_MASK;
ctrl1 |= this->gain_;
// enable internal LDO
if (this->ldo_ != NAU7802_LDO_EXTERNAL) {
pu_ctrl |= PU_CTRL_AVDD_EXTERNAL;
ctrl1 &= ~CTRL1_LDO_MASK;
ctrl1 |= this->ldo_ << CTRL1_LDO_SHIFT;
}
// set sps
ctrl2 &= ~CTRL2_CRS_MASK;
ctrl2 |= this->sps_ << CTRL2_CRS_SHIFT;
// disable ADC chopper clock
i2c::I2CRegister adc_reg = this->reg(ADC_REG);
adc_reg |= ADC_CHPS_DISABLE;
// use low ESR caps
i2c::I2CRegister pga_reg = this->reg(PGA_REG);
pga_reg &= ~PGA_LDOMODE_ESR;
// PGA stabilizer cap on output
i2c::I2CRegister pwr_reg = this->reg(POWER_REG);
pwr_reg |= POWER_PGA_CAP_EN;
this->setup_complete_ = true;
}
void NAU7802Sensor::dump_config() {
LOG_SENSOR("", "NAU7802", this);
LOG_I2C_DEVICE(this);
if (this->is_failed()) {
ESP_LOGE(TAG, "Communication with NAU7802 failed earlier, during setup");
return;
}
// Note these may differ from the values on the device if calbration has been run
ESP_LOGCONFIG(TAG, " Offset Calibration: %s", to_string(this->offset_calibration_).c_str());
ESP_LOGCONFIG(TAG, " Gain Calibration: %f", this->gain_calibration_);
std::string voltage = "unknown";
switch (this->ldo_) {
case NAU7802_LDO_2V4:
voltage = "2.4V";
break;
case NAU7802_LDO_2V7:
voltage = "2.7V";
break;
case NAU7802_LDO_3V0:
voltage = "3.0V";
break;
case NAU7802_LDO_3V3:
voltage = "3.3V";
break;
case NAU7802_LDO_3V6:
voltage = "3.6V";
break;
case NAU7802_LDO_3V9:
voltage = "3.9V";
break;
case NAU7802_LDO_4V2:
voltage = "4.2V";
break;
case NAU7802_LDO_4V5:
voltage = "4.5V";
break;
case NAU7802_LDO_EXTERNAL:
voltage = "External";
break;
}
ESP_LOGCONFIG(TAG, " LDO Voltage: %s", voltage.c_str());
int gain = 0;
switch (this->gain_) {
case NAU7802_GAIN_128:
gain = 128;
break;
case NAU7802_GAIN_64:
gain = 64;
break;
case NAU7802_GAIN_32:
gain = 32;
break;
case NAU7802_GAIN_16:
gain = 16;
break;
case NAU7802_GAIN_8:
gain = 8;
break;
case NAU7802_GAIN_4:
gain = 4;
break;
case NAU7802_GAIN_2:
gain = 2;
break;
case NAU7802_GAIN_1:
gain = 1;
break;
}
ESP_LOGCONFIG(TAG, " Gain: %dx", gain);
int sps = 0;
switch (this->sps_) {
case NAU7802_SPS_320:
sps = 320;
break;
case NAU7802_SPS_80:
sps = 80;
break;
case NAU7802_SPS_40:
sps = 40;
break;
case NAU7802_SPS_20:
sps = 20;
break;
case NAU7802_SPS_10:
sps = 10;
break;
}
ESP_LOGCONFIG(TAG, " Samples Per Second: %d", sps);
LOG_UPDATE_INTERVAL(this);
}
void NAU7802Sensor::write_value_(uint8_t start_reg, size_t size, int32_t value) {
uint8_t data[4];
for (int i = 0; i < size; i++) {
data[i] = 0xFF & (value >> (size - 1 - i) * 8);
}
this->write_register(start_reg, data, size);
}
int32_t NAU7802Sensor::read_value_(uint8_t start_reg, size_t size) {
uint8_t data[4];
this->read_register(start_reg, data, size);
int32_t result = 0;
for (int i = 0; i < size; i++) {
result |= data[i] << (size - 1 - i) * 8;
}
// extend sign bit
if (result & 0x800000 && size == 3) {
result |= 0xFF000000;
}
return result;
}
bool NAU7802Sensor::calibrate_(enum NAU7802CalibrationModes mode) {
// check if already calbrating
if (this->state_ != CalibrationState::INACTIVE) {
ESP_LOGW(TAG, "Calibration already in progress");
return false;
}
this->state_ = mode == NAU7802_CALIBRATE_GAIN ? CalibrationState::GAIN : CalibrationState::OFFSET;
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
// clear calibraye registers
ctrl2 &= CTRL2_CONFIG_MASK;
// Calibrate
ctrl2 |= mode;
ctrl2 |= CTRL2_CALS;
return true;
}
void NAU7802Sensor::set_calibration_failure_(bool failed) {
switch (this->state_) {
case CalibrationState::GAIN:
this->gain_calibration_failed_ = failed;
break;
case CalibrationState::OFFSET:
this->offset_calibration_failed_ = failed;
break;
case CalibrationState::INACTIVE:
// shouldn't happen
break;
}
}
void NAU7802Sensor::loop() {
i2c::I2CRegister ctrl2 = this->reg(CTRL2_REG);
if (this->state_ != CalibrationState::INACTIVE && !(ctrl2.get() & CTRL2_CALS)) {
if (ctrl2.get() & CTRL2_CAL_ERR) {
this->set_calibration_failure_(true);
this->state_ = CalibrationState::INACTIVE;
ESP_LOGE(TAG, "Failed to calibrate sensor");
this->status_set_error("Calibration Failed");
return;
}
this->set_calibration_failure_(false);
this->state_ = CalibrationState::INACTIVE;
if (!this->offset_calibration_failed_ && !this->gain_calibration_failed_)
this->status_clear_error();
int32_t ocal = this->read_value_(OCAL1_B2_REG, 3);
ESP_LOGI(TAG, "New Offset: %s", to_string(ocal).c_str());
uint32_t gcal = this->read_value_(GCAL1_B3_REG, 4);
float gcal_f = ((float) gcal / (float) (1 << GCAL1_FRACTIONAL));
ESP_LOGI(TAG, "New Gain: %f", gcal_f);
}
}
float NAU7802Sensor::get_setup_priority() const { return setup_priority::DATA; }
void NAU7802Sensor::update() {
if (!this->is_data_ready_()) {
ESP_LOGW(TAG, "No measurements ready!");
this->status_set_warning();
return;
}
this->status_clear_warning();
// Get the most recent sample to publish
int32_t result = this->read_value_(ADCO_B2_REG, 3);
ESP_LOGD(TAG, "'%s': Got value %" PRId32, this->name_.c_str(), result);
this->publish_state(result);
}
bool NAU7802Sensor::is_data_ready_() { return this->reg(PU_CTRL_REG).get() & PU_CTRL_CYCLE_READY; }
bool NAU7802Sensor::can_proceed() { return this->setup_complete_; }
} // namespace nau7802
} // namespace esphome

View file

@ -0,0 +1,121 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include <cinttypes>
namespace esphome {
namespace nau7802 {
enum NAU7802Gain {
NAU7802_GAIN_128 = 0b111,
NAU7802_GAIN_64 = 0b110,
NAU7802_GAIN_32 = 0b101,
NAU7802_GAIN_16 = 0b100,
NAU7802_GAIN_8 = 0b011,
NAU7802_GAIN_4 = 0b010,
NAU7802_GAIN_2 = 0b001,
NAU7802_GAIN_1 = 0b000,
};
enum NAU7802SPS {
NAU7802_SPS_320 = 0b111,
NAU7802_SPS_80 = 0b011,
NAU7802_SPS_40 = 0b010,
NAU7802_SPS_20 = 0b001,
NAU7802_SPS_10 = 0b000,
};
enum NAU7802LDO {
NAU7802_LDO_2V4 = 0b111,
NAU7802_LDO_2V7 = 0b110,
NAU7802_LDO_3V0 = 0b101,
NAU7802_LDO_3V3 = 0b100,
NAU7802_LDO_3V6 = 0b011,
NAU7802_LDO_3V9 = 0b010,
NAU7802_LDO_4V2 = 0b001,
NAU7802_LDO_4V5 = 0b000,
// Never write this to a register
NAU7802_LDO_EXTERNAL = 0b1000,
};
enum NAU7802CalibrationModes {
NAU7802_CALIBRATE_EXTERNAL_OFFSET = 0b10,
NAU7802_CALIBRATE_INTERNAL_OFFSET = 0b00,
NAU7802_CALIBRATE_GAIN = 0b11,
};
class NAU7802Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
public:
void set_samples_per_second(NAU7802SPS sps) { this->sps_ = sps; }
void set_ldo_voltage(NAU7802LDO ldo) { this->ldo_ = ldo; }
void set_gain(NAU7802Gain gain) { this->gain_ = gain; }
void set_gain_calibration(float gain_calibration) { this->gain_calibration_ = gain_calibration; }
void set_offset_calibration(int32_t offset_calibration) { this->offset_calibration_ = offset_calibration; }
bool calibrate_external_offset() { return this->calibrate_(NAU7802_CALIBRATE_EXTERNAL_OFFSET); }
bool calibrate_internal_offset() { return this->calibrate_(NAU7802_CALIBRATE_INTERNAL_OFFSET); }
bool calibrate_gain() { return this->calibrate_(NAU7802_CALIBRATE_GAIN); }
void setup() override;
void loop() override;
bool can_proceed() override;
void dump_config() override;
float get_setup_priority() const override;
void update() override;
protected:
//
// Internal state
//
enum class CalibrationState : uint8_t {
INACTIVE,
OFFSET,
GAIN,
} state_{CalibrationState::INACTIVE};
float gain_calibration_;
int32_t offset_calibration_;
bool offset_calibration_failed_ = false;
bool gain_calibration_failed_ = false;
bool setup_complete_ = false;
//
// Config values
//
NAU7802LDO ldo_;
NAU7802SPS sps_;
NAU7802Gain gain_;
//
// Internal Methods
//
bool calibrate_(enum NAU7802CalibrationModes mode);
void complete_setup_();
void write_value_(uint8_t start_reg, size_t size, int32_t value);
int32_t read_value_(uint8_t start_reg, size_t size);
bool is_data_ready_();
void set_calibration_failure_(bool failed);
};
template<typename... Ts>
class NAU7802CalbrateExternalOffsetAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
public:
void play(Ts... x) override { this->parent_->calibrate_external_offset(); }
};
template<typename... Ts>
class NAU7802CalbrateInternalOffsetAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
public:
void play(Ts... x) override { this->parent_->calibrate_internal_offset(); }
};
template<typename... Ts> class NAU7802CalbrateGainAction : public Action<Ts...>, public Parented<NAU7802Sensor> {
public:
void play(Ts... x) override { this->parent_->calibrate_gain(); }
};
} // namespace nau7802
} // namespace esphome

View file

@ -0,0 +1,134 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_GAIN, CONF_ID, ICON_SCALE, STATE_CLASS_MEASUREMENT
CODEOWNERS = ["@cujomalainey"]
DEPENDENCIES = ["i2c"]
CONF_GAIN_CALIBRATION = "gain_calibration"
CONF_OFFSET_CALIBRATION = "offset_calibration"
CONF_LDO_VOLTAGE = "ldo_voltage"
CONF_SAMPLES_PER_SECOND = "samples_per_second"
nau7802_ns = cg.esphome_ns.namespace("nau7802")
NAU7802Sensor = nau7802_ns.class_(
"NAU7802Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
NAU7802CalbrateExternalOffsetAction = nau7802_ns.class_(
"NAU7802CalbrateExternalOffsetAction",
automation.Action,
cg.Parented.template(NAU7802Sensor),
)
NAU7802CalbrateInternalOffsetAction = nau7802_ns.class_(
"NAU7802CalbrateInternalOffsetAction",
automation.Action,
cg.Parented.template(NAU7802Sensor),
)
NAU7802CalbrateGainAction = nau7802_ns.class_(
"NAU7802CalbrateGainAction", automation.Action, cg.Parented.template(NAU7802Sensor)
)
NAU7802Gain = nau7802_ns.enum("NAU7802Gain")
GAINS = {
128: NAU7802Gain.NAU7802_GAIN_128,
64: NAU7802Gain.NAU7802_GAIN_64,
32: NAU7802Gain.NAU7802_GAIN_32,
16: NAU7802Gain.NAU7802_GAIN_16,
8: NAU7802Gain.NAU7802_GAIN_8,
4: NAU7802Gain.NAU7802_GAIN_4,
2: NAU7802Gain.NAU7802_GAIN_2,
1: NAU7802Gain.NAU7802_GAIN_1,
}
NAU7802SPS = nau7802_ns.enum("NAU7802SPS")
SAMPLES_PER_SECOND = {
320: NAU7802SPS.NAU7802_SPS_320,
80: NAU7802SPS.NAU7802_SPS_80,
40: NAU7802SPS.NAU7802_SPS_40,
20: NAU7802SPS.NAU7802_SPS_20,
10: NAU7802SPS.NAU7802_SPS_10,
}
NAU7802LDO = nau7802_ns.enum("NAU7802LDO")
LDO = {
"2.4V": NAU7802LDO.NAU7802_LDO_2V4,
"2.7V": NAU7802LDO.NAU7802_LDO_2V7,
"3.0V": NAU7802LDO.NAU7802_LDO_3V0,
"3.3V": NAU7802LDO.NAU7802_LDO_3V3,
"3.6V": NAU7802LDO.NAU7802_LDO_3V6,
"3.9V": NAU7802LDO.NAU7802_LDO_3V9,
"4.2V": NAU7802LDO.NAU7802_LDO_4V2,
"4.5V": NAU7802LDO.NAU7802_LDO_4V5,
"EXTERNAL": NAU7802LDO.NAU7802_LDO_EXTERNAL,
"EXT": NAU7802LDO.NAU7802_LDO_EXTERNAL,
}
CONFIG_SCHEMA = (
sensor.sensor_schema(
NAU7802Sensor,
icon=ICON_SCALE,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(
{
cv.Optional(CONF_LDO_VOLTAGE, default="3.0V"): cv.enum(LDO, upper=True),
cv.Optional(CONF_SAMPLES_PER_SECOND, default=10): cv.enum(
SAMPLES_PER_SECOND, int=True
),
cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True),
cv.Optional(CONF_OFFSET_CALIBRATION, default=0): cv.int_range(
min=-8388608, max=8388607
),
cv.Optional(CONF_GAIN_CALIBRATION, default=1.0): cv.float_range(
min=0, max=511.9999998807907
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x2A))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
await sensor.register_sensor(var, config)
cg.add(var.set_samples_per_second(config[CONF_SAMPLES_PER_SECOND]))
cg.add(var.set_ldo_voltage(config[CONF_LDO_VOLTAGE]))
cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(var.set_gain_calibration(config[CONF_GAIN_CALIBRATION]))
cg.add(var.set_offset_calibration(config[CONF_OFFSET_CALIBRATION]))
NAU7802_CALIBRATE_SCHEMA = maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.use_id(NAU7802Sensor),
}
)
@automation.register_action(
"nau7802.calibrate_internal_offset",
NAU7802CalbrateInternalOffsetAction,
NAU7802_CALIBRATE_SCHEMA,
)
@automation.register_action(
"nau7802.calibrate_external_offset",
NAU7802CalbrateExternalOffsetAction,
NAU7802_CALIBRATE_SCHEMA,
)
@automation.register_action(
"nau7802.calibrate_gain",
NAU7802CalbrateGainAction,
NAU7802_CALIBRATE_SCHEMA,
)
async def nau7802_calibrate_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var

View file

View file

@ -0,0 +1,111 @@
#include "npi19.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace npi19 {
static const char *const TAG = "npi19";
static const uint8_t READ_COMMAND = 0xAC;
void NPI19Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up NPI19...");
uint16_t raw_temperature(0);
uint16_t raw_pressure(0);
i2c::ErrorCode err = this->read_(raw_temperature, raw_pressure);
if (err != i2c::ERROR_OK) {
ESP_LOGCONFIG(TAG, " I2C Communication Failed...");
this->mark_failed();
return;
}
ESP_LOGCONFIG(TAG, " Success...");
}
void NPI19Component::dump_config() {
ESP_LOGCONFIG(TAG, "NPI19:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Raw Pressure", this->raw_pressure_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
float NPI19Component::get_setup_priority() const { return setup_priority::DATA; }
i2c::ErrorCode NPI19Component::read_(uint16_t &raw_temperature, uint16_t &raw_pressure) {
// initiate data read from device
i2c::ErrorCode w_err = write(&READ_COMMAND, sizeof(READ_COMMAND), true);
if (w_err != i2c::ERROR_OK) {
return w_err;
}
// read 4 bytes from senesor
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
i2c::ErrorCode r_err = this->read(response, 4);
if (r_err != i2c::ERROR_OK) {
return r_err;
}
// extract top 6 bits of first byte and all bits of second byte for pressure
raw_pressure = ((response[0] & 0x3F) << 8) | response[1];
// extract all bytes of 3rd byte and top 3 bits of fourth byte for temperature
raw_temperature = (response[2] << 3) | ((response[3] & 0xE0) >> 5);
return i2c::ERROR_OK;
}
inline float convert_temperature(uint16_t raw_temperature) {
/*
* Correspondance with Amphenol confirmed the appropriate equation for computing temperature is:
* T (°C) =(((((Th*8)+Tl)/2048)*200)-50), where Th is the high (third) byte and Tl is the low (fourth) byte.
*
* Tl is actually the upper 3 bits of the fourth data byte; the first 5 (LSBs) must be masked out.
*
*
* The NPI-19 I2C has a temperature output, however the manufacturer does
* not specify its accuracy on the published datasheet. They indicate
* that the sensor should not be used as a calibrated temperature
* reading; its only intended for curve fitting data during
* compensation.
*/
const float temperature_bits_span = 2048;
const float temperature_max = 150;
const float temperature_min = -50;
const float temperature_span = temperature_max - temperature_min;
float temperature = (raw_temperature * temperature_span / temperature_bits_span) + temperature_min;
return temperature;
}
void NPI19Component::update() {
uint16_t raw_temperature(0);
uint16_t raw_pressure(0);
i2c::ErrorCode err = this->read_(raw_temperature, raw_pressure);
if (err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "I2C Communication Failed");
this->status_set_warning();
return;
}
float temperature = convert_temperature(raw_temperature);
ESP_LOGD(TAG, "Got raw pressure=%d, temperature=%.1f°C", raw_pressure, temperature);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->raw_pressure_sensor_ != nullptr)
this->raw_pressure_sensor_->publish_state(raw_pressure);
this->status_clear_warning();
}
} // namespace npi19
} // namespace esphome

View file

@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace npi19 {
/// This class implements support for the npi19 pressure and temperature i2c sensors.
class NPI19Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_raw_pressure_sensor(sensor::Sensor *raw_pressure_sensor) {
this->raw_pressure_sensor_ = raw_pressure_sensor;
}
float get_setup_priority() const override;
void setup() override;
void dump_config() override;
void update() override;
protected:
i2c::ErrorCode read_(uint16_t &raw_temperature, uint16_t &raw_pressure);
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *raw_pressure_sensor_{nullptr};
};
} // namespace npi19
} // namespace esphome

View file

@ -0,0 +1,52 @@
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
CODEOWNERS = ["@bakerkj"]
DEPENDENCIES = ["i2c"]
npi19_ns = cg.esphome_ns.namespace("npi19")
NPI19Component = npi19_ns.class_("NPI19Component", cg.PollingComponent, i2c.I2CDevice)
CONF_RAW_PRESSURE = "raw_pressure"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(NPI19Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RAW_PRESSURE): sensor.sensor_schema(
accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x28))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if raw_pressure_config := config.get(CONF_RAW_PRESSURE):
sens = await sensor.new_sensor(raw_pressure_config)
cg.add(var.set_raw_pressure_sensor(sens))

View file

@ -18,7 +18,7 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE, CONF_VALUE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI, DEVICE_CLASS_AQI,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE, DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
@ -254,10 +254,8 @@ async def setup_number_core_(
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None: if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: await web_server.add_entity_config(var, web_server_config)
web_server_ = await cg.get_variable(webserver_id)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_number( async def register_number(

View file

@ -125,7 +125,7 @@ async def online_image_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_URL in config: if CONF_URL in config:
template_ = await cg.templatable(config[CONF_URL], args, cg.const_char_ptr) template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
cg.add(var.set_url(template_)) cg.add(var.set_url(template_))
return var return var

View file

@ -157,7 +157,7 @@ class OnlineImage : public PollingComponent,
template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> { template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
public: public:
OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {} OnlineImageSetUrlAction(OnlineImage *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(const char *, url) TEMPLATABLE_VALUE(std::string, url)
void play(Ts... x) override { void play(Ts... x) override {
this->parent_->set_url(this->url_.value(x...)); this->parent_->set_url(this->url_.value(x...));
this->parent_->update(); this->parent_->update();
@ -170,7 +170,6 @@ template<typename... Ts> class OnlineImageSetUrlAction : public Action<Ts...> {
template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> { template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> {
public: public:
OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {} OnlineImageReleaseAction(OnlineImage *parent) : parent_(parent) {}
TEMPLATABLE_VALUE(const char *, url)
void play(Ts... x) override { this->parent_->release(); } void play(Ts... x) override { this->parent_->release(); }
protected: protected:

View file

@ -1,5 +1,7 @@
#include "radon_eye_listener.h" #include "radon_eye_listener.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <algorithm>
#include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -10,9 +12,14 @@ static const char *const TAG = "radon_eye_ble";
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (not device.get_name().empty()) { if (not device.get_name().empty()) {
if (device.get_name().rfind("FR:R", 0) == 0) { // Vector containing the prefixes to search for
// This is an RD200, I think std::vector<std::string> prefixes = {"FR:R", "FR:I", "FR:H"};
ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
// Check if the device name starts with any of the prefixes
if (std::any_of(prefixes.begin(), prefixes.end(),
[&](const std::string &prefix) { return device.get_name().rfind(prefix, 0) == 0; })) {
// Device found
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
device.address_str().c_str()); device.address_str().c_str());
} }
} }

View file

@ -14,7 +14,7 @@ from esphome.const import (
CONF_OPERATION, CONF_OPERATION,
CONF_OPTION, CONF_OPTION,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
@ -104,9 +104,8 @@ async def setup_select_core_(var, config, *, options: list[str]):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_select(var, config, *, options: list[str]): async def register_select(var, config, *, options: list[str]):

View file

@ -36,7 +36,7 @@ from esphome.const import (
CONF_TYPE, CONF_TYPE,
CONF_UNIT_OF_MEASUREMENT, CONF_UNIT_OF_MEASUREMENT,
CONF_VALUE, CONF_VALUE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
CONF_WINDOW_SIZE, CONF_WINDOW_SIZE,
DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_APPARENT_POWER,
DEVICE_CLASS_AQI, DEVICE_CLASS_AQI,
@ -800,9 +800,8 @@ async def setup_sensor_core_(var, config):
else: else:
cg.add(mqtt_.set_expire_after(expire_after)) cg.add(mqtt_.set_expire_after(expire_after))
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_sensor(var, config): async def register_sensor(var, config):

View file

@ -64,46 +64,46 @@ uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len) {
return std::accumulate<decltype(buf), uint16_t>(buf, buf + len, 0); return std::accumulate<decltype(buf), uint16_t>(buf, buf + len, 0);
} }
void ShellyDimmer::setup() { bool ShellyDimmer::is_running_configured_version() const {
this->pin_nrst_->setup(); return this->version_major_ == USE_SHD_FIRMWARE_MAJOR_VERSION &&
this->pin_boot0_->setup(); this->version_minor_ == USE_SHD_FIRMWARE_MINOR_VERSION;
}
ESP_LOGI(TAG, "Initializing Shelly Dimmer...");
void ShellyDimmer::handle_firmware() {
// Reset the STM32 and check the firmware version. // Reset the STM32 and check the firmware version.
for (int i = 0; i < 2; i++) {
this->reset_normal_boot_(); this->reset_normal_boot_();
this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0); this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0);
ESP_LOGI(TAG, "STM32 current firmware version: %d.%d, desired version: %d.%d", this->version_major_, ESP_LOGI(TAG, "STM32 current firmware version: %d.%d, desired version: %d.%d", this->version_major_,
this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION); this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION);
if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION ||
this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) {
#ifdef USE_SHD_FIRMWARE_DATA
// Update firmware if needed.
ESP_LOGW(TAG, "Unsupported STM32 firmware version, flashing");
if (i > 0) {
// Upgrade was already performed but the reported version is still not right.
ESP_LOGE(TAG, "STM32 firmware upgrade already performed, but version is still incorrect");
this->mark_failed();
return;
}
if (!is_running_configured_version()) {
#ifdef USE_SHD_FIRMWARE_DATA
if (!this->upgrade_firmware_()) { if (!this->upgrade_firmware_()) {
ESP_LOGW(TAG, "Failed to upgrade firmware"); ESP_LOGW(TAG, "Failed to upgrade firmware");
this->mark_failed(); this->mark_failed();
return; return;
} }
// Firmware upgrade completed, do the checks again. this->reset_normal_boot_();
continue; this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0);
#else if (!is_running_configured_version()) {
ESP_LOGW(TAG, "Firmware version mismatch, put 'update: true' in the yaml to flash an update."); ESP_LOGE(TAG, "STM32 firmware upgrade already performed, but version is still incorrect");
this->mark_failed(); this->mark_failed();
return; return;
}
#else
ESP_LOGW(TAG, "Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
#endif #endif
} }
break; }
}
void ShellyDimmer::setup() {
this->pin_nrst_->setup();
this->pin_boot0_->setup();
ESP_LOGI(TAG, "Initializing Shelly Dimmer...");
this->handle_firmware();
this->send_settings_(); this->send_settings_();
// Do an immediate poll to refresh current state. // Do an immediate poll to refresh current state.

View file

@ -20,6 +20,8 @@ class ShellyDimmer : public PollingComponent, public light::LightOutput, public
public: public:
float get_setup_priority() const override { return setup_priority::LATE; } float get_setup_priority() const override { return setup_priority::LATE; }
bool is_running_configured_version() const;
void handle_firmware();
void setup() override; void setup() override;
void update() override; void update() override;
void dump_config() override; void dump_config() override;

View file

@ -14,7 +14,7 @@ from esphome.const import (
CONF_ON_TURN_ON, CONF_ON_TURN_ON,
CONF_RESTORE_MODE, CONF_RESTORE_MODE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_OUTLET, DEVICE_CLASS_OUTLET,
DEVICE_CLASS_SWITCH, DEVICE_CLASS_SWITCH,
@ -156,9 +156,8 @@ async def setup_switch_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class)) cg.add(var.set_device_class(device_class))

View file

View file

@ -0,0 +1,55 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
)
CODEOWNERS = ["@bakerkj"]
DEPENDENCIES = ["i2c"]
tem3200_ns = cg.esphome_ns.namespace("tem3200")
TEM3200Component = tem3200_ns.class_(
"TEM3200Component", cg.PollingComponent, i2c.I2CDevice
)
CONF_RAW_PRESSURE = "raw_pressure"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TEM3200Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_RAW_PRESSURE): sensor.sensor_schema(
accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x28))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if raw_pressure_config := config.get(CONF_RAW_PRESSURE):
sens = await sensor.new_sensor(raw_pressure_config)
cg.add(var.set_raw_pressure_sensor(sens))

View file

@ -0,0 +1,151 @@
#include "tem3200.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace tem3200 {
static const char *const TAG = "tem3200";
enum ErrorCode {
NONE = 0,
RESERVED = 1,
STALE = 2,
FAULT = 3,
};
void TEM3200Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up TEM3200...");
uint8_t status(NONE);
uint16_t raw_temperature(0);
uint16_t raw_pressure(0);
i2c::ErrorCode err = this->read_(status, raw_temperature, raw_pressure);
if (err != i2c::ERROR_OK) {
ESP_LOGCONFIG(TAG, " I2C Communication Failed...");
this->mark_failed();
return;
}
switch (status) {
case RESERVED:
ESP_LOGE(TAG, "Invalid RESERVED Device Status");
this->mark_failed();
return;
case FAULT:
ESP_LOGE(TAG, "FAULT condition in the SSC or sensing element");
this->mark_failed();
return;
case STALE:
ESP_LOGE(TAG, "STALE data. Data has not been updated since last fetch");
this->status_set_warning();
break;
}
ESP_LOGCONFIG(TAG, " Success...");
}
void TEM3200Component::dump_config() {
ESP_LOGCONFIG(TAG, "TEM3200:");
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
LOG_SENSOR(" ", "Raw Pressure", this->raw_pressure_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
float TEM3200Component::get_setup_priority() const { return setup_priority::DATA; }
i2c::ErrorCode TEM3200Component::read_(uint8_t &status, uint16_t &raw_temperature, uint16_t &raw_pressure) {
uint8_t response[4] = {0x00, 0x00, 0x00, 0x00};
// initiate data read
i2c::ErrorCode err = this->read(response, 4);
if (err != i2c::ERROR_OK) {
return err;
}
// extract top 2 bits of first byte for status
status = (ErrorCode) (response[0] & 0xc0) >> 6;
if (status == RESERVED || status == FAULT) {
return i2c::ERROR_OK;
}
// if data is stale; reread
if (status == STALE) {
// wait for measurement 2ms
delay(2);
err = this->read(response, 4);
if (err != i2c::ERROR_OK) {
return err;
}
}
// extract top 2 bits of first byte for status
status = (ErrorCode) (response[0] & 0xc0) >> 6;
if (status == RESERVED || status == FAULT) {
return i2c::ERROR_OK;
}
// extract top 6 bits of first byte and all bits of second byte for pressure
raw_pressure = (((response[0] & 0x3f)) << 8 | response[1]);
// extract all bytes of 3rd byte and top 3 bits of fourth byte for temperature
raw_temperature = ((response[2] << 3) | (response[3] & 0xe0) >> 5);
return i2c::ERROR_OK;
}
inline float convert_temperature(uint16_t raw_temperature) {
const float temperature_bits_span = 2048;
const float temperature_max = 150;
const float temperature_min = -50;
const float temperature_span = temperature_max - temperature_min;
float temperature = (raw_temperature * temperature_span / temperature_bits_span) + temperature_min;
return temperature;
}
void TEM3200Component::update() {
uint8_t status(NONE);
uint16_t raw_temperature(0);
uint16_t raw_pressure(0);
i2c::ErrorCode err = this->read_(status, raw_temperature, raw_pressure);
if (err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "I2C Communication Failed");
this->status_set_warning();
return;
}
switch (status) {
case RESERVED:
ESP_LOGE(TAG, "Failed: Device return RESERVED status");
this->status_set_warning();
return;
case FAULT:
ESP_LOGE(TAG, "Failed: FAULT condition in the SSC or sensing element");
this->mark_failed();
return;
case STALE:
ESP_LOGE(TAG, "Warning: STALE data. Data has not been updated since last fetch");
this->status_set_warning();
return;
}
float temperature = convert_temperature(raw_temperature);
ESP_LOGD(TAG, "Got raw pressure=%d, temperature=%.1f°C", raw_pressure, temperature);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->raw_pressure_sensor_ != nullptr)
this->raw_pressure_sensor_->publish_state(raw_pressure);
this->status_clear_warning();
}
} // namespace tem3200
} // namespace esphome

View file

@ -0,0 +1,30 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace tem3200 {
/// This class implements support for the tem3200 pressure and temperature i2c sensors.
class TEM3200Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_raw_pressure_sensor(sensor::Sensor *raw_pressure_sensor) {
this->raw_pressure_sensor_ = raw_pressure_sensor;
}
float get_setup_priority() const override;
void setup() override;
void dump_config() override;
void update() override;
protected:
i2c::ErrorCode read_(uint8_t &status, uint16_t &raw_temperature, uint16_t &raw_pressure);
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *raw_pressure_sensor_{nullptr};
};
} // namespace tem3200
} // namespace esphome

View file

@ -1,8 +1,10 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor from esphome.components import binary_sensor
from esphome.const import CONF_ID, CONF_LAMBDA, CONF_STATE import esphome.config_validation as cv
from esphome.const import CONF_CONDITION, CONF_ID, CONF_LAMBDA, CONF_STATE
from esphome.cpp_generator import LambdaExpression
from .. import template_ns from .. import template_ns
TemplateBinarySensor = template_ns.class_( TemplateBinarySensor = template_ns.class_(
@ -13,7 +15,10 @@ CONFIG_SCHEMA = (
binary_sensor.binary_sensor_schema(TemplateBinarySensor) binary_sensor.binary_sensor_schema(TemplateBinarySensor)
.extend( .extend(
{ {
cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Exclusive(CONF_LAMBDA, CONF_CONDITION): cv.returning_lambda,
cv.Exclusive(
CONF_CONDITION, CONF_CONDITION
): automation.validate_potentially_and_condition,
} }
) )
.extend(cv.COMPONENT_SCHEMA) .extend(cv.COMPONENT_SCHEMA)
@ -24,9 +29,17 @@ async def to_code(config):
var = await binary_sensor.new_binary_sensor(config) var = await binary_sensor.new_binary_sensor(config)
await cg.register_component(var, config) await cg.register_component(var, config)
if CONF_LAMBDA in config: if lamb := config.get(CONF_LAMBDA):
template_ = await cg.process_lambda( template_ = await cg.process_lambda(
config[CONF_LAMBDA], [], return_type=cg.optional.template(bool) lamb, [], return_type=cg.optional.template(bool)
)
cg.add(var.set_template(template_))
if condition := config.get(CONF_CONDITION):
condition = await automation.build_condition(
condition, cg.TemplateArguments(), []
)
template_ = LambdaExpression(
f"return {condition.check()};", [], return_type=cg.optional.template(bool)
) )
cg.add(var.set_template(template_)) cg.add(var.set_template(template_))

View file

@ -11,7 +11,7 @@ from esphome.const import (
CONF_ON_VALUE, CONF_ON_VALUE,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VALUE, CONF_VALUE,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
@ -82,9 +82,8 @@ async def setup_text_core_(
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text( async def register_text(

View file

@ -15,7 +15,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_TO, CONF_TO,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_DATE, DEVICE_CLASS_DATE,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_TIMESTAMP, DEVICE_CLASS_TIMESTAMP,
@ -212,9 +212,8 @@ async def setup_text_sensor_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_text_sensor(var, config): async def register_text_sensor(var, config):

View file

@ -18,8 +18,8 @@ void Touchscreen::attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::Int
void Touchscreen::call_setup() { void Touchscreen::call_setup() {
if (this->display_ != nullptr) { if (this->display_ != nullptr) {
this->display_width_ = this->display_->get_native_width(); this->display_width_ = this->display_->get_width();
this->display_height_ = this->display_->get_native_height(); this->display_height_ = this->display_->get_height();
} }
PollingComponent::call_setup(); PollingComponent::call_setup();
} }

View file

@ -8,7 +8,7 @@ from esphome.const import (
CONF_FORCE_UPDATE, CONF_FORCE_UPDATE,
CONF_ID, CONF_ID,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_FIRMWARE, DEVICE_CLASS_FIRMWARE,
ENTITY_CATEGORY_CONFIG, ENTITY_CATEGORY_CONFIG,
@ -73,9 +73,8 @@ async def setup_update_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id_config, var) mqtt_ = cg.new_Pvariable(mqtt_id_config, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)
if web_server_id_config := config.get(CONF_WEB_SERVER_ID): if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(web_server_id_config) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_update(var, config): async def register_update(var, config):

View file

@ -14,7 +14,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_STOP, CONF_STOP,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY, DEVICE_CLASS_EMPTY,
DEVICE_CLASS_GAS, DEVICE_CLASS_GAS,
DEVICE_CLASS_WATER, DEVICE_CLASS_WATER,
@ -124,9 +124,8 @@ async def setup_valve_core_(var, config):
mqtt_.set_custom_position_command_topic(position_command_topic_config) mqtt_.set_custom_position_command_topic(position_command_topic_config)
) )
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None: if web_server_config := config.get(CONF_WEB_SERVER):
web_server_ = await cg.get_variable(webserver_id) await web_server.add_entity_config(var, web_server_config)
web_server.add_entity_to_sorting_list(web_server_, var, config)
async def register_valve(var, config): async def register_valve(var, config):

View file

@ -17,13 +17,14 @@ from esphome.const import (
CONF_JS_URL, CONF_JS_URL,
CONF_LOCAL, CONF_LOCAL,
CONF_LOG, CONF_LOG,
CONF_NAME,
CONF_OTA, CONF_OTA,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PORT, CONF_PORT,
CONF_USERNAME, CONF_USERNAME,
CONF_VERSION, CONF_VERSION,
CONF_WEB_SERVER,
CONF_WEB_SERVER_ID, CONF_WEB_SERVER_ID,
CONF_WEB_SERVER_SORTING_WEIGHT,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
@ -34,9 +35,15 @@ import esphome.final_validate as fv
AUTO_LOAD = ["json", "web_server_base"] AUTO_LOAD = ["json", "web_server_base"]
CONF_SORTING_GROUP_ID = "sorting_group_id"
CONF_SORTING_GROUPS = "sorting_groups"
CONF_SORTING_WEIGHT = "sorting_weight"
web_server_ns = cg.esphome_ns.namespace("web_server") web_server_ns = cg.esphome_ns.namespace("web_server")
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
sorting_groups = {}
def default_url(config): def default_url(config):
config = config.copy() config = config.copy()
@ -70,42 +77,74 @@ def validate_ota(config):
return config return config
def _validate_no_sorting_weight( def validate_sorting_groups(config):
webserver_version: int, config: dict, path: list[str] | None = None if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3:
) -> None: raise cv.Invalid(
if path is None: f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3"
path = []
if CONF_WEB_SERVER_SORTING_WEIGHT in config:
raise cv.FinalExternalInvalid(
f"Sorting weight on entities is not supported in web_server version {webserver_version}",
path=path + [CONF_WEB_SERVER_SORTING_WEIGHT],
) )
for p, value in config.items():
if isinstance(value, dict):
_validate_no_sorting_weight(webserver_version, value, path + [p])
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
_validate_no_sorting_weight(webserver_version, item, path + [p, i])
def _final_validate_sorting_weight(config):
if (webserver_version := config.get(CONF_VERSION)) != 3:
_validate_no_sorting_weight(webserver_version, fv.full_config.get())
return config return config
FINAL_VALIDATE_SCHEMA = _final_validate_sorting_weight def _validate_no_sorting_component(
sorting_component: str,
webserver_version: int,
config: dict,
path: list[str] | None = None,
) -> None:
if path is None:
path = []
if CONF_WEB_SERVER in config and sorting_component in config[CONF_WEB_SERVER]:
raise cv.FinalExternalInvalid(
f"{sorting_component} on entities is not supported in web_server version {webserver_version}",
path=path + [sorting_component],
)
for p, value in config.items():
if isinstance(value, dict):
_validate_no_sorting_component(
sorting_component, webserver_version, value, path + [p]
)
elif isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, dict):
_validate_no_sorting_component(
sorting_component, webserver_version, item, path + [p, i]
)
def _final_validate_sorting(config):
if (webserver_version := config.get(CONF_VERSION)) != 3:
_validate_no_sorting_component(
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
)
_validate_no_sorting_component(
CONF_SORTING_GROUP_ID, webserver_version, fv.full_config.get()
)
return config
FINAL_VALIDATE_SCHEMA = _final_validate_sorting
sorting_group = {
cv.Required(CONF_ID): cv.declare_id(cg.int_),
cv.Required(CONF_NAME): cv.string,
cv.Optional(CONF_SORTING_WEIGHT): cv.float_,
}
WEBSERVER_SORTING_SCHEMA = cv.Schema( WEBSERVER_SORTING_SCHEMA = cv.Schema(
{
cv.Optional(CONF_WEB_SERVER): cv.Schema(
{ {
cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer), cv.OnlyWith(CONF_WEB_SERVER_ID, "web_server"): cv.use_id(WebServer),
cv.Optional(CONF_WEB_SERVER_SORTING_WEIGHT): cv.All( cv.Optional(CONF_SORTING_WEIGHT): cv.All(
cv.requires_component("web_server"), cv.requires_component("web_server"),
cv.float_, cv.float_,
), ),
cv.Optional(CONF_SORTING_GROUP_ID): cv.All(
cv.requires_component("web_server"),
cv.use_id(cg.int_),
),
}
)
} }
) )
@ -145,24 +184,38 @@ CONFIG_SCHEMA = cv.All(
): cv.boolean, ): cv.boolean,
cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean,
cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean,
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]),
default_url, default_url,
validate_local, validate_local,
validate_ota, validate_ota,
validate_sorting_groups,
) )
def add_entity_to_sorting_list(web_server, entity, config): def add_sorting_groups(web_server_var, config):
sorting_weight = 50 for group in config:
if CONF_WEB_SERVER_SORTING_WEIGHT in config: sorting_groups[group[CONF_ID]] = group[CONF_NAME]
sorting_weight = config[CONF_WEB_SERVER_SORTING_WEIGHT] group_sorting_weight = group.get(CONF_SORTING_WEIGHT, 50)
cg.add(
web_server_var.add_sorting_group(
hash(group[CONF_ID]), group[CONF_NAME], group_sorting_weight
)
)
async def add_entity_config(entity, config):
web_server = await cg.get_variable(config[CONF_WEB_SERVER_ID])
sorting_weight = config.get(CONF_SORTING_WEIGHT, 50)
sorting_group_hash = hash(config.get(CONF_SORTING_GROUP_ID))
cg.add( cg.add(
web_server.add_entity_to_sorting_list( web_server.add_entity_config(
entity, entity,
sorting_weight, sorting_weight,
sorting_group_hash,
) )
) )
@ -241,3 +294,6 @@ async def to_code(config):
cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
if CONF_LOCAL in config and config[CONF_LOCAL]: if CONF_LOCAL in config and config[CONF_LOCAL]:
cg.add_define("USE_WEBSERVER_LOCAL") cg.add_define("USE_WEBSERVER_LOCAL")
if (sorting_group_config := config.get(CONF_SORTING_GROUPS)) is not None:
add_sorting_groups(var, sorting_group_config)

File diff suppressed because it is too large Load diff

View file

@ -105,6 +105,14 @@ void WebServer::setup() {
// Configure reconnect timeout and send config // Configure reconnect timeout and send config
client->send(this->get_config_json().c_str(), "ping", millis(), 30000); client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
for (auto &group : this->sorting_groups_) {
client->send(json::build_json([group](JsonObject root) {
root["name"] = group.second.name;
root["sorting_weight"] = group.second.weight;
}).c_str(),
"sorting_group");
}
this->entities_iterator_.begin(this->include_internal_); this->entities_iterator_.begin(this->include_internal_);
}); });
@ -246,6 +254,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
if (!obj->get_unit_of_measurement().empty()) if (!obj->get_unit_of_measurement().empty())
root["uom"] = obj->get_unit_of_measurement(); root["uom"] = obj->get_unit_of_measurement();
@ -284,6 +295,9 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -332,6 +346,9 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
root["assumed_state"] = obj->assumed_state(); root["assumed_state"] = obj->assumed_state();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -368,6 +385,9 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -404,6 +424,9 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -487,6 +510,9 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -603,6 +629,9 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
} }
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -684,6 +713,9 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -745,6 +777,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
root["uom"] = obj->traits.get_unit_of_measurement(); root["uom"] = obj->traits.get_unit_of_measurement();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
if (std::isnan(value)) { if (std::isnan(value)) {
@ -814,6 +849,9 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -872,6 +910,9 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -931,6 +972,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -992,6 +1036,9 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
root["mode"] = (int) obj->traits.get_mode(); root["mode"] = (int) obj->traits.get_mode();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -1048,6 +1095,9 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
} }
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -1164,6 +1214,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
} }
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
@ -1257,6 +1310,9 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -1326,8 +1382,13 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
if (obj->get_traits().get_supports_position()) if (obj->get_traits().get_supports_position())
root["position"] = obj->position; root["position"] = obj->position;
if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
}
} }
}); });
} }
@ -1367,6 +1428,9 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -1377,9 +1441,26 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
void WebServer::on_event(event::Event *obj, const std::string &event_type) { void WebServer::on_event(event::Event *obj, const std::string &event_type) {
this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state"); this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (event::Event *obj : App.get_events()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET && match.method.empty()) {
auto detail = DETAIL_STATE;
auto *param = request->getParam("detail");
if (param && param->value() == "all") {
detail = DETAIL_ALL;
}
std::string data = this->event_json(obj, "", detail);
request->send(200, "application/json", data.c_str());
return;
}
}
request->send(404);
}
std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) { std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
return json::build_json([obj, event_type, start_config](JsonObject root) { return json::build_json([this, obj, event_type, start_config](JsonObject root) {
set_json_id(root, obj, "event-" + obj->get_object_id(), start_config); set_json_id(root, obj, "event-" + obj->get_object_id(), start_config);
if (!event_type.empty()) { if (!event_type.empty()) {
root["event_type"] = event_type; root["event_type"] = event_type;
@ -1390,6 +1471,12 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
event_types.add(event_type); event_types.add(event_type);
} }
root["device_class"] = obj->get_device_class(); root["device_class"] = obj->get_device_class();
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
}
} }
}); });
} }
@ -1453,6 +1540,9 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
root["release_url"] = obj->update_info.release_url; root["release_url"] = obj->update_info.release_url;
if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) { if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
root["sorting_weight"] = this->sorting_entitys_[obj].weight; root["sorting_weight"] = this->sorting_entitys_[obj].weight;
if (this->sorting_groups_.find(this->sorting_entitys_[obj].group_id) != this->sorting_groups_.end()) {
root["sorting_group"] = this->sorting_groups_[this->sorting_entitys_[obj].group_id].name;
}
} }
} }
}); });
@ -1578,6 +1668,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true; return true;
#endif #endif
#ifdef USE_EVENT
if (request->method() == HTTP_GET && match.domain == "event")
return true;
#endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update")
return true; return true;
@ -1751,8 +1846,12 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
bool WebServer::isRequestHandlerTrivial() { return false; } bool WebServer::isRequestHandlerTrivial() { return false; }
void WebServer::add_entity_to_sorting_list(EntityBase *entity, float weight) { void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) {
this->sorting_entitys_[entity] = SortingComponents{weight}; this->sorting_entitys_[entity] = SortingComponents{weight, group};
}
void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) {
this->sorting_groups_[group_id] = SortingGroup{group_name, weight};
} }
void WebServer::schedule_(std::function<void()> &&f) { void WebServer::schedule_(std::function<void()> &&f) {

View file

@ -44,6 +44,12 @@ struct UrlMatch {
struct SortingComponents { struct SortingComponents {
float weight; float weight;
uint64_t group_id;
};
struct SortingGroup {
std::string name;
float weight;
}; };
enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
@ -316,6 +322,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
#ifdef USE_EVENT #ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override; void on_event(event::Event *obj, const std::string &event_type) override;
/// Handle a event request under '/event<id>'.
void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the event details with its value as a JSON string. /// Dump the event details with its value as a JSON string.
std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config); std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config);
#endif #endif
@ -337,7 +346,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// This web handle is not trivial. /// This web handle is not trivial.
bool isRequestHandlerTrivial() override; // NOLINT(readability-identifier-naming) bool isRequestHandlerTrivial() override; // NOLINT(readability-identifier-naming)
void add_entity_to_sorting_list(EntityBase *entity, float weight); void add_entity_config(EntityBase *entity, float weight, uint64_t group);
void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight);
protected: protected:
void schedule_(std::function<void()> &&f); void schedule_(std::function<void()> &&f);
@ -346,6 +356,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
AsyncEventSource events_{"/events"}; AsyncEventSource events_{"/events"};
ListEntitiesIterator entities_iterator_; ListEntitiesIterator entities_iterator_;
std::map<EntityBase *, SortingComponents> sorting_entitys_; std::map<EntityBase *, SortingComponents> sorting_entitys_;
std::map<uint64_t, SortingGroup> sorting_groups_;
#if USE_WEBSERVER_VERSION == 1 #if USE_WEBSERVER_VERSION == 1
const char *css_url_{nullptr}; const char *css_url_{nullptr};
const char *js_url_{nullptr}; const char *js_url_{nullptr};

View file

@ -1,6 +1,6 @@
"""Constants used by esphome.""" """Constants used by esphome."""
__version__ = "2024.10.0-dev" __version__ = "2024.11.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = ( VALID_SUBSTITUTIONS_CHARACTERS = (
@ -400,6 +400,7 @@ CONF_INDOOR = "indoor"
CONF_INFRARED = "infrared" CONF_INFRARED = "infrared"
CONF_INITIAL_MODE = "initial_mode" CONF_INITIAL_MODE = "initial_mode"
CONF_INITIAL_OPTION = "initial_option" CONF_INITIAL_OPTION = "initial_option"
CONF_INITIAL_STATE = "initial_state"
CONF_INITIAL_VALUE = "initial_value" CONF_INITIAL_VALUE = "initial_value"
CONF_INPUT = "input" CONF_INPUT = "input"
CONF_INTEGRATION_TIME = "integration_time" CONF_INTEGRATION_TIME = "integration_time"
@ -936,7 +937,6 @@ CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature"
CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold"
CONF_WEB_SERVER = "web_server" CONF_WEB_SERVER = "web_server"
CONF_WEB_SERVER_ID = "web_server_id" CONF_WEB_SERVER_ID = "web_server_id"
CONF_WEB_SERVER_SORTING_WEIGHT = "web_server_sorting_weight"
CONF_WEIGHT = "weight" CONF_WEIGHT = "weight"
CONF_WHILE = "while" CONF_WHILE = "while"
CONF_WHITE = "white" CONF_WHITE = "white"
@ -964,6 +964,7 @@ ICON_ACCELERATION_Y = "mdi:axis-y-arrow"
ICON_ACCELERATION_Z = "mdi:axis-z-arrow" ICON_ACCELERATION_Z = "mdi:axis-z-arrow"
ICON_ACCOUNT = "mdi:account" ICON_ACCOUNT = "mdi:account"
ICON_ACCOUNT_CHECK = "mdi:account-check" ICON_ACCOUNT_CHECK = "mdi:account-check"
ICON_AIR_FILTER = "mdi:air-filter"
ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical" ICON_ARROW_EXPAND_VERTICAL = "mdi:arrow-expand-vertical"
ICON_BATTERY = "mdi:battery" ICON_BATTERY = "mdi:battery"
ICON_BLUETOOTH = "mdi:bluetooth" ICON_BLUETOOTH = "mdi:bluetooth"
@ -985,6 +986,7 @@ ICON_FINGERPRINT = "mdi:fingerprint"
ICON_FLASH = "mdi:flash" ICON_FLASH = "mdi:flash"
ICON_FLASK = "mdi:flask" ICON_FLASK = "mdi:flask"
ICON_FLASK_OUTLINE = "mdi:flask-outline" ICON_FLASK_OUTLINE = "mdi:flask-outline"
ICON_FLASK_ROUND_BOTTOM = "mdi:flask-round-bottom"
ICON_FLOWER = "mdi:flower" ICON_FLOWER = "mdi:flower"
ICON_GAS_CYLINDER = "mdi:gas-cylinder" ICON_GAS_CYLINDER = "mdi:gas-cylinder"
ICON_GAUGE = "mdi:gauge" ICON_GAUGE = "mdi:gauge"
@ -997,6 +999,7 @@ ICON_KEY_PLUS = "mdi:key-plus"
ICON_LIGHTBULB = "mdi:lightbulb" ICON_LIGHTBULB = "mdi:lightbulb"
ICON_MAGNET = "mdi:magnet" ICON_MAGNET = "mdi:magnet"
ICON_MEMORY = "mdi:memory" ICON_MEMORY = "mdi:memory"
ICON_MOLECULE_CO = "mdi:molecule-co"
ICON_MOLECULE_CO2 = "mdi:molecule-co2" ICON_MOLECULE_CO2 = "mdi:molecule-co2"
ICON_MOTION_SENSOR = "mdi:motion-sensor" ICON_MOTION_SENSOR = "mdi:motion-sensor"
ICON_NEW_BOX = "mdi:new-box" ICON_NEW_BOX = "mdi:new-box"

View file

@ -657,35 +657,45 @@ void delay_microseconds_safe(uint32_t us);
/// @name Memory management /// @name Memory management
///@{ ///@{
/** An STL allocator that uses SPI RAM. /** An STL allocator that uses SPI or internal RAM.
* Returns `nullptr` in case no memory is available.
* *
* By setting flags, it can be configured to don't try main memory if SPI RAM is full or unavailable, and to return * By setting flags, it can be configured to:
* `nulllptr` instead of aborting when no memory is available. * - perform external allocation falling back to main memory if SPI RAM is full or unavailable
* - perform external allocation only
* - perform internal allocation only
*/ */
template<class T> class ExternalRAMAllocator { template<class T> class RAMAllocator {
public: public:
using value_type = T; using value_type = T;
enum Flags { enum Flags {
NONE = 0, NONE = 0, // Perform external allocation and fall back to internal memory
REFUSE_INTERNAL = 1 << 0, ///< Refuse falling back to internal memory when external RAM is full or unavailable. ALLOC_EXTERNAL = 1 << 0, // Perform external allocation only.
ALLOW_FAILURE = 1 << 1, ///< Don't abort when memory allocation fails. ALLOC_INTERNAL = 1 << 1, // Perform internal allocation only.
ALLOW_FAILURE = 1 << 2, // Does nothing. Kept for compatibility.
}; };
ExternalRAMAllocator() = default; RAMAllocator() = default;
ExternalRAMAllocator(Flags flags) : flags_{flags} {} RAMAllocator(uint8_t flags) : flags_{flags} {}
template<class U> constexpr ExternalRAMAllocator(const ExternalRAMAllocator<U> &other) : flags_{other.flags_} {} template<class U> constexpr RAMAllocator(const RAMAllocator<U> &other) : flags_{other.flags_} {}
T *allocate(size_t n) { T *allocate(size_t n) {
size_t size = n * sizeof(T); size_t size = n * sizeof(T);
T *ptr = nullptr; T *ptr = nullptr;
#ifdef USE_ESP32 #ifdef USE_ESP32
// External allocation by default or if explicitely requested
if ((this->flags_ & Flags::ALLOC_EXTERNAL) || ((this->flags_ & Flags::ALLOC_INTERNAL) == 0)) {
ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); ptr = static_cast<T *>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
#endif }
if (ptr == nullptr && (this->flags_ & Flags::REFUSE_INTERNAL) == 0) // Fallback to internal allocation if explicitely requested or no flag is specified
if (ptr == nullptr && ((this->flags_ & Flags::ALLOC_INTERNAL) || (this->flags_ & Flags::ALLOC_EXTERNAL) == 0)) {
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
if (ptr == nullptr && (this->flags_ & Flags::ALLOW_FAILURE) == 0) }
abort(); #else
// Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported
ptr = static_cast<T *>(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
#endif
return ptr; return ptr;
} }
@ -694,9 +704,11 @@ template<class T> class ExternalRAMAllocator {
} }
private: private:
Flags flags_{Flags::ALLOW_FAILURE}; uint8_t flags_{Flags::ALLOW_FAILURE};
}; };
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
/// @} /// @}
/// @name Internal functions /// @name Internal functions

View file

@ -1,2 +1,2 @@
pillow==10.2.0 pillow==10.4.0
cairosvg==2.7.1 cairosvg==2.7.1

View file

@ -2,6 +2,15 @@
set -e set -e
help() {
echo "Usage: $0 [-e <config|compile|clean>] [-c <string>] [-t <string>]" 1>&2
echo 1>&2
echo " - e - Parameter for esphome command. Default compile. Common alternative is config." 1>&2
echo " - c - Component folder name to test. Default *. E.g. '-c logger'." 1>&2
echo " - t - Target name to test. Put '-t list' to display all possibilities. E.g. '-t esp32-s2-idf-51'." 1>&2
exit 1
}
# Parse parameter: # Parse parameter:
# - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`. # - `e` - Parameter for `esphome` command. Default `compile`. Common alternative is `config`.
# - `c` - Component folder name to test. Default `*`. # - `c` - Component folder name to test. Default `*`.
@ -13,7 +22,7 @@ do
e) esphome_command=${OPTARG};; e) esphome_command=${OPTARG};;
c) target_component=${OPTARG};; c) target_component=${OPTARG};;
t) requested_target_platform=${OPTARG};; t) requested_target_platform=${OPTARG};;
\?) echo "Usage: $0 [-e <config|compile|clean>] [-c <string>] [-t <string>]" 1>&2; exit 1;; \?) help;;
esac esac
done done
@ -24,8 +33,8 @@ if ! [ -d "./tests/test_build_components/build" ]; then
fi fi
start_esphome() { start_esphome() {
if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform" ]; then if [ -n "$requested_target_platform" ] && [ "$requested_target_platform" != "$target_platform_with_version" ]; then
echo "Skiping $target_platform" echo "Skipping $target_platform_with_version"
return return
fi fi
# create dynamic yaml file in `build` folder. # create dynamic yaml file in `build` folder.

View file

@ -4,6 +4,7 @@ touchscreen:
interrupt_pin: interrupt_pin:
number: 21 number: 21
reset_pin: GPIO16 reset_pin: GPIO16
skip_probe: false
transform: transform:
mirror_x: false mirror_x: false
mirror_y: false mirror_y: false
@ -11,14 +12,14 @@ touchscreen:
i2c: i2c:
sda: 3 sda: 3
scl: 2 scl: 4
display: display:
- id: my_display - id: my_display
platform: ili9xxx platform: ili9xxx
dimensions: 480x320 dimensions: 480x320
model: ST7796 model: ST7796
cs_pin: 15 cs_pin: 18
dc_pin: 20 dc_pin: 20
reset_pin: 22 reset_pin: 22
transform: transform:

View file

@ -0,0 +1,13 @@
sensor:
- platform: grove_gas_mc_v2
i2c_id: i2c_bus
nitrogen_dioxide:
name: "Nitrogen Dioxide"
ethanol:
name: "Ethanol"
carbon_monoxide:
name: "Carbon Monoxide"
tvoc:
name: "Volatile Organic Compounds"
update_interval: 30s
address: 0xAD

View file

@ -0,0 +1,6 @@
i2c:
sda: 21
scl: 22
id: i2c_bus
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
sda: 21
scl: 22
id: i2c_bus
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
sda: 4
scl: 5
id: i2c_bus
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
sda: 21
scl: 22
id: i2c_bus
<<: !include common.yaml

View file

@ -2,13 +2,13 @@ spi:
- id: spi_main_lcd - id: spi_main_lcd
clk_pin: 16 clk_pin: 16
mosi_pin: 17 mosi_pin: 17
miso_pin: 15 miso_pin: 32
display: display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 14
dc_pin: 13 dc_pin: 13
reset_pin: 21 reset_pin: 21
invert_colors: true invert_colors: true

View file

@ -8,8 +8,8 @@ display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 3
dc_pin: 9 dc_pin: 11
reset_pin: 10 reset_pin: 10
invert_colors: true invert_colors: true

View file

@ -8,8 +8,8 @@ display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 8 cs_pin: 3
dc_pin: 9 dc_pin: 11
reset_pin: 10 reset_pin: 10
invert_colors: true invert_colors: true

View file

@ -2,13 +2,13 @@ spi:
- id: spi_main_lcd - id: spi_main_lcd
clk_pin: 16 clk_pin: 16
mosi_pin: 17 mosi_pin: 17
miso_pin: 15 miso_pin: 18
display: display:
- platform: ili9xxx - platform: ili9xxx
id: main_lcd id: main_lcd
model: ili9342 model: ili9342
cs_pin: 12 cs_pin: 19
dc_pin: 13 dc_pin: 13
reset_pin: 21 reset_pin: 21
invert_colors: true invert_colors: true

View file

@ -0,0 +1,125 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
light:
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
- platform: rgb
id: test_rgb_light_initial_state
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
initial_state:
color_mode: rgb
red: 100%
green: 50%
blue: 50%

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 17 pin: 17
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 5 pin: 5
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 5 pin: 5
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 17 pin: 17
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 16 pin: 16
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -1,23 +1,3 @@
esphome:
on_boot:
then:
- light.toggle: test_binary_light
- light.turn_off: test_rgb_light
- light.turn_on:
id: test_rgb_light
brightness: 100%
red: 100%
green: 100%
blue: 1.0
- light.control:
id: test_monochromatic_light
state: on
- light.dim_relative:
id: test_monochromatic_light
relative_brightness: 5%
brightness_limits:
max_brightness: 90%
output: output:
- platform: gpio - platform: gpio
id: test_binary id: test_binary
@ -38,97 +18,4 @@ output:
id: test_ledc_5 id: test_ledc_5
pin: 5 pin: 5
light: <<: !include common.yaml
- platform: binary
id: test_binary_light
name: Binary Light
output: test_binary
effects:
- strobe:
on_state:
- logger.log: Binary light state changed
- platform: monochromatic
id: test_monochromatic_light
name: Monochromatic Light
output: test_ledc_1
gamma_correct: 2.8
default_transition_length: 2s
effects:
- strobe:
- flicker:
- flicker:
name: My Flicker
alpha: 98%
intensity: 1.5%
- lambda:
name: My Custom Effect
update_interval: 1s
lambda: |-
static int state = 0;
state += 1;
if (state == 4)
state = 0;
- pulse:
transition_length: 10s
update_interval: 20s
min_brightness: 10%
max_brightness: 90%
- pulse:
name: pulse2
transition_length:
on_length: 10s
off_length: 5s
update_interval: 15s
min_brightness: 10%
max_brightness: 90%
- platform: rgb
id: test_rgb_light
name: RGB Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
- platform: rgbw
id: test_rgbw_light
name: RGBW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
white: test_ledc_4
color_interlock: true
- platform: rgbww
id: test_rgbww_light
name: RGBWW Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
cold_white: test_ledc_4
warm_white: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: rgbct
id: test_rgbct_light
name: RGBCT Light
red: test_ledc_1
green: test_ledc_2
blue: test_ledc_3
color_temperature: test_ledc_4
white_brightness: test_ledc_5
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
color_interlock: true
- platform: cwww
id: test_cwww_light
name: CWWW Light
cold_white: test_ledc_1
warm_white: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds
constant_brightness: true
- platform: color_temperature
id: test_color_temperature_light
name: CT Light
color_temperature: test_ledc_1
brightness: test_ledc_2
cold_white_color_temperature: 153 mireds
warm_white_color_temperature: 500 mireds

View file

@ -135,3 +135,9 @@ wifi:
time: time:
platform: sntp platform: sntp
id: time_id id: time_id
text:
- id: lvgl_text
platform: lvgl
widget: hello_label
mode: text

View file

@ -1,3 +1,16 @@
substitutions:
light_recessed: "\U000F179B"
wall_sconce_round: "\U000F0748"
gas_burner: "\U000F1A1B"
home_icon: "\U000F02DC"
menu_left: "\U000F0A02"
menu_right: "\U000F035F"
close: "\U000F0156"
delete: "\U000F01B4"
backspace: "\U000F006E"
check: "\U000F012C"
arrow_down: "\U000F004B"
lvgl: lvgl:
log_level: TRACE log_level: TRACE
bg_color: light_blue bg_color: light_blue
@ -138,6 +151,10 @@ lvgl:
align: center align: center
text_font: montserrat_40 text_font: montserrat_40
border_post: true border_post: true
on_press:
lvgl.label.update:
id: hello_label
text: Goodbye
on_click: on_click:
then: then:
- lvgl.animimg.stop: anim_img - lvgl.animimg.stop: anim_img
@ -599,6 +616,42 @@ lvgl:
- name: Cat - name: Cat
id: tabview_tab_2 id: tabview_tab_2
widgets: widgets:
- dropdown:
indicator:
text_font: helvetica20
id: lv_dropdown
options:
- First
- Second
- Third
- 4th
- 5th
- 6th
- 7th
- 8th
- 9th
selected_index: 2
dir: top
symbol: ${arrow_down}
dropdown_list:
max_height: 100px
bg_color: 0x000080
text_color: 0xFF00
selected:
bg_color: 0xFFFF00
checked:
bg_color: 0x00
text_color: 0xFF0000
scrollbar:
bg_color: 0xFF
on_value:
logger.log:
format: "Dropdown changed = %d"
args: [x]
on_cancel:
logger.log:
format: "Dropdown closed = %d"
args: [x]
- image: - image:
src: cat_image src: cat_image
on_click: on_click:
@ -608,6 +661,59 @@ lvgl:
id: tabview_id id: tabview_id
index: 0 index: 0
animated: true animated: true
- meter:
height: 200px
width: 200px
indicator:
bg_color: 0xFF
radius: 0
bg_opa: TRANSP
text_color: 0xFFFFFF
scales:
- ticks:
width: 1
count: 61
length: 20
color: 0xFFFFFF
range_from: 0
range_to: 60
angle_range: 360
rotation: 270
indicators:
- line:
opa: 50%
id: minute_hand
color: 0xFF0000
r_mod: -1
width: 3
-
angle_range: 330
rotation: 300
range_from: 1
range_to: 12
ticks:
width: 1
count: 12
length: 1
major:
stride: 1
width: 4
length: 8
color: 0xC0C0C0
label_gap: 6
- angle_range: 360
rotation: 270
range_from: 0
range_to: 720
indicators:
- line:
id: hour_hand
value: 180
width: 4
color: 0xA0A0A0
r_mod: -20
opa: 0%
font: font:
- file: "gfonts://Roboto" - file: "gfonts://Roboto"
id: space16 id: space16

Some files were not shown because too many files have changed in this diff Show more