Merge branch 'dev' into i2c-write-registers-migrate

This commit is contained in:
J. Nick Koston
2026-10-17 15:28:30 -10:00
committed by GitHub
372 changed files with 9753 additions and 1513 deletions
+6 -1
View File
@@ -428,6 +428,9 @@ esphome/components/mpl3115a2/* @kbickar
esphome/components/mpu6886/* @fabaff
esphome/components/ms8607/* @e28eta
esphome/components/msa3xx/* @latonita
esphome/components/mt6701/* @slimcdk
esphome/components/mt6701_i2c/* @slimcdk
esphome/components/mt6701_spi/* @slimcdk
esphome/components/nau7802/* @cujomalainey
esphome/components/network/* @esphome/core
esphome/components/nextion/* @edwardtfn @senexcrenshaw
@@ -512,6 +515,7 @@ esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rp2_ble_tracker/* @bdraco
esphome/components/rpi_dpi_rgb/* @clydebarrow
esphome/components/rtl87xx/* @kuba2k2
esphome/components/rtsp_server/* @max246
esphome/components/rtttl/* @glmnet @ximex
esphome/components/runtime_image/* @clydebarrow @guillempages @kahrendt
esphome/components/runtime_stats/* @bdraco
@@ -687,7 +691,7 @@ esphome/components/version/* @esphome/core
esphome/components/voice_assistant/* @jesserockz @kahrendt
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
esphome/components/watchdog/* @oarcher
esphome/components/water_heater/* @dhoeben
esphome/components/water_heater/* @esphome/core
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/waveshare_io_ch32v003/* @latonita
esphome/components/web_server/ota/* @esphome/core
@@ -726,6 +730,7 @@ esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zephyr_coredump/* @tomaszduda23
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
esphome/components/zephyr_pwm/* @wiomoc
esphome/components/zhlt01/* @cfeenstra1024
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["spi"]
@@ -20,6 +19,4 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await spi.new_spi_component(config)
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
CODEOWNERS = ["@solomondg1"]
@@ -26,6 +25,4 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await spi.new_spi_component(config)
+1 -4
View File
@@ -7,7 +7,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_ECO2,
CONF_HUMIDITY,
CONF_ID,
CONF_PM_0_3UM,
CONF_PM_0_5UM,
CONF_PM_1_0,
@@ -165,9 +164,7 @@ FINAL_VALIDATE_SCHEMA = final_validate
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_PM_1_0, var.set_pm_1_0_sensor)
+2 -9
View File
@@ -52,16 +52,9 @@ enum { // APDS9306 registers
}
void APDS9306::setup() {
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
// 0xB1 for APDS9306, 0xB3 for APDS9306-065; they differ only in bit 1
if (!this->check_chip_id(this, APDS9306_PART_ID, 0xB1, 0xFD))
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->mark_failed(LOG_STR("APDS9306 has invalid id"));
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
+1 -9
View File
@@ -64,16 +64,8 @@ static constexpr uint16_t ALS_SATURATION = 1024 * (256 - APDS9930_ATIME_VALUE);
bool APDS9930Component::write_reg_(uint8_t reg, uint8_t value) { return this->write_byte(reg | APDS9930_CMD, value); }
void APDS9930Component::setup() {
uint8_t id;
if (!this->read_byte(APDS9930_ID | APDS9930_CMD, &id)) {
this->mark_failed(LOG_STR("Communication failed"));
if (!this->check_chip_id(this, APDS9930_ID | APDS9930_CMD, APDS9930_CHIP_ID))
return;
}
if (id != APDS9930_CHIP_ID) {
ESP_LOGE(TAG, "Wrong ID 0x%02X (expected 0x%02X)", id, APDS9930_CHIP_ID);
this->mark_failed(LOG_STR("Wrong chip ID"));
return;
}
const uint8_t control =
(this->led_drive_ & 0b11) << CONTROL_PDRIVE_SHIFT | CONTROL_PDIODE_CH1 << CONTROL_PDIODE_SHIFT |
+3
View File
@@ -65,6 +65,9 @@ void APIServer::socket_failed_(const LogString *msg) {
}
void APIServer::setup() {
#if defined(USE_API_HOMEASSISTANT_STATES) && defined(HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT)
this->state_subs_.reserve(HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT);
#endif
#ifdef USE_API_NOISE
// Always reserve the slot: flash preferences are positional on esp8266, so
// a yaml key build must keep the layout of a runtime key build
+1 -7
View File
@@ -9,14 +9,8 @@ ESPHOME_LOG_TAG(TAG, "as7341");
void AS7341Component::setup() {
LOG_I2C_DEVICE(this);
// Verify device ID
uint8_t id;
this->read_byte(AS7341_ID, &id);
ESP_LOGCONFIG(TAG, " Read ID: 0x%X", id);
if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) {
this->mark_failed(LOG_STR("Wrong chip ID"));
if (!this->check_chip_id(this, AS7341_ID, AS7341_CHIP_ID << 2, 0xFC))
return;
}
// Power on (enter IDLE state)
if (!this->enable_power(true)) {
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_POWER_FACTOR,
@@ -125,9 +124,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
var = await spi.new_spi_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -8
View File
@@ -63,15 +63,8 @@ static float calculate_cct(uint16_t red, uint16_t green, uint16_t blue, bool low
}
void BH1745Component::setup() {
uint8_t manufacturer_id;
if (!this->read_byte(REG_MANUFACTURER_ID, &manufacturer_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
if (!this->check_chip_id(this, REG_MANUFACTURER_ID, MANUFACTURER_ID))
return;
}
if (manufacturer_id != MANUFACTURER_ID) {
this->mark_failed(LOG_STR("Wrong manufacturer ID"));
return;
}
uint8_t system_control;
if (!this->read_byte(REG_SYSTEM_CONTROL, &system_control) ||
(system_control & SYSTEM_CONTROL_PART_ID_MASK) != PART_ID) {
+1 -3
View File
@@ -147,9 +147,7 @@ automation.register_apply_action(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
+1 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_MODE,
CONF_VOLTAGE,
@@ -112,9 +111,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
if (mode := config[CONF_MODE]) != DEFAULT_MODE:
cg.add(var.set_mode(mode))
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_EXTERNAL_TEMPERATURE,
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_POWER,
CONF_REFERENCE_VOLTAGE,
@@ -227,9 +226,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -4
View File
@@ -6,7 +6,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_RESET,
@@ -97,9 +96,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -10
View File
@@ -78,17 +78,8 @@ bool BMI270Component::load_config_file_() {
void BMI270Component::setup() {
MotionComponent::setup();
// 1. Verify chip ID
uint8_t chip_id = 0;
if (!this->read_byte(BMI270_REG_CHIP_ID, &chip_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
if (!this->check_chip_id(this, BMI270_REG_CHIP_ID, BMI270_CHIP_ID_VALUE))
return;
}
if (chip_id != BMI270_CHIP_ID_VALUE) {
ESP_LOGE(TAG, "Wrong chip ID: 0x%02X (expected 0x%02X)", chip_id, BMI270_CHIP_ID_VALUE);
this->mark_failed(LOG_STR("Wrong chip ID"));
return;
}
ESP_LOGD(TAG, "Chip ID: 0x%02X", chip_id);
// 2. Soft-reset via CMD register (0x7E = 0xB6)
if (!this->write_byte(BMI270_REG_CMD, BMI270_CMD_SOFT_RESET)) {
+1 -11
View File
@@ -79,19 +79,9 @@ bool BQ25186Component::apply_config(const BQ25186ConfigRegisters &regs) {
}
void BQ25186Component::setup() {
uint8_t mask_id;
if (!this->read_byte(BQ25186_REG_MASK_ID, &mask_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
// Another charger at this address could lay its registers out differently, so do not write into it
const uint8_t device_id = mask_id & 0x0F;
if (device_id != 0x01) {
ESP_LOGE(TAG, "Unexpected device id 0x%X (expected 0x1)", device_id);
this->mark_failed(LOG_STR("Not a BQ25186"));
if (!this->check_chip_id(this, BQ25186_REG_MASK_ID, 0x01, 0x0F))
return;
}
// A failed write only sets the warning status: the chip has identified itself, so a later
// apply_configuration may retry
+3 -57
View File
@@ -1,18 +1,10 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, sensor
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_VOLT,
)
from esphome.const import CONF_ADDRESS
from esphome.types import ConfigType
AUTO_LOAD = ["sensor"]
CODEOWNERS = ["@limpkin"]
DEPENDENCIES = ["i2c"]
DOMAIN = "bq76972"
@@ -25,37 +17,7 @@ BQ76972Component = bq76972_ns.class_(
CONF_REG_DISABLED = "reg_disabled"
CONF_CRC_ENABLED = "crc_enabled"
CONF_STACK_VOLTAGE = "stack_voltage"
# Ordered as the chip reports them, starting at command 0x68
CONF_TEMPERATURES = [
"internal_temperature",
"cfetoff_temperature",
"dfetoff_temperature",
"alert_temperature",
"ts1_temperature",
"ts2_temperature",
"ts3_temperature",
"hdq_temperature",
"dchg_temperature",
"ddsg_temperature",
]
CONF_CELL_VOLTAGES = [f"cell_{i}_voltage" for i in range(1, 17)]
voltage_sensor_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
temp_sensor_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
CONF_BQ76972_ID = "bq76972_id"
CONFIG_SCHEMA = (
cv.Schema(
@@ -63,9 +25,6 @@ CONFIG_SCHEMA = (
cv.GenerateID(): cv.declare_id(BQ76972Component),
cv.Optional(CONF_CRC_ENABLED, default=False): cv.boolean,
cv.Optional(CONF_REG_DISABLED, default=False): cv.boolean,
cv.Optional(CONF_STACK_VOLTAGE): voltage_sensor_schema,
**{cv.Optional(key): voltage_sensor_schema for key in CONF_CELL_VOLTAGES},
**{cv.Optional(key): temp_sensor_schema for key in CONF_TEMPERATURES},
}
)
.extend(i2c.i2c_device_schema(0x08))
@@ -79,19 +38,6 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_crc_mode(config[CONF_CRC_ENABLED]))
cg.add(var.set_reg_disable(config[CONF_REG_DISABLED]))
sensors = sensor.sub_sensors(config)
await sensors(CONF_STACK_VOLTAGE, var.set_stack_voltage_sensor)
for idx, key in enumerate(CONF_TEMPERATURES):
if (conf := config.get(key)) is not None:
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens, idx))
for idx, key in enumerate(CONF_CELL_VOLTAGES):
if (conf := config.get(key)) is not None:
sens = await sensor.new_sensor(conf)
cg.add(var.set_cell_sensor(sens, idx))
BQ76972_PROGRAM_I2C_ADDRESS_SCHEMA = cv.maybe_simple_value(
{
+8
View File
@@ -172,10 +172,12 @@ const LogString *BQ76972Component::configure_() {
const bool write_regs = !regs_disabled;
bool write_thermistors = false;
#ifdef USE_SENSOR
for (size_t i = 1; i < BQ76972_NUM_TEMPERATURES; i++) {
if (i != BQ76972_TS1_INDEX && this->temperature_sensors_[i] != nullptr)
write_thermistors = true;
}
#endif
if (write_regs || write_thermistors) {
if (!this->enter_cfg_update_())
@@ -201,12 +203,14 @@ const LogString *BQ76972Component::write_config_(bool write_regs) {
!this->write_subcommand_(BQ76972_MEM_REG0_CONFIG, &zero, 1))
return LOG_STR("Writing regulator config failed");
}
#ifdef USE_SENSOR
for (size_t i = 1; i < BQ76972_NUM_TEMPERATURES; i++) {
if (i == BQ76972_TS1_INDEX || this->temperature_sensors_[i] == nullptr)
continue;
if (!this->write_subcommand_(BQ76972_MEM_TEMP_PIN_CONFIG_BASE + i, &BQ76972_PIN_CONFIG_THERMISTOR, 1))
return LOG_STR("Writing thermistor pin config failed");
}
#endif
return nullptr;
}
@@ -248,6 +252,7 @@ void BQ76972Component::program_i2c_address(uint8_t address) {
ESP_LOGI(TAG, "I2C address set to 0x%02X", address);
}
#ifdef USE_SENSOR
template<size_t N> static bool any_set(const std::array<sensor::Sensor *, N> &sensors) {
for (auto *sensor : sensors) {
if (sensor != nullptr)
@@ -294,5 +299,8 @@ void BQ76972Component::update() {
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
}
}
#else
void BQ76972Component::update() {}
#endif
} // namespace esphome::bq76972
+8 -1
View File
@@ -1,8 +1,11 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#include <array>
@@ -19,9 +22,11 @@ class BQ76972Component final : public PollingComponent, public i2c::I2CDevice {
void set_crc_mode(bool crc_mode) { this->crc_mode_ = crc_mode; }
void set_reg_disable(bool reg_disable) { this->reg_disable_ = reg_disable; }
#ifdef USE_SENSOR
void set_stack_voltage_sensor(sensor::Sensor *s) { this->stack_voltage_sensor_ = s; }
void set_cell_sensor(sensor::Sensor *s, size_t index) { this->cell_sensors_[index] = s; }
void set_temperature_sensor(sensor::Sensor *s, size_t index) { this->temperature_sensors_[index] = s; }
#endif
void program_i2c_address(uint8_t address);
@@ -43,9 +48,11 @@ class BQ76972Component final : public PollingComponent, public i2c::I2CDevice {
bool reg_disable_{false};
bool crc_mode_{false};
#ifdef USE_SENSOR
sensor::Sensor *stack_voltage_sensor_{nullptr};
std::array<sensor::Sensor *, BQ76972_NUM_CELLS> cell_sensors_{};
std::array<sensor::Sensor *, BQ76972_NUM_TEMPERATURES> temperature_sensors_{};
#endif
};
} // namespace esphome::bq76972
+73
View File
@@ -0,0 +1,73 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_VOLT,
)
from esphome.types import ConfigType
from . import CONF_BQ76972_ID, BQ76972Component
DEPENDENCIES = ["bq76972"]
CONF_STACK_VOLTAGE = "stack_voltage"
# Ordered as the chip reports them, starting at command 0x68
CONF_TEMPERATURES = [
"internal_temperature",
"cfetoff_temperature",
"dfetoff_temperature",
"alert_temperature",
"ts1_temperature",
"ts2_temperature",
"ts3_temperature",
"hdq_temperature",
"dchg_temperature",
"ddsg_temperature",
]
CONF_CELL_VOLTAGES = [f"cell_{i}_voltage" for i in range(1, 17)]
voltage_sensor_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
)
temp_sensor_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_BQ76972_ID): cv.use_id(BQ76972Component),
cv.Optional(CONF_STACK_VOLTAGE): voltage_sensor_schema,
**{cv.Optional(key): voltage_sensor_schema for key in CONF_CELL_VOLTAGES},
**{cv.Optional(key): temp_sensor_schema for key in CONF_TEMPERATURES},
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_BQ76972_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_STACK_VOLTAGE, hub.set_stack_voltage_sensor)
for idx, key in enumerate(CONF_TEMPERATURES):
if (conf := config.get(key)) is not None:
sens = await sensor.new_sensor(conf)
cg.add(hub.set_temperature_sensor(sens, idx))
for idx, key in enumerate(CONF_CELL_VOLTAGES):
if (conf := config.get(key)) is not None:
sens = await sensor.new_sensor(conf)
cg.add(hub.set_cell_sensor(sens, idx))
+2 -1
View File
@@ -11,8 +11,9 @@ namespace esphome::camera {
* IDLE: Camera requests to send an image to the API.
* API_REQUESTER: API requests a new image.
* WEB_REQUESTER: ESP32 web server request an image. Ignored by API.
* RTSP_REQUESTER: RTSP server requests a stream of images. Ignored by API and web server.
*/
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER, RTSP_REQUESTER };
/// Enumeration of different pixel formats.
enum PixelFormat : uint8_t {
+1 -3
View File
@@ -274,9 +274,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
var = await spi.new_spi_component(config)
for opt in CONFIG_MAP:
key = opt.schema
@@ -135,13 +135,14 @@ void LgIrClimate::transmit_state() {
default:
return;
}
this->transmit_(remote_state);
this->publish_state();
return;
} else { // just toggle swing when advanced_commands_support is not set
remote_state |= HEADER_BASIC;
remote_state |= BASIC_SWING_TOGGLE;
}
// The remote's swing button sends a fixed code with no fan speed or temperature bits, so the
// frame goes out as is instead of falling through to them
this->transmit_(remote_state);
return;
} else { // Mode commands
const bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
switch (this->mode) {
@@ -208,7 +209,6 @@ void LgIrClimate::transmit_state() {
}
this->transmit_(remote_state);
this->publish_state();
}
bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
+1 -4
View File
@@ -10,7 +10,6 @@ from esphome.const import (
CONF_BASELINE,
CONF_CO2,
CONF_CYCLE,
CONF_ID,
DEVICE_CLASS_CARBON_DIOXIDE,
ICON_MOLECULE_CO2,
STATE_CLASS_MEASUREMENT,
@@ -68,9 +67,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
"""Code generation entry point."""
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_CO2, var.set_co2_sensor)
+1 -3
View File
@@ -103,9 +103,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
var = await spi.new_spi_component(config)
cg.add(var.set_samples(config[CONF_SAMPLES]))
cg.add(var.set_phase_offset(config[CONF_PHASE_OFFSET]))
+1 -4
View File
@@ -3,7 +3,6 @@ from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_FREQUENCY,
CONF_ID,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
@@ -121,9 +120,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID], config[CONF_CURRENT_INPUT])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config, config[CONF_CURRENT_INPUT])
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_APPARENT_POWER,
CONF_CURRENT,
CONF_ENERGY,
CONF_ID,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_REACTIVE_POWER,
@@ -94,9 +93,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+2 -4
View File
@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ADDRESS, CONF_ID
from esphome.const import CONF_ADDRESS
from esphome.types import ConfigType
CODEOWNERS = ["@s1lvi0"]
@@ -37,7 +37,5 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
cg.add(var.set_address(config[CONF_ADDRESS]))
@@ -34,6 +34,15 @@ void DebugComponent::dump_config() {
this->free_heap_ = get_free_heap_();
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
uint32_t uptime = static_cast<uint32_t>(millis_64() / 1000);
uint32_t days = uptime / 86400;
uptime -= days * 86400;
uint32_t hours = uptime / 3600;
uptime -= hours * 3600;
uint32_t minutes = uptime / 60;
uptime -= minutes * 60;
ESP_LOGD(TAG, "Uptime: %" PRIu32 "d %02" PRIu32 "h %02" PRIu32 "m %02" PRIu32 "s", days, hours, minutes, uptime);
pos = get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>(device_info_buffer), pos);
#ifdef USE_TEXT_SENSOR
+15 -2
View File
@@ -155,6 +155,12 @@ def validate_wakeup_pin(
return processed_pins
_WAKEUP_PIN_MODE_BOTH_LEVELS = (
"Specify 'wakeup_pin_mode' either at the top level under 'deep_sleep' "
"or under the pin entry, not both"
)
def validate_config(config: ConfigType) -> ConfigType:
wakeup_pins = config.get(CONF_WAKEUP_PIN)
has_pin_mode = CONF_WAKEUP_PIN_MODE in config
@@ -170,6 +176,14 @@ def validate_config(config: ConfigType) -> ConfigType:
"ESP32 does not support multiple entries for 'wakeup_pin'",
path=[CONF_WAKEUP_PIN],
)
if wakeup_pins and CONF_WAKEUP_PIN_MODE in (pin := wakeup_pins[0]):
if has_pin_mode:
raise cv.Invalid(
_WAKEUP_PIN_MODE_BOTH_LEVELS,
path=[CONF_WAKEUP_PIN, 0, CONF_WAKEUP_PIN_MODE],
)
# ESP32 has one global mode, which to_code() reads from the top level
config[CONF_WAKEUP_PIN_MODE] = pin.pop(CONF_WAKEUP_PIN_MODE)
elif has_pin_mode:
if multiple_pins:
raise cv.Invalid(
@@ -178,8 +192,7 @@ def validate_config(config: ConfigType) -> ConfigType:
)
if CONF_WAKEUP_PIN_MODE in (pin := wakeup_pins[0]):
raise cv.Invalid(
"Specify 'wakeup_pin_mode' either at the top level under 'deep_sleep' "
"or under the pin entry, not both",
_WAKEUP_PIN_MODE_BOTH_LEVELS,
path=[CONF_WAKEUP_PIN, 0, CONF_WAKEUP_PIN_MODE],
)
# Move the global mode into the single pin entry, which to_code() reads
+2 -4
View File
@@ -5,7 +5,7 @@ import esphome.codegen as cg
from esphome.components import uart
from esphome.components.const import CONF_LOOP
import esphome.config_validation as cv
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_VOLUME
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@glmnet"]
@@ -51,9 +51,7 @@ _CALLBACK_AUTOMATIONS = (
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
@@ -42,9 +42,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config)
automation.register_apply_action(
@@ -79,7 +79,5 @@ def final_validate_mode(keys: dict[str, str]) -> Callable[[ConfigType], None]:
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID], RUN_MODES[config[CONF_MODE]])
cg.add_define("USE_LINE_READER")
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config, RUN_MODES[config[CONF_MODE]])
@@ -64,15 +64,6 @@ static bool starts_with(const char *str, ESPHOME_PGM_P prefix) {
return ESPHOME_strncmp_P(str, prefix, ESPHOME_strlen_P(prefix)) == 0;
}
static bool contains(const char *str, ESPHOME_PGM_P needle) {
const size_t len = ESPHOME_strlen_P(needle);
for (; *str != '\0'; str++) {
if (ESPHOME_strncmp_P(str, needle, len) == 0)
return true;
}
return false;
}
// Splits "a,b,c*" in place; returns the number of fields stored in `fields`
static uint8_t split_fields(char *str, char **fields, uint8_t max_fields) {
uint8_t count = 0;
@@ -94,9 +85,9 @@ static uint8_t split_fields(char *str, char **fields, uint8_t max_fields) {
static bool answer_means_done(Operation operation, const char *line) {
switch (operation) {
case Operation::OPERATION_STOP:
return contains(line, ESPHOME_PSTR("stopped already"));
return ESPHOME_strstr_P(line, ESPHOME_PSTR("stopped already")) != nullptr;
case Operation::OPERATION_START:
return contains(line, ESPHOME_PSTR("started already"));
return ESPHOME_strstr_P(line, ESPHOME_PSTR("started already")) != nullptr;
case Operation::OPERATION_SAVE:
return starts_with(line, ESPHOME_PSTR("no parameter"));
default:
@@ -261,11 +252,12 @@ void DFRobotSEN0609Component::handle_line_(char *line) {
this->finish_operation_();
} else if (starts_with(line, ESPHOME_PSTR("Response "))) {
this->parse_response_(line + 9);
} else if (contains(line, ESPHOME_PSTR("not stopped"))) {
} else if (ESPHOME_strstr_P(line, ESPHOME_PSTR("not stopped")) != nullptr) {
// Stop the sensor before the next attempt
this->stopped_ = false;
this->fail_operation_();
} else if (starts_with(line, ESPHOME_PSTR("Error")) || contains(line, ESPHOME_PSTR("not recognized"))) {
} else if (starts_with(line, ESPHOME_PSTR("Error")) ||
ESPHOME_strstr_P(line, ESPHOME_PSTR("not recognized")) != nullptr) {
ESP_LOGW(TAG, "Command '%s' rejected: %s", this->command_, line);
this->fail_operation_();
} else {
+10 -4
View File
@@ -30,6 +30,7 @@ from esphome.const import (
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_MODEL,
CONF_OFFSET_HEIGHT,
CONF_PAGES,
CONF_RESET_DURATION,
CONF_RESET_PIN,
@@ -73,12 +74,17 @@ for module_info in pkgutil.iter_modules(models.__path__):
MODELS = models.EpaperModel.models
DIMENSION_SCHEMA = cv.Schema(
{
def dimension_schema(model: models.EpaperModel) -> cv.Schema:
schema = {
cv.Required(CONF_WIDTH): cv.int_,
cv.Required(CONF_HEIGHT): cv.int_,
}
)
# Only models with an offset_height default use it, and the default must survive explicit dimensions
if (offset_height := model.get_default(CONF_OFFSET_HEIGHT, None)) is not None:
schema[cv.Optional(CONF_OFFSET_HEIGHT, default=offset_height)] = cv.positive_int
return cv.Schema(schema)
TRANSFORM_OPTIONS = {CONF_MIRROR_X, CONF_MIRROR_Y, CONF_SWAP_XY}
@@ -135,7 +141,7 @@ def model_schema(config):
model.option(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.GenerateID(): cv.declare_id(class_name),
cv.GenerateID(CONF_INIT_SEQUENCE_ID): cv.declare_id(cg.uint8),
cv_dimensions(CONF_DIMENSIONS): DIMENSION_SCHEMA,
cv_dimensions(CONF_DIMENSIONS): dimension_schema(model),
model.option(CONF_ENABLE_PIN): cv.ensure_list(pins.gpio_output_pin_schema),
model.option(CONF_INIT_SEQUENCE, cv.UNDEFINED): cv.ensure_list(
map_sequence
+33 -7
View File
@@ -90,7 +90,9 @@ bool EPaperBase::is_idle_() const {
return !this->busy_pin_->digital_read();
}
bool EPaperBase::reset() {
bool EPaperBase::reset() { return this->hardware_reset_(); }
bool EPaperBase::hardware_reset_() {
if (this->reset_pin_ != nullptr) {
if (this->state_ == EPaperState::RESET) {
this->reset_pin_->digital_write(false);
@@ -131,7 +133,9 @@ void EPaperBase::update() {
}
void EPaperBase::wait_for_idle_(bool should_wait) {
this->waiting_for_idle_start_ = millis();
// The wait starts when the step's delay ends, or now for a wait in the middle of a step
const uint32_t now = millis();
this->waiting_for_idle_start_ = static_cast<int32_t>(this->delay_until_ - now) > 0 ? this->delay_until_ : now;
this->waiting_for_idle_ = should_wait;
}
@@ -145,19 +149,30 @@ void EPaperBase::loop() {
auto now = millis();
// using modulus arithmetic to handle wrap-around
int diff = now - this->delay_until_;
if (diff < 0)
if (diff < 0) {
// Running the loop continuously cannot shorten a delay longer than one loop interval
if (-diff > static_cast<int>(App.get_loop_interval())) {
this->high_frequency_loop_.stop();
} else {
this->high_frequency_loop_.start();
}
return;
}
if (this->waiting_for_idle_) {
if (this->is_idle_()) {
this->waiting_for_idle_ = false;
this->high_frequency_loop_.start();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGV(TAG, "Screen was busy for %u ms", (unsigned) (millis() - this->waiting_for_idle_start_));
#endif
} else {
if (now - this->waiting_for_idle_start_ > this->busy_timeout_seconds_ * 1000u) {
const uint32_t busy_for = now - this->waiting_for_idle_start_;
if (busy_for > this->busy_timeout_seconds_ * 1000u) {
this->abandon_update_();
return;
}
if (busy_for > FAST_BUSY_POLL_MS)
this->high_frequency_loop_.stop();
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
if (now - this->waiting_for_idle_last_print_ >= 1000) {
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
@@ -168,6 +183,8 @@ void EPaperBase::loop() {
}
}
this->process_state_();
if (this->is_failed())
this->high_frequency_loop_.stop();
}
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
@@ -225,10 +242,12 @@ void EPaperBase::process_state_() {
this->set_state_(EPaperState::IDLE);
return;
}
// A full update resets update_count_ to zero, so a non-zero count marks a partial one
this->partial_ = this->update_count_ != 0;
this->set_state_(EPaperState::RESET);
break;
case EPaperState::INITIALISE:
if (!this->initialise(this->update_count_ != 0)) {
if (!this->initialise(this->partial_)) {
return; // Not done yet, come back next loop
}
this->set_state_(EPaperState::TRANSFER_DATA);
@@ -248,11 +267,14 @@ void EPaperBase::process_state_() {
this->set_state_(EPaperState::REFRESH_SCREEN);
break;
case EPaperState::REFRESH_SCREEN:
this->refresh_screen(this->update_count_ != 0);
this->refresh_screen(this->partial_);
this->update_count_ = (this->update_count_ + 1) % this->full_update_every_;
this->set_state_(EPaperState::POWER_OFF);
break;
case EPaperState::POWER_OFF:
if (!this->after_refresh()) {
return; // Not done yet, come back next loop
}
this->power_off();
this->set_state_(EPaperState::DEEP_SLEEP);
break;
@@ -268,16 +290,20 @@ void EPaperBase::process_state_() {
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
this->state_ = state;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
// allow subclasses to nominate delays
if (delay == 0)
delay = this->next_delay_;
this->next_delay_ = 0;
this->delay_until_ = millis() + delay;
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
TRUEFALSE(this->waiting_for_idle_));
// Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
if (state == EPaperState::IDLE) {
this->high_frequency_loop_.stop();
this->disable_loop();
} else {
this->high_frequency_loop_.start();
}
}
@@ -5,6 +5,7 @@
#include "esphome/components/spi/spi.h"
#include "esphome/components/split_buffer/split_buffer.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome::epaper_spi {
using namespace display;
@@ -31,6 +32,8 @@ static constexpr uint8_t SWAP_XY = 4;
static constexpr uint32_t MAX_TRANSFER_TIME = 10; // Transfer in 10ms blocks to allow the loop to run
static constexpr size_t MAX_TRANSFER_SIZE = 128;
// Longer busy waits are the waveform or a stuck pin, and are polled at the normal loop pace
static constexpr uint32_t FAST_BUSY_POLL_MS = 50;
static constexpr uint8_t DELAY_FLAG = 0xFF;
class EPaperBase : public Display,
@@ -129,6 +132,8 @@ class EPaperBase : public Display,
bool is_idle_() const;
void setup_pins_() const;
virtual bool reset();
/// The reset pin part of reset(), for a driver that must skip what its parent adds to it
bool hardware_reset_();
virtual bool initialise(bool partial);
void send_init_sequence_(const uint8_t *sequence, size_t length);
void wait_for_idle_(bool should_wait);
@@ -165,6 +170,13 @@ class EPaperBase : public Display,
*/
virtual void power_off() = 0;
/**
* Called after the refresh, before power off, repeatedly until it returns true.
* Lets a driver do extra work spread over several loops, such as rewriting the previous image.
* @return true when done, false to be called again next loop
*/
virtual bool after_refresh() { return true; }
/**
* Place the display into deep sleep
*/
@@ -193,12 +205,14 @@ class EPaperBase : public Display,
GPIOPin *reset_pin_{};
std::vector<GPIOPin *> enable_pins_{};
bool waiting_for_idle_{};
HighFrequencyLoopRequester high_frequency_loop_;
uint32_t delay_until_{}; // timestamp until which to delay processing
uint16_t next_delay_{}; // milliseconds to delay before next state
uint8_t transform_{};
uint8_t effective_transform_{};
uint8_t update_count_{};
bool full_update_requested_{};
bool partial_{}; // the update in progress is a partial one; set when UPDATE moves to RESET
// these values represent the bounds of the updated buffer. Note that x_high and y_high
// point to the pixel past the last one updated, i.e. may range up to width/height.
uint16_t x_low_{}, y_low_{}, x_high_{}, y_high_{};
@@ -68,7 +68,7 @@ bool HOT EPaperMono::transfer_data() {
while (this->current_data_index_ != this->y_high_) {
size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_ / 8;
for (size_t i = 0; i != row_length; i++) {
bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++];
bytes_to_send[i] = this->send_red_ && !this->send_red_as_image_ ? 0 : this->buffer_[data_idx++];
}
++this->current_data_index_;
this->write_array(&bytes_to_send.front(), row_length); // NOLINT
@@ -21,6 +21,8 @@ class EPaperMono : public EPaperBase {
virtual void set_window();
bool transfer_data() override;
bool send_red_{true};
// When set, the red RAM pass writes the image rather than blank data.
bool send_red_as_image_{false};
};
} // namespace esphome::epaper_spi
@@ -10,12 +10,11 @@ ESPHOME_LOG_TAG(TAG, "epaper_spi.uc8179");
bool EPaperUC8179::initialise(bool partial) {
EPaperBase::initialise(partial); // send the model init sequence
this->partial_ = partial;
ESP_LOGV(TAG, "Power on");
// POWER ON must precede the waveform/mode registers and the data transfer
// (the original driver powers on and busy-waits before writing them).
// The state machine busy-waits before entering TRANSFER_DATA.
this->command(0x04);
this->command(CMD_POWER_ON);
// Give the busy line time to assert before the state machine polls it
this->next_delay_ = 100;
return true;
@@ -27,21 +26,17 @@ void EPaperUC8179::set_refresh_mode_() {
return; // plain full refresh uses the mode set by the init sequence
}
// Fast and partial refresh use flipped data polarity and a floating border
this->cmd_data(0x50, {0xA9, 0x07});
this->cmd_data(CMD_CDI, {0xA9, 0x07});
// Force the waveform via the temperature registers: 0x5A selects the fast
// full-refresh waveform, 0x6E the partial-refresh waveform
this->cmd_data(0xE0, {0x02});
this->cmd_data(CMD_CCSET, {0x02});
if (this->partial_) {
this->cmd_data(0xE5, {0x6E});
this->command(0x91); // enter partial mode
// Set the partial window to the full screen
const uint16_t x_end = this->width_ - 1;
const uint16_t y_end = this->height_ - 1;
this->cmd_data(0x90, {0x00, 0x00, static_cast<uint8_t>(x_end >> 8), static_cast<uint8_t>(x_end & 0xFF), 0x00, 0x00,
static_cast<uint8_t>(y_end >> 8), static_cast<uint8_t>(y_end & 0xFF), 0x01});
this->cmd_data(CMD_TSSET, {0x6E});
// Partial mode, with the window set to the full screen
this->set_partial_window_(0, this->width_ - 1, 0, this->height_ - 1);
} else {
this->cmd_data(0xE5, {0x5A});
this->command(0x92); // exit partial mode
this->cmd_data(CMD_TSSET, {0x5A});
this->command(CMD_PARTIAL_OUT); // exit partial mode
}
}
@@ -63,7 +58,7 @@ bool HOT EPaperUC8179::transfer_data() {
// Phase 1 (fast full refresh only): previous image via 0x10 (DTM1), inverse of the new image
if (two_pass && this->current_data_index_ < buffer_length) {
if (this->current_data_index_ == 0) {
this->command(0x10); // DATA START TRANSMISSION 1 (previous image)
this->command(CMD_DTM1); // DATA START TRANSMISSION 1 (previous image)
}
this->start_data_();
while (this->current_data_index_ < buffer_length) {
@@ -86,7 +81,7 @@ bool HOT EPaperUC8179::transfer_data() {
const size_t total = offset + buffer_length;
if (this->current_data_index_ < total) {
if (this->current_data_index_ == offset) {
this->command(0x13); // DATA START TRANSMISSION 2 (new image)
this->command(CMD_DTM2); // DATA START TRANSMISSION 2 (new image)
}
this->start_data_();
while (this->current_data_index_ < total) {
@@ -117,7 +112,7 @@ void EPaperUC8179::power_on() {
void EPaperUC8179::refresh_screen(bool /*partial*/) {
ESP_LOGV(TAG, "Refresh");
this->command(0x12); // DISPLAY REFRESH
this->command(CMD_REFRESH); // DISPLAY REFRESH
// Delay the next busy poll: the busy line takes a short time to assert after
// the refresh command, and polling too early would read it as already idle
this->next_delay_ = 100;
@@ -125,15 +120,7 @@ void EPaperUC8179::refresh_screen(bool /*partial*/) {
void EPaperUC8179::power_off() {
ESP_LOGV(TAG, "Power off");
this->command(0x02); // POWER OFF
}
void EPaperUC8179::deep_sleep() {
// Deep sleep loses the previous-image RAM that partial refresh compares against
if (!this->is_using_partial_update_()) {
ESP_LOGV(TAG, "Deep sleep");
this->cmd_data(0x07, {0xA5}); // DEEP SLEEP with check code
}
this->command(CMD_POWER_OFF); // POWER OFF
}
} // namespace esphome::epaper_spi
@@ -1,6 +1,6 @@
#pragma once
#include "epaper_spi.h"
#include "epaper_spi_ultrachip.h"
namespace esphome::epaper_spi {
@@ -28,11 +28,11 @@ namespace esphome::epaper_spi {
* waveform is forced, partial mode is entered with a full-screen window and
* only DTM2 is sent; the controller compares against its previous-image RAM.
*/
class EPaperUC8179 final : public EPaperBase {
class EPaperUC8179 final : public EPaperUltraChip {
public:
EPaperUC8179(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length)
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {}
: EPaperUltraChip(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {}
protected:
bool initialise(bool partial) override;
@@ -40,11 +40,7 @@ class EPaperUC8179 final : public EPaperBase {
void refresh_screen(bool partial) override;
void power_on() override;
void power_off() override;
void deep_sleep() override;
void set_refresh_mode_();
// Set by initialise() so transfer_data() knows which planes to send
bool partial_{};
};
} // namespace esphome::epaper_spi
@@ -0,0 +1,144 @@
#include "epaper_spi_uc8279.h"
#include <algorithm>
#include <cstring>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::epaper_spi {
ESPHOME_LOG_TAG(TAG, "epaper_spi.uc8279");
static constexpr uint8_t CMD_PSR = 0x00;
static constexpr uint8_t CMD_PFS = 0x03;
static constexpr uint8_t CMD_GATE_SCAN = 0xE1;
static constexpr uint8_t PSR0_OTP_LUT = 0x17;
static constexpr uint8_t PSR1 = 0x4D;
static constexpr uint8_t PFS = 0x20;
static constexpr uint8_t GATE_SCAN = 0x02;
static constexpr uint8_t CCSET = 0x02;
static constexpr uint8_t TSSET_FULL = 0x1E;
static constexpr uint8_t TSSET_PARTIAL = 0x5A;
static constexpr uint8_t CDI_FULL = 0x97;
static constexpr uint8_t CDI_PARTIAL = 0xD7;
static constexpr uint16_t RESET_SETTLE_MS = 50;
static constexpr uint16_t BUSY_ASSERT_MS = 10;
static constexpr uint16_t REFRESH_START_MS = 50;
bool EPaperUC8279::reset() {
// A partial update follows one that left the controller configured, in standby, with DTM1 holding the image
if (this->partial_)
return true;
if (!EPaperBase::reset())
return false;
this->next_delay_ = RESET_SETTLE_MS;
return true;
}
bool EPaperUC8279::initialise(bool partial) {
if (partial)
return true;
return EPaperBase::initialise(partial);
}
void EPaperUC8279::write_gate_(size_t gate, bool white) {
const size_t row_width = this->row_width_;
if (white || gate < this->offset_height_) {
uint8_t white_bytes[MAX_TRANSFER_SIZE];
memset(white_bytes, 0xFF, sizeof(white_bytes));
for (size_t written = 0; written != row_width;) {
const size_t length = std::min(row_width - written, MAX_TRANSFER_SIZE);
this->write_array(white_bytes, length);
written += length;
}
return;
}
size_t index = (gate - this->offset_height_) * row_width;
for (size_t remaining = row_width; remaining != 0;) {
size_t length;
const uint8_t *data = this->buffer_.get_span(index, length);
length = std::min(length, remaining);
this->write_array(data, length);
index += length;
remaining -= length;
}
}
// One plane of one gate's row per step, time-sliced; `base` is the value of current_data_index_ at its first gate
bool HOT EPaperUC8279::write_plane_(uint8_t command, bool white, size_t base) {
const uint32_t start_time = millis();
const size_t gates = this->gate_count_();
if (this->current_data_index_ == base)
this->command(command);
this->start_data_();
while (this->current_data_index_ - base != gates) {
this->write_gate_(this->current_data_index_ - base, white);
++this->current_data_index_;
if (this->current_data_index_ - base != gates && millis() - start_time > MAX_TRANSFER_TIME) {
this->disable();
return false;
}
}
this->disable();
return true;
}
// DTM2 gets the new image; a full refresh also writes a white DTM1, so every pixel transitions
bool HOT EPaperUC8279::transfer_data() {
const size_t gates = this->gate_count_();
if (this->current_data_index_ < gates) {
if (!this->write_plane_(CMD_DTM2, false, 0))
return false;
// Start DTM1 on the next pass, so this one stays within its time slice
if (!this->partial_)
return false;
}
if (!this->partial_ && !this->write_plane_(CMD_DTM1, true, gates))
return false;
this->current_data_index_ = 0;
return true;
}
void EPaperUC8279::power_on() {
ESP_LOGV(TAG, "Power on");
this->cmd_data(CMD_CDI, {this->partial_ ? CDI_PARTIAL : CDI_FULL});
this->cmd_data(CMD_CCSET, {CCSET});
this->cmd_data(CMD_TSSET, {this->partial_ ? TSSET_PARTIAL : TSSET_FULL});
if (this->partial_) {
this->cmd_data(CMD_PFS, {PFS});
this->cmd_data(CMD_GATE_SCAN, {GATE_SCAN});
}
this->command(CMD_POWER_ON);
this->next_delay_ = BUSY_ASSERT_MS;
}
void EPaperUC8279::refresh_screen(bool partial) {
ESP_LOGV(TAG, "Refresh screen (%s)", partial ? LOG_STR_LITERAL("partial") : LOG_STR_LITERAL("full"));
if (partial)
this->set_partial_window_(0, this->width_ - 1, this->offset_height_, this->gate_count_() - 1);
// Power-on reloads the panel's stored settings, so the panel setting only takes when written after it
this->cmd_data(CMD_PSR, {PSR0_OTP_LUT, PSR1});
this->command(CMD_REFRESH);
this->next_delay_ = REFRESH_START_MS;
}
bool HOT EPaperUC8279::after_refresh() {
// While partial updates are in use, DTM1 holds the image on the panel for the next partial comparison
if (!this->is_using_partial_update_())
return true;
if (this->current_data_index_ == 0 && this->partial_)
this->command(CMD_PARTIAL_OUT);
if (!this->write_plane_(CMD_DTM1, false, 0))
return false;
this->current_data_index_ = 0;
return true;
}
void EPaperUC8279::power_off() {
this->command(CMD_POWER_OFF);
this->next_delay_ = BUSY_ASSERT_MS;
}
} // namespace esphome::epaper_spi
@@ -0,0 +1,46 @@
#pragma once
#include "epaper_spi_ultrachip.h"
namespace esphome::epaper_spi {
/**
* Monochrome e-paper displays using the UltraChip UC8279 controller, such as the Xteink X4 Pro.
*
* Buffer layout: 1 bit per pixel, 1=white, 0=black (the base class default).
*
* The controller scans offset_height + height gates; the panel is bonded to the last height of them,
* so every image plane is written for all gates, white outside the panel.
*
* A full refresh writes the new image to DTM2 and white to DTM1. A partial refresh writes only DTM2
* and drives the pixels that differ from DTM1, which therefore has to hold the image on the panel:
* while partial updates are in use it is rewritten after every refresh, in time slices like the
* image transfer. The panel loses both planes in its own deep sleep, so it then waits in standby
* between updates instead.
*/
class EPaperUC8279 : public EPaperUltraChip {
public:
EPaperUC8279(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, uint16_t offset_height)
: EPaperUltraChip(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY),
offset_height_(offset_height) {}
protected:
bool reset() override;
bool initialise(bool partial) override;
bool transfer_data() override;
void power_on() override;
void refresh_screen(bool partial) override;
void power_off() override;
bool after_refresh() override;
uint16_t gate_count_() const { return this->offset_height_ + this->height_; }
// Writes one gate's row of the image, or white for a gate outside the panel or when white is true
void write_gate_(size_t gate, bool white);
// Writes one plane in time slices, sending `command` at its first gate; true once all gates are written
bool write_plane_(uint8_t command, bool white, size_t base);
uint16_t offset_height_;
};
} // namespace esphome::epaper_spi
@@ -0,0 +1,33 @@
#include "epaper_spi_ultrachip.h"
#include "esphome/core/log.h"
namespace esphome::epaper_spi {
ESPHOME_LOG_TAG(TAG, "epaper_spi.ultrachip");
static constexpr uint8_t hi(uint16_t v) { return static_cast<uint8_t>(v >> 8); }
static constexpr uint8_t lo(uint16_t v) { return static_cast<uint8_t>(v & 0xFF); }
void EPaperUltraChip::deep_sleep() {
// Partial updates compare against the previous image, which deep sleep would lose; standby keeps it
if (this->is_using_partial_update_())
return;
this->send_deep_sleep_();
}
void EPaperUltraChip::send_deep_sleep_() {
ESP_LOGV(TAG, "Deep sleep");
this->cmd_data(CMD_DEEP_SLEEP, {DEEP_SLEEP_CHECK});
}
void EPaperUltraChip::on_safe_shutdown() { this->send_deep_sleep_(); }
void EPaperUltraChip::set_partial_window_(uint16_t x_start, uint16_t x_end, uint16_t y_start, uint16_t y_end) {
this->command(CMD_PARTIAL_IN);
this->cmd_data(CMD_PARTIAL_WINDOW,
{hi(x_start), static_cast<uint8_t>(lo(x_start) & 0xF8), hi(x_end),
static_cast<uint8_t>(lo(x_end) | 0x07), hi(y_start), lo(y_start), hi(y_end), lo(y_end), 0x01});
}
} // namespace esphome::epaper_spi
@@ -0,0 +1,43 @@
#pragma once
#include "epaper_spi.h"
namespace esphome::epaper_spi {
/**
* Shared base for the UltraChip UC81xx mono controllers (UC8179, UC8279).
*
* They use the same command set and the same deep-sleep and partial-window handling; the
* concrete drivers differ only in how each refresh is configured and which planes are sent.
*/
class EPaperUltraChip : public EPaperBase {
public:
using EPaperBase::EPaperBase;
// Nothing carries the image across a restart or the controller's deep sleep, so sleep the panel rather than
// leaving it waiting in standby
void on_safe_shutdown() override;
protected:
static constexpr uint8_t CMD_POWER_OFF = 0x02;
static constexpr uint8_t CMD_POWER_ON = 0x04;
static constexpr uint8_t CMD_DEEP_SLEEP = 0x07;
static constexpr uint8_t CMD_DTM1 = 0x10;
static constexpr uint8_t CMD_REFRESH = 0x12;
static constexpr uint8_t CMD_DTM2 = 0x13;
static constexpr uint8_t CMD_CDI = 0x50;
static constexpr uint8_t CMD_PARTIAL_WINDOW = 0x90;
static constexpr uint8_t CMD_PARTIAL_IN = 0x91;
static constexpr uint8_t CMD_PARTIAL_OUT = 0x92;
static constexpr uint8_t CMD_CCSET = 0xE0;
static constexpr uint8_t CMD_TSSET = 0xE5;
static constexpr uint8_t DEEP_SLEEP_CHECK = 0xA5;
void deep_sleep() override;
// Sends the deep-sleep command unconditionally; deep_sleep() skips it while partial updates are in use
void send_deep_sleep_();
// Enters partial mode and sets the refresh window; x is rounded out to whole bytes
void set_partial_window_(uint16_t x_start, uint16_t x_end, uint16_t y_start, uint16_t y_end);
};
} // namespace esphome::epaper_spi
@@ -8,15 +8,19 @@ bool EpaperWaveshare::initialise(bool partial) {
EPaperBase::initialise(partial);
if (partial) {
this->cmd_data(0x32, this->partial_lut_, this->partial_lut_length_);
this->send_init_sequence_(this->partial_sequence_, this->partial_sequence_length_);
this->cmd_data(0x3C, {0x80});
this->cmd_data(0x22, {0xC0});
this->command(0x20);
this->next_delay_ = 100;
} else {
this->cmd_data(0x32, this->lut_, this->lut_length_);
this->send_init_sequence_(this->full_sequence_, this->full_sequence_length_);
this->cmd_data(0x3C, {0x05});
}
this->send_red_ = true;
// The previous image must stay in the base RAM for partial updates to compare against,
// so models needing it write the base RAM on full updates only.
this->send_red_ = !(this->base_image_required_ && partial);
return true;
}
@@ -45,4 +49,13 @@ void EpaperWaveshare::refresh_screen(bool partial) {
}
void EpaperWaveshare::deep_sleep() { this->cmd_data(0x10, {0x01}); }
bool EpaperWaveshare::reset() {
// Skip the software reset (0x12) EPaperMono adds: it restarts the alternation between the two RAM
// banks, so a partial update would compare against the wrong bank. The RST pulse only wakes the
// controller from sleep.
if (this->must_keep_base_image_())
return this->hardware_reset_();
return EPaperMono::reset();
}
} // namespace esphome::epaper_spi
@@ -10,21 +10,39 @@ class EpaperWaveshare final : public EPaperMono {
public:
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,
uint16_t partial_lut_length)
uint16_t partial_lut_length, const uint8_t *full_sequence = nullptr, size_t full_sequence_length = 0,
const uint8_t *partial_sequence = nullptr, size_t partial_sequence_length = 0,
bool base_image_required = false)
: EPaperMono(name, width, height, init_sequence, init_sequence_length),
lut_(lut),
lut_length_(lut_length),
partial_lut_(partial_lut),
partial_lut_length_(partial_lut_length) {}
full_sequence_(full_sequence),
partial_sequence_(partial_sequence),
lut_length_(lut_length),
full_sequence_length_(full_sequence_length),
partial_sequence_length_(partial_sequence_length),
partial_lut_length_(partial_lut_length),
base_image_required_(base_image_required) {
this->send_red_as_image_ = base_image_required;
}
protected:
bool initialise(bool partial) override;
void set_window() override;
void refresh_screen(bool partial) override;
void deep_sleep() override;
bool reset() override;
// True while the base image in the controller RAM must be preserved for the next update.
bool must_keep_base_image_() const { return this->base_image_required_ && this->update_count_ != 0; }
const uint8_t *lut_;
size_t lut_length_;
const uint8_t *partial_lut_;
// Commands sent after the LUT, e.g. voltage settings that differ between full and partial updates.
const uint8_t *full_sequence_;
const uint8_t *partial_sequence_;
size_t lut_length_;
size_t full_sequence_length_;
size_t partial_sequence_length_;
uint16_t partial_lut_length_;
bool base_image_required_;
};
} // namespace esphome::epaper_spi
@@ -1,12 +1,17 @@
from typing import Any, Self
import esphome.config_validation as cv
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_WIDTH
from esphome.const import CONF_DIMENSIONS, CONF_HEIGHT, CONF_OFFSET_HEIGHT, CONF_WIDTH
from esphome.core import CORE
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
def ultrachip_resolution(width: int, gates: int) -> tuple:
"""The UC81xx resolution command (0x61): source (width) and gate counts, each big-endian."""
return (0x61, width >> 8, width & 0xFF, gates >> 8, gates & 0xFF)
class EpaperModel:
models: dict[str, Self] = {}
@@ -86,6 +91,13 @@ class EpaperModel:
height = self.get_default(CONF_HEIGHT)
return width, height
def get_offset_height(self, config) -> int:
# The panel's first row within the controller's addressable area; from explicit dimensions or the model default
dimensions = config.get(CONF_DIMENSIONS, {})
return dimensions.get(
CONF_OFFSET_HEIGHT, self.get_default(CONF_OFFSET_HEIGHT, 0)
)
def extend(self, name, **kwargs) -> "EpaperModel":
"""
Extend the current model with additional parameters or a modified init sequence.
@@ -18,7 +18,7 @@ from typing import Any
from esphome.const import CONF_DATA_RATE
from . import EpaperModel
from . import EpaperModel, ultrachip_resolution
class UC8179(EpaperModel):
@@ -51,13 +51,7 @@ class UC8179(EpaperModel):
# PANEL SETTING (black/white mode, LUT from OTP)
(0x00, 0x1F),
# RESOLUTION SETTING (width x height)
(
0x61,
(width >> 8) & 0xFF,
width & 0xFF,
(height >> 8) & 0xFF,
height & 0xFF,
),
ultrachip_resolution(width, height),
# DUAL SPI MODE (disabled)
(0x15, 0x00),
# VCOM AND DATA INTERVAL SETTING
@@ -0,0 +1,74 @@
"""Monochrome e-paper displays using the UltraChip UC8279 controller.
Supported models:
- uc8279: the controller on its own; dimensions and pins must be configured
- xteink-x4pro-uc8279: the Xteink X4 Pro e-reader (ESP32-S3) with its 800x480 UC8279 panel;
the X4 Pro also ships with other panel controllers
The controller scans offset_height + height gates, and the panel is bonded to the last
height of them; offset_height (under dimensions) is a property of the panel.
"""
from typing import Any
import esphome.config_validation as cv
from esphome.const import CONF_DATA_RATE, CONF_DIMENSIONS, CONF_OFFSET_HEIGHT
from esphome.types import ConfigType
from . import EpaperModel, ultrachip_resolution
class UC8279(EpaperModel):
def __init__(
self,
name: str,
class_name: str = "EPaperUC8279",
data_rate: str = "10MHz",
**defaults: Any,
) -> None:
defaults.setdefault(CONF_DATA_RATE, data_rate)
defaults.setdefault("partial_update", True)
defaults.setdefault(CONF_OFFSET_HEIGHT, 0)
defaults.setdefault("width_multiple", 8)
super().__init__(name, class_name=class_name, **defaults)
def validate_config(self, config: ConfigType) -> ConfigType:
_width, height = self.get_dimensions(config)
if self.get_offset_height(config) + height > 0xFFFF:
raise cv.Invalid(
f"{CONF_OFFSET_HEIGHT} plus the panel height must not exceed {0xFFFF} gates",
path=[CONF_DIMENSIONS, CONF_OFFSET_HEIGHT],
)
return config
def get_constructor_args(self, config) -> tuple:
return (self.get_offset_height(config),)
# fmt: off
def get_init_sequence(self, config: dict) -> tuple:
width, height = self.get_dimensions(config)
gates = self.get_offset_height(config) + height
return (
(0x00, 0x37, 0x4D), # panel setting; the driver switches it to the OTP LUT before each refresh
ultrachip_resolution(width, gates), # resolution: sources, gates
(0x65, 0x00, 0x00, 0x00, 0x00), # gate and source start
(0x03, 0x20), # power off sequence
(0x30, 0x0E), # PLL
(0xE1, 0x02), # gate scan
)
# fmt: on
uc8279 = UC8279("uc8279")
uc8279.extend(
"xteink-x4pro-uc8279",
width=800,
height=480,
offset_height=120,
cs_pin=13,
dc_pin=18,
reset_pin=14,
busy_pin={"number": 6, "inverted": True},
reset_duration="50ms",
)
+106 -19
View File
@@ -1,5 +1,10 @@
from collections.abc import Sequence
from typing import Any
import esphome.codegen as cg
from esphome.components.mipi import flatten_sequence
from esphome.core import ID
from esphome.types import ConfigType
from ..display import CONF_INIT_SEQUENCE_ID
from . import EpaperModel
@@ -7,34 +12,49 @@ from . import EpaperModel
class WaveshareModel(EpaperModel):
def __init__(
self, name, lut, lut_partial=None, class_name="EpaperWaveshare", **defaults
):
self,
name: str,
lut: tuple[int, ...],
lut_partial: tuple[int, ...] | None = None,
sequence_full: tuple[tuple[int, ...], ...] = (),
sequence_partial: tuple[tuple[int, ...], ...] = (),
base_image_required: bool = False,
class_name: str = "EpaperWaveshare",
**defaults: Any,
) -> None:
# A partial LUT is what lets EpaperWaveshare do partial refresh
defaults.setdefault("partial_update", lut_partial is not None)
super().__init__(name, class_name=class_name, **defaults)
self.lut = lut
self.lut_partial = lut_partial
self.sequence_full = sequence_full
self.sequence_partial = sequence_partial
self.base_image_required = base_image_required
def get_constructor_args(self, config) -> tuple:
lut = (
def _array_arg(
self, config: ConfigType, suffix: str, data: Sequence[int] | None
) -> tuple:
if not data:
return cg.nullptr, 0
return (
cg.static_const_array(
ID(config[CONF_INIT_SEQUENCE_ID].id + "_lut", type=cg.uint8), self.lut
ID(config[CONF_INIT_SEQUENCE_ID].id + suffix, type=cg.uint8), data
),
len(self.lut),
len(data),
)
def get_constructor_args(self, config: ConfigType) -> tuple:
return (
*self._array_arg(config, "_lut", self.lut),
*self._array_arg(config, "_lut_partial", self.lut_partial),
*self._array_arg(
config, "_sequence_full", flatten_sequence(self.sequence_full)
),
*self._array_arg(
config, "_sequence_partial", flatten_sequence(self.sequence_partial)
),
self.base_image_required,
)
if self.lut_partial is None:
lut_partial = cg.nullptr, 0
else:
lut_partial = (
cg.static_const_array(
ID(
config[CONF_INIT_SEQUENCE_ID].id + "_lut_partial", type=cg.uint8
),
self.lut_partial,
),
len(self.lut_partial),
)
return *lut, *lut_partial
# fmt: off
@@ -90,3 +110,70 @@ WaveshareModel(
0x0, 0x0, 0x0,
),
)
WaveshareModel(
"waveshare-2.9in-v2",
width=128,
height=296,
initsequence=(
(0x01, 0x27, 0x01, 0x00), # driver output control
(0x0C, 0xD7, 0xD6, 0x9D), # booster soft start control
(0x3A, 0x1A), # dummy line period
(0x3B, 0x08), # gate line width
(0x11, 0x03), # data entry mode
(0x21, 0x00, 0x80), # display update control
(0x18, 0x80), # select internal temperature sensor
),
# The last 6 bytes of the LUTs in the Waveshare sample code are voltage settings
# rather than waveform data, and are sent as separate commands below.
lut=(
0x80, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x10, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x80, 0x66, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x66, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x14, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x0A, 0x00,
0x0A, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x08, 0x00, 0x01,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44,
0x00, 0x00, 0x00,
),
lut_partial=(
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x00, 0x00, 0x00,
),
sequence_full=(
(0x3F, 0x22), # option for LUT end
(0x03, 0x17), # gate voltage
(0x04, 0x41, 0x00, 0x32), # source voltage: VSH1, VSH2, VSL
(0x2C, 0x36), # VCOM
),
sequence_partial=(
(0x3F, 0x22), # option for LUT end
(0x03, 0x17), # gate voltage
(0x04, 0x41, 0xB0, 0x32), # source voltage: VSH1, VSH2, VSL
(0x2C, 0x36), # VCOM
(0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00),
),
base_image_required=True,
)
@@ -69,16 +69,8 @@ const LogString *deemphasis_to_str(DeemphasisMode mode) {
} // namespace
void ES9038Q2MKatana::setup() {
uint8_t chip_id = 0;
if (!this->read_byte(KATANA_REG_CHIP_ID, &chip_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
if (!this->check_chip_id(this, KATANA_REG_CHIP_ID, KATANA_CHIP_ID))
return;
}
if (chip_id != KATANA_CHIP_ID) {
ESP_LOGE(TAG, "Unexpected chip ID 0x%02X", chip_id);
this->mark_failed(LOG_STR("Unexpected chip ID"));
return;
}
if (!this->write_byte(KATANA_REG_RESET, 0x01)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
+5 -4
View File
@@ -1183,7 +1183,7 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
# See: https://github.com/pioarduino/esp-idf/releases
ARDUINO_IDF_VERSION_LOOKUP = {
cv.Version(4, 0, 0, "alpha1"): cv.Version(6, 0, 1),
cv.Version(3, 3, 12): cv.Version(5, 5, 5),
cv.Version(3, 3, 12): cv.Version(5, 5, 6),
cv.Version(3, 3, 11): cv.Version(5, 5, 5),
cv.Version(3, 3, 10): cv.Version(5, 5, 5),
cv.Version(3, 3, 9): cv.Version(5, 5, 4),
@@ -1207,9 +1207,9 @@ ARDUINO_IDF_VERSION_LOOKUP = {
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(5, 5, 5),
"latest": cv.Version(5, 5, 5),
"dev": cv.Version(5, 5, 5),
"recommended": cv.Version(5, 5, 6),
"latest": cv.Version(5, 5, 6),
"dev": cv.Version(5, 5, 6),
}
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
@@ -1219,6 +1219,7 @@ ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(
6, 0, 0
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
cv.Version(5, 5, 6): cv.Version(55, 3, 312),
cv.Version(5, 5, 5): cv.Version(55, 3, 312),
cv.Version(5, 5, 4): cv.Version(55, 3, 39),
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
+2 -35
View File
@@ -1,30 +1,21 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import i2c, sensor, text_sensor
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import (
CONF_CURRENT,
CONF_INTERRUPT_PIN,
CONF_ON_CONNECT,
CONF_ON_DISCONNECT,
CONF_ON_ERROR,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_FLASH,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
UNIT_VOLT,
)
from esphome.types import ConfigType
AUTO_LOAD = ["sensor", "text_sensor"]
CODEOWNERS = ["@remcom"]
DEPENDENCIES = ["i2c"]
DOMAIN = "fusb302b"
CONF_CONTRACT = "contract"
CONF_FUSB302B_ID = "fusb302b_id"
CONF_ON_POWER_READY = "on_power_ready"
CONF_ON_REQUEST_FAILED = "on_request_failed"
CONF_REQUEST_VOLTAGE = "request_voltage"
@@ -47,24 +38,6 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(FUSB302B),
cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pullup_pin_schema,
cv.Required(CONF_REQUEST_VOLTAGE): validate_voltage,
cv.Optional(CONF_CONTRACT): text_sensor.text_sensor_schema(
icon=ICON_FLASH,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ON_CONNECT): automation.validate_automation({}),
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation({}),
cv.Optional(CONF_ON_POWER_READY): automation.validate_automation({}),
@@ -101,12 +74,6 @@ async def to_code(config: ConfigType) -> None:
var = await i2c.new_i2c_component(config, interrupt_pin)
cg.add(var.set_request_voltage(config[CONF_REQUEST_VOLTAGE]))
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
await sensors(CONF_CURRENT, var.set_current_sensor)
text_sensors = text_sensor.sub_text_sensors(config)
await text_sensors(CONF_CONTRACT, var.set_contract_text_sensor)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
+45
View File
@@ -0,0 +1,45 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_CURRENT,
CONF_VOLTAGE,
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_VOLTAGE,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_AMPERE,
UNIT_VOLT,
)
from esphome.types import ConfigType
from . import CONF_FUSB302B_ID, FUSB302B
DEPENDENCIES = ["fusb302b"]
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_FUSB302B_ID): cv.use_id(FUSB302B),
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=1,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
unit_of_measurement=UNIT_AMPERE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_CURRENT,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_FUSB302B_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, hub.set_voltage_sensor)
await sensors(CONF_CURRENT, hub.set_current_sensor)
@@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_FLASH
from esphome.types import ConfigType
from . import CONF_FUSB302B_ID, FUSB302B
DEPENDENCIES = ["fusb302b"]
CONF_CONTRACT = "contract"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_FUSB302B_ID): cv.use_id(FUSB302B),
cv.Optional(CONF_CONTRACT): text_sensor.text_sensor_schema(
icon=ICON_FLASH,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_FUSB302B_ID])
text_sensors = text_sensor.sub_text_sensors(config)
await text_sensors(CONF_CONTRACT, hub.set_contract_text_sensor)
+1 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_PM_1_0,
CONF_PM_2_5,
CONF_PM_10_0,
@@ -113,9 +112,7 @@ TYPES = {
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
for key, funcName in TYPES.items():
if key in config:
+1 -3
View File
@@ -96,9 +96,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
# Pre-create all sensor variables so automations that reference
# sibling sensors don't deadlock waiting for unregistered IDs.
@@ -46,10 +46,7 @@ void HonWaterHeater::send_frame_(FrameType type, const uint8_t *payload, size_t
const size_t length = encode_frame(type, payload, size, wire);
if (length == 0)
return;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex[format_hex_pretty_size(sizeof(wire))];
ESP_LOGV(TAG, "TX type=%02X: %s", static_cast<uint8_t>(type), format_hex_pretty_to(hex, wire, length));
#endif
ESP_LOGV_HEX(TAG, wire, length, sizeof(wire), "TX type=%02X: %s", static_cast<uint8_t>(type));
this->write_array(wire, length);
}
@@ -221,11 +218,8 @@ void HonWaterHeater::confirm_control_(const Status &status) {
void HonWaterHeater::process_frame_() {
const uint8_t *frame = this->parser_.data();
const auto type = static_cast<FrameType>(frame[FRAME_TYPE_OFFSET]);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex[format_hex_pretty_size(MAX_STORED_FRAME)];
ESP_LOGV(TAG, "RX decoded type=%02X flags=%02X: %s", static_cast<uint8_t>(type), frame[FRAME_FLAGS_OFFSET],
format_hex_pretty_to(hex, frame, this->parser_.stored_size()));
#endif
ESP_LOGV_HEX(TAG, frame, this->parser_.stored_size(), MAX_STORED_FRAME, "RX decoded type=%02X flags=%02X: %s",
static_cast<uint8_t>(type), frame[FRAME_FLAGS_OFFSET]);
this->handle_frame_type_(type);
Status status{};
+1 -3
View File
@@ -51,9 +51,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_CO2, var.set_co2_sensor)
+1 -4
View File
@@ -75,10 +75,7 @@ void HDMICEC::loop() {
}
// One inbound frame per pass keeps the time spent in the automations bounded
if (const Frame *frame = this->frames_queue_.front()) {
#ifdef ESPHOME_LOG_HAS_DEBUG
char line[format_hex_pretty_size(Frame::MAX_LENGTH)];
ESP_LOGD(TAG, "Received: %s", format_hex_pretty_to(line, frame->bytes().data(), frame->size()));
#endif
ESP_LOGD_HEX(TAG, frame->bytes().data(), frame->size(), Frame::MAX_LENGTH, "Received: %s");
#ifdef HDMI_CEC_MESSAGE_CALLBACK_COUNT
this->message_callbacks_.call(frame->initiator_addr(), frame->destination_addr(), frame->payload());
#endif
+1 -4
View File
@@ -4,7 +4,6 @@ from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_ID,
CONF_NAME,
CONF_ON_ENROLLMENT_DONE,
CONF_ON_ENROLLMENT_FAILED,
@@ -90,9 +89,7 @@ _CALLBACK_AUTOMATIONS = (
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
+2 -5
View File
@@ -161,11 +161,8 @@ void HlkFm22xComponent::recv_command_() {
}
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type,
format_hex_pretty_to(hex_buf, this->recv_buf_.data(), to_store));
#endif
ESP_LOGV_HEX(TAG, this->recv_buf_.data(), to_store, HLK_FM22X_MAX_RESPONSE_SIZE, "Recv type: 0x%.2X, data: %s",
response_type);
byte = this->read();
if (byte != checksum) {
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_APPARENT_POWER,
CONF_CURRENT,
CONF_CURRENT_RESISTOR,
CONF_ID,
CONF_POWER,
CONF_POWER_FACTOR,
CONF_VOLTAGE,
@@ -75,9 +74,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
@@ -1,13 +1,18 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC
from esphome.const import (
DEVICE_CLASS_CONNECTIVITY,
DEVICE_CLASS_PROBLEM,
ENTITY_CATEGORY_DIAGNOSTIC,
)
from esphome.types import ConfigType
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp, hoermann_hcp_ns
DEPENDENCIES = ["hoermann_hcp"]
CONF_ACTUATOR_ERROR = "actuator_error"
CONF_IS_CONNECTED = "is_connected"
HoermannHcpConnectedBinarySensor = hoermann_hcp_ns.class_(
@@ -23,14 +28,20 @@ CONFIG_SCHEMA = cv.All(
device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.COMPONENT_SCHEMA),
cv.Optional(CONF_ACTUATOR_ERROR): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_PROBLEM,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
),
cv.has_at_least_one_key(CONF_IS_CONNECTED),
cv.has_at_least_one_key(CONF_IS_CONNECTED, CONF_ACTUATOR_ERROR),
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
if (conf := config.get(CONF_IS_CONNECTED)) is not None:
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
var = await binary_sensor.new_binary_sensor(conf, parent)
var = await binary_sensor.new_binary_sensor(conf, hub)
await cg.register_component(var, conf)
binary_sensors = binary_sensor.sub_binary_sensors(config)
await binary_sensors(CONF_ACTUATOR_ERROR, hub.set_actuator_error_binary_sensor)
@@ -187,6 +187,9 @@ void HoermannHcp::dump_config() {
"Hoermann HCP bridge:\n"
" Modbus server address: 0x%02X",
this->get_address());
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Actuator Error", this->actuator_error_binary_sensor_);
#endif
#ifdef USE_HOERMANN_HCP_IDENTITY
LOG_TEXT_SENSOR(" ", "Serial Number", this->serial_number_text_sensor_);
log_identity_value(this->serial_number_text_sensor_);
@@ -551,10 +554,25 @@ void HoermannHcp::on_state_reg_(uint16_t value) {
}
// Low byte of register 6: bit 0x10 is the lamp, bit 0x04 the relay. The reference implementation records
// 0x00, 0x04, 0x10 and 0x14, so only the lamp bit decides here.
// 0x00, 0x04, 0x10 and 0x14, so only the lamp bit decides here. In the high byte, Hoermann's own bus accessory
// treats either bit of 0x30 as an actuator error; what sets them is not known.
void HoermannHcp::on_light_reg_(uint16_t value) {
this->set_light_seen_(true);
this->set_light_on_((value & 0x0010) != 0);
const bool actuator_error = (value & 0x3000) != 0;
#ifdef USE_BINARY_SENSOR
// Unknown until first reported, then kept like the other entities here while the bus is quiet.
if (this->actuator_error_binary_sensor_ != nullptr)
this->actuator_error_binary_sensor_->publish_state(actuator_error);
#endif
if (this->actuator_error_ == actuator_error)
return;
this->actuator_error_ = actuator_error;
if (actuator_error) {
ESP_LOGW(TAG, "Motor reports an actuator error (0x%04X)", value);
} else {
ESP_LOGI(TAG, "Actuator error cleared");
}
}
bool HoermannHcp::queue_command_(const HoermannHcpCommand &command) {
@@ -6,6 +6,9 @@
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_HOERMANN_HCP_IDENTITY
#include "esphome/components/text_sensor/text_sensor.h"
#endif
@@ -55,6 +58,9 @@ struct HoermannHcpCommand {
};
class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(actuator_error)
#endif
#ifdef USE_HOERMANN_HCP_IDENTITY
// The motor is asked for these only when one of them is configured.
SUB_TEXT_SENSOR(serial_number)
@@ -186,6 +192,8 @@ class HoermannHcp : public PollingComponent, public modbus::ModbusServerDevice {
bool light_requested_{false};
bool light_command_sent_{false};
bool light_target_{false};
// Last reported, kept across a quiet bus so a reconnect logs only a real change.
bool actuator_error_{false};
bool door_state_seen_{false};
bool short_broadcast_logged_{false};
// Only the read half right after a 0x17 write carries a command, so a second read without a new write does not.
+30 -1
View File
@@ -1,8 +1,11 @@
from collections.abc import Callable, Iterable
from dataclasses import dataclass
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID, CONF_INTERNAL
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
from esphome.cpp_generator import MockObj
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -59,7 +62,28 @@ def final_validate_entity_id(config: ConfigType) -> ConfigType:
return config
def setup_home_assistant_entity(var: MockObj, config: ConfigType) -> None:
@dataclass
class HomeAssistantData:
state_subscription_count: int = 0
def _get_data() -> HomeAssistantData:
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = HomeAssistantData()
return CORE.data[DOMAIN]
@coroutine_with_priority(CoroPriority.FINAL)
async def _emit_state_subscription_count_define() -> None:
"""Emit the HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT define with the total count."""
count = _get_data().state_subscription_count
if count > 0:
cg.add_define("HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT", count)
def setup_home_assistant_entity(
var: MockObj, config: ConfigType, state_subscription_count: int = 1
) -> None:
from esphome.components.api import wizard
if (buffer := wizard.wizard_input_buffer(config[CONF_ID])) is not None:
@@ -70,3 +94,8 @@ def setup_home_assistant_entity(var: MockObj, config: ConfigType) -> None:
if CONF_ATTRIBUTE in config:
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
cg.add_define("USE_API_HOMEASSISTANT_STATES")
if state_subscription_count > 0:
data = _get_data()
if data.state_subscription_count == 0:
CORE.add_job(_emit_state_subscription_count_define)
data.state_subscription_count += state_subscription_count
@@ -37,4 +37,5 @@ async def to_code(config: ConfigType) -> None:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
var = await button.new_button(config)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)
# Button only calls services, does not subscribe to state
setup_home_assistant_entity(var, config, state_subscription_count=0)
@@ -39,4 +39,5 @@ async def to_code(config: ConfigType) -> None:
step=0,
)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)
# 1 state + min/max/step fetches in HomeassistantNumber::setup()
setup_home_assistant_entity(var, config, state_subscription_count=4)
@@ -55,4 +55,5 @@ async def to_code(config: ConfigType) -> None:
options=[],
)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)
# 1 state + options fetch in HomeassistantSelect::setup()
setup_home_assistant_entity(var, config, state_subscription_count=2)
@@ -37,4 +37,5 @@ async def to_code(config: ConfigType) -> None:
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
var = await text.new_text(config)
await cg.register_component(var, config)
setup_home_assistant_entity(var, config)
# 1 state + min/max/mode fetches in HomeassistantText::setup()
setup_home_assistant_entity(var, config, state_subscription_count=4)
+1 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import sensor, spi
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
@@ -52,9 +51,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
var = await spi.new_spi_component(config)
sensors = sensor.sub_sensors(config)
if await sensors(CONF_PRESSURE, var.set_pressure_sensor):
@@ -10,6 +10,7 @@
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
// Include BearSSL error constants for TLS failure diagnostics
#ifdef USE_ESP8266
@@ -39,7 +40,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const char *url, cons
const uint32_t start = millis();
bool secure = strstr(url, "https:") != nullptr;
bool secure = ESPHOME_strstr_P(url, ESPHOME_PSTR("https:")) != nullptr;
container->set_secure(secure);
container->set_watchdog_timeout(this->get_watchdog_timeout(timeout_ms));
+1 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MODEL,
CONF_MOISTURE,
CONF_RESOLUTION,
@@ -140,10 +139,8 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
cg.add_define("USE_LINE_READER")
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
cg.add_define(
"HYDREON_RGXX_PROTOCOL_LIST(F, sep)",
+14
View File
@@ -75,6 +75,15 @@ bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
return true;
}
bool I2CDevice::update_register(uint8_t a_register, uint8_t mask, uint8_t value) {
uint8_t data;
if (!this->read_byte(a_register, &data))
return false;
const uint8_t updated = (data & ~mask) | (value & mask);
ESP_LOGV(TAG, "Register 0x%02X: 0x%02X -> 0x%02X", a_register, data, updated);
return this->write_byte(a_register, updated);
}
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const {
const size_t size = 1 + len * 2;
SmallBufferWithHeapFallback<17> buffer_alloc(size); // Register plus up to 8 words
@@ -94,6 +103,11 @@ bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) const {
return this->bus_->write_readv(this->address_, buffer, sizeof(buffer), nullptr, 0) == ERROR_OK;
}
bool I2CDevice::write_byte_16_le(uint8_t a_register, uint16_t data) const {
const uint8_t buffer[3] = {a_register, static_cast<uint8_t>(data), static_cast<uint8_t>(data >> 8)};
return this->bus_->write_readv(this->address_, buffer, sizeof(buffer), nullptr, 0) == ERROR_OK;
}
I2CRegister &I2CRegister::operator=(uint8_t value) {
this->parent_->write_register(this->register_, &value, 1);
return *this;
+23
View File
@@ -280,6 +280,21 @@ class I2CDevice {
bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
/// Read len 16-bit values stored least significant byte first; no conversion is compiled on little-endian hosts.
bool read_bytes_16_le(uint8_t a_register, uint16_t *data, uint8_t len) {
if (this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2) != ERROR_OK)
return false;
for (size_t i = 0; i < len; i++)
data[i] = convert_little_endian(data[i]);
return true;
}
bool read_bytes_16_le(uint8_t a_register, int16_t *data, uint8_t len) {
return this->read_bytes_16_le(a_register, reinterpret_cast<uint16_t *>(data), len);
}
/// Read a 16-bit value stored least significant byte first.
bool read_byte_16_le(uint8_t a_register, uint16_t *data) { return this->read_bytes_16_le(a_register, data, 1); }
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const {
return write_register(a_register, data, len) == ERROR_OK;
}
@@ -298,6 +313,14 @@ class I2CDevice {
bool write_byte_16(uint8_t a_register, uint16_t data) const;
/// Write a 16-bit value least significant byte first.
bool write_byte_16_le(uint8_t a_register, uint16_t data) const;
/// Read a register, replace the bits in @p mask with those of @p value and write it back.
/// The write always happens; bits of @p value outside @p mask are ignored. Logs both values at VERBOSE.
/// @return false if the read or the write failed (nothing is written after a failed read)
bool update_register(uint8_t a_register, uint8_t mask, uint8_t value);
protected:
uint8_t address_{0x00}; ///< store the address of the device on the bus
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
+1 -4
View File
@@ -111,10 +111,7 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe
return ERROR_NOT_INITIALIZED;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
ESP_LOGV_HEX(TAG, write_buffer, write_count, I2C_MAX_LOG_BYTES, "0x%02X TX %s", address);
uint8_t status = 0;
if (write_count != 0 || read_count == 0) {
+1 -4
View File
@@ -167,10 +167,7 @@ ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, s
jobs[num_jobs++].write.total_bytes = 1;
} else {
if (write_count != 0) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
#endif
ESP_LOGV_HEX(TAG, write_buffer, write_count, I2C_MAX_LOG_BYTES, "0x%02X TX %s", address);
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
@@ -354,11 +354,8 @@ void ImprovBLEComponent::process_incoming_data_() {
return;
uint8_t length = this->incoming_data_[1];
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(IMPROV_MAX_LOG_BYTES)];
ESP_LOGV(TAG, "Processing bytes - %s",
format_hex_pretty_to(hex_buf, this->incoming_data_.data(), this->incoming_data_.size()));
#endif
ESP_LOGV_HEX(TAG, this->incoming_data_.data(), this->incoming_data_.size(), IMPROV_MAX_LOG_BYTES,
"Processing bytes - %s");
if (this->incoming_data_.size() - 3 == length) {
this->set_error_(improv::ERROR_NONE);
improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_);
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_COMMAND,
CONF_CUSTOM,
CONF_FLOW,
CONF_ID,
CONF_POWER,
CONF_VOLUME,
DEVICE_CLASS_EMPTY,
@@ -113,9 +112,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
# Standard sensors
for key in [
+1 -3
View File
@@ -72,9 +72,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config)
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
+2 -8
View File
@@ -607,17 +607,11 @@ void LD2410Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Periodic Data: %s");
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Ack Data: %s");
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
AUTO_LOAD = ["ld24xx"]
@@ -37,6 +36,4 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config)
+2 -4
View File
@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_THROTTLE
from esphome.const import CONF_THROTTLE
from esphome.types import ConfigType
AUTO_LOAD = ["ld24xx"]
@@ -43,6 +43,4 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config)
+2 -8
View File
@@ -702,17 +702,11 @@ void LD2412Component::readline_(int readch) {
return; // Not enough data to process yet
}
if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Periodic Data: %s");
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Ack Data: %s");
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
CODEOWNERS = ["@descipher"]
@@ -36,6 +35,4 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config)
+2 -4
View File
@@ -3,7 +3,7 @@ import esphome.codegen as cg
from esphome.components import uart
from esphome.config_helpers import filter_source_files_from_defines
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE
from esphome.const import CONF_ON_DATA, CONF_THROTTLE
from esphome.types import ConfigType
AUTO_LOAD = ["ld24xx"]
@@ -63,7 +63,5 @@ FILTER_SOURCE_FILES = filter_source_files_from_defines(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
+2 -8
View File
@@ -829,17 +829,11 @@ void LD2450Component::readline_(int readch) {
}
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Periodic Data: %s");
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame
} else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Handling Ack Data: %s");
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
+2 -4
View File
@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_DATA
from esphome.const import CONF_ON_DATA
from esphome.types import ConfigType
AUTO_LOAD = ["ld24xx"]
@@ -41,7 +41,5 @@ _CALLBACK_AUTOMATIONS = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
+1 -4
View File
@@ -168,10 +168,7 @@ void LD2460Component::readline_(uint8_t readch) {
return;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_LINE_LENGTH)];
ESP_LOGV(TAG, "Frame: %s", format_hex_pretty_to(hex_buf, this->buffer_data_, this->buffer_pos_));
#endif
ESP_LOGV_HEX(TAG, this->buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH, "Frame: %s");
if (is_data) {
this->handle_periodic_data_();
+2 -4
View File
@@ -2,7 +2,7 @@ from esphome import pins
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_WAKEUP_PIN
from esphome.const import CONF_WAKEUP_PIN
from esphome.types import ConfigType
from .const import CONF_AUTO_WAKE, CONF_WAKEUP_PULSE
@@ -62,9 +62,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
if wakeup_pin_config := config.get(CONF_WAKEUP_PIN):
pin = await cg.gpio_pin_expression(wakeup_pin_config)
@@ -1,6 +1,8 @@
#pragma once
#ifdef USE_LIBRETINY
#include "esphome/core/defines.h"
#include <cstddef>
#include <cstdint>
@@ -20,6 +22,14 @@ class LibreTinyPreferenceBackend final {
uint32_t key;
struct fdb_kvdb *db;
struct fdb_blob *blob;
#ifdef USE_LIBRETINY_RTC_PREFERENCES
// storage: rtc lives under its own FlashDB name prefix, so the database can drop every rtc entry
// at once after a boot that RTC memory would not have survived
bool rtc{false};
bool is_rtc() const { return this->rtc; }
#else
static constexpr bool is_rtc() { return false; }
#endif
};
class LibreTinyPreferences;
+85 -10
View File
@@ -4,6 +4,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cstring>
#include <span>
#include <vector>
namespace esphome::libretiny {
@@ -13,20 +14,58 @@ ESPHOME_LOG_TAG(TAG, "preferences");
struct NVSData {
uint32_t key;
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
#ifdef USE_LIBRETINY_RTC_PREFERENCES
bool rtc;
bool is_rtc() const { return this->rtc; }
#else
static constexpr bool is_rtc() { return false; }
#endif
};
static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// The FlashDB name of a key: its decimal digits, with a prefix for storage: rtc so one pass over the
// database finds every rtc entry. Without rtc storage is_rtc() is a constant false and the branch folds away.
static constexpr size_t KEY_NAME_SIZE = UINT32_MAX_STR_SIZE + 1;
static constexpr char RTC_KEY_PREFIX = 'r';
static void key_name(char (&buf)[KEY_NAME_SIZE], uint32_t key, bool rtc) {
char *digits = buf;
if (rtc)
*digits++ = RTC_KEY_PREFIX;
uint32_to_str(std::span<char, UINT32_MAX_STR_SIZE>(digits, UINT32_MAX_STR_SIZE), key);
}
#ifdef USE_LIBRETINY_RTC_PREFERENCES
// Whether this boot followed a reset that RTC memory survives on ESP32 and ESP8266. LibreTiny chips
// have no such memory, so storage: rtc keeps preferences in flash and uses this to forget them.
static bool boot_kept_rtc_preferences() {
switch (lt_get_reboot_reason()) {
case REBOOT_REASON_SOFTWARE:
case REBOOT_REASON_WATCHDOG:
case REBOOT_REASON_CRASH:
case REBOOT_REASON_SLEEP_GPIO:
case REBOOT_REASON_SLEEP_RTC:
case REBOOT_REASON_SLEEP_USB:
return true;
default: // power-on, brown-out, reset pin, debugger, unknown
return false;
}
}
#endif // USE_LIBRETINY_RTC_PREFERENCES
bool LibreTinyPreferenceBackend::save(const uint8_t *data, size_t len) {
// try find in pending saves and update that
// try find in pending saves and update that; a flash and an rtc entry may share a key
for (auto &obj : s_pending_save) {
if (obj.key == this->key) {
if (obj.key == this->key && obj.is_rtc() == this->is_rtc()) {
obj.data.set(data, len);
return true;
}
}
NVSData save{};
save.key = this->key;
#ifdef USE_LIBRETINY_RTC_PREFERENCES
save.rtc = this->rtc;
#endif
save.data.set(data, len);
s_pending_save.push_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
@@ -36,7 +75,7 @@ bool LibreTinyPreferenceBackend::save(const uint8_t *data, size_t len) {
bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) {
// try find in pending saves and load from that
for (auto &obj : s_pending_save) {
if (obj.key == this->key) {
if (obj.key == this->key && obj.is_rtc() == this->is_rtc()) {
if (obj.data.size() != len) {
// size mismatch
return false;
@@ -46,8 +85,8 @@ bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) {
}
}
char key_str[UINT32_MAX_STR_SIZE];
uint32_to_str(key_str, this->key);
char key_str[KEY_NAME_SIZE];
key_name(key_str, this->key, this->is_rtc());
fdb_blob_make(this->blob, data, len);
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
if (actual_len != len) {
@@ -64,21 +103,57 @@ void LibreTinyPreferences::open() {
fdb_err_t err = fdb_kvdb_init(&this->db, "esphome", "kvs", NULL, NULL);
if (err != FDB_NO_ERR) {
LT_E("fdb_kvdb_init(...) failed: %d", err);
} else {
LT_I("Preferences initialized");
return;
}
LT_I("Preferences initialized");
#ifdef USE_LIBRETINY_RTC_PREFERENCES
if (!boot_kept_rtc_preferences())
this->discard_rtc_entries_();
#endif
}
#ifdef USE_LIBRETINY_RTC_PREFERENCES
ESPPreferenceObject LibreTinyPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
return ESPPreferenceObject(new LibreTinyPreferenceBackend(this->make_backend_(type, !in_flash)));
}
// One pass over the database deletes every rtc entry. FlashDB only marks a deleted entry's status in place
// and its iterator steps by address, so deleting the current entry is safe. This runs before the logger
// exists, so it logs through LibreTiny; a failed delete is a flash fault the next write will report as well.
void LibreTinyPreferences::discard_rtc_entries_() {
uint16_t discarded = 0;
fdb_kv_iterator iterator;
fdb_kv_iterator_init(&iterator);
while (fdb_kv_iterate(&this->db, &iterator)) {
const char *name = iterator.curr_kv.name;
if (name[0] != RTC_KEY_PREFIX)
continue;
if (const fdb_err_t err = fdb_kv_del(&this->db, name); err != FDB_NO_ERR) {
LT_E("Failed to discard rtc preference %s: %d", name, err);
continue;
}
discarded++;
}
if (discarded != 0) {
LT_D("Discarded %u rtc preferences after a hard reset", discarded);
}
}
#endif
ESPPreferenceObject LibreTinyPreferences::make_preference(size_t length, uint32_t type) {
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
return ESPPreferenceObject(new LibreTinyPreferenceBackend(this->make_backend_(type)));
}
LibreTinyPreferenceBackend LibreTinyPreferences::make_backend_(uint32_t type) {
LibreTinyPreferenceBackend LibreTinyPreferences::make_backend_(uint32_t type, bool rtc) {
LibreTinyPreferenceBackend backend;
backend.key = type;
backend.db = &this->db;
backend.blob = &this->blob;
#ifdef USE_LIBRETINY_RTC_PREFERENCES
backend.rtc = rtc;
#endif
return backend;
}
@@ -97,8 +172,8 @@ bool LibreTinyPreferences::sync() {
uint32_t last_key = 0;
for (const auto &save : s_pending_save) {
char key_str[UINT32_MAX_STR_SIZE];
uint32_to_str(key_str, save.key);
char key_str[KEY_NAME_SIZE];
key_name(key_str, save.key, save.is_rtc());
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
if (this->is_changed_(&this->db, save, key_str)) {
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
+10 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#ifdef USE_LIBRETINY
#include "esphome/core/defines.h"
#include "esphome/core/preference_backend.h"
#include <flashdb.h>
@@ -12,9 +13,14 @@ class LibreTinyPreferences final : public PreferencesMixin<LibreTinyPreferences>
public:
using PreferencesMixin<LibreTinyPreferences>::make_preference;
void open();
#ifdef USE_LIBRETINY_RTC_PREFERENCES
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash);
#else
// Without rtc storage every preference lives in flash; inline so builds that don't use rtc pay nothing
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) {
return this->make_preference(length, type);
}
#endif
ESPPreferenceObject make_preference(size_t length, uint32_t type);
/// One-shot read of a stored preference by key, without allocating a backend
bool load_from_key(uint32_t type, uint8_t *data, size_t len);
@@ -25,8 +31,11 @@ class LibreTinyPreferences final : public PreferencesMixin<LibreTinyPreferences>
struct fdb_blob blob;
protected:
LibreTinyPreferenceBackend make_backend_(uint32_t type);
LibreTinyPreferenceBackend make_backend_(uint32_t type, bool rtc = false);
bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str);
#ifdef USE_LIBRETINY_RTC_PREFERENCES
void discard_rtc_entries_();
#endif
};
void setup_preferences();
+1 -9
View File
@@ -27,16 +27,8 @@ static const uint16_t RATE_HZ[] PROGMEM = {1, 10, 25, 50, 100, 200, 400, 1620, 1
void LIS3DHComponent::setup() {
MotionComponent::setup();
uint8_t who_am_i = 0;
if (!this->read_byte(LIS3DH_REG_WHO_AM_I, &who_am_i)) {
this->mark_failed(LOG_STR("Failed to read chip ID - check wiring / address"));
if (!this->check_chip_id(this, LIS3DH_REG_WHO_AM_I, LIS3DH_WHO_AM_I_VALUE))
return;
}
if (who_am_i != LIS3DH_WHO_AM_I_VALUE) {
ESP_LOGE(TAG, "Wrong chip ID: 0x%02X (expected 0x%02X)", who_am_i, LIS3DH_WHO_AM_I_VALUE);
this->mark_failed(LOG_STR("Wrong chip ID"));
return;
}
if (!this->setup_accelerometer_() || !this->setup_temperature_() || !this->setup_interrupt_()) {
this->mark_failed(LOG_STR("Failed to configure sensor"));
+31 -7
View File
@@ -3,6 +3,9 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "logger.h"
#ifdef USE_CRASH_HANDLER
#include "esphome/components/zephyr_coredump/coredump.h"
#endif
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
@@ -12,12 +15,6 @@
#include <esphome/components/zephyr/reset_reason.h>
#endif
namespace esphome::zephyr_coredump {
__attribute__((weak)) void print_coredump() {}
} // namespace esphome::zephyr_coredump
namespace esphome::logger {
// Zephyr 3.7 renamed z_arch_esf_t to struct arch_esf; the old name was later removed.
@@ -56,6 +53,14 @@ void Logger::cdc_loop_() {
if (!opened) {
App.schedule_dump_config();
}
#ifdef USE_CRASH_HANDLER
// The new reader also gets a coredump stored by the last crash; a closed port keeps it for the next one
if (!opened) {
log_crash_to_client();
} else {
zephyr_coredump::stop_coredump();
}
#endif
opened = !opened;
}
#endif
@@ -103,7 +108,16 @@ void Logger::pre_setup() {
const char *reset_reason = zephyr::get_reset_reason(std::span<char, zephyr::RESET_REASON_BUFFER_SIZE>(reason_buffer));
ESP_LOGI(TAG, "Reset reason: %s", reset_reason);
dump_crash_();
zephyr_coredump::print_coredump();
#endif
#ifdef USE_CRASH_HANDLER
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
// Nobody reads a USB port this early; cdc_loop_() reports the dump once the port is opened
if (this->uart_ == UART_SELECTION_USB_CDC)
return;
#endif
// Without a UART (baud_rate: 0, or one that did not come up) nothing reaches the console
if (this->uart_dev_ != nullptr)
log_crash_to_client();
#endif
}
@@ -195,6 +209,16 @@ void k_sys_fatal_error_handler(unsigned int reason, const FatalErrorEsf *esf) {
arch_restart();
}
#ifdef USE_CRASH_HANDLER
bool crash_log_pending() { return zephyr_coredump::coredump_pending(); }
void log_crash_to_client() {
// A level that drops the lines would erase the dump unseen; keep it for a client that can see it
if (global_logger->get_log_level(zephyr_coredump::CRASH_LOG_TAG) < ESPHOME_LOG_LEVEL_ERROR)
return;
zephyr_coredump::print_coredump();
}
#endif
} // namespace esphome::logger
extern "C" {
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["spi"]
@@ -22,6 +21,4 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await spi.new_spi_component(config)
+1 -3
View File
@@ -73,9 +73,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_CO2, var.set_co2_sensor)
+1 -4
View File
@@ -4,7 +4,6 @@ from esphome import pins
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
from esphome.cpp_generator import MockObj
@@ -106,9 +105,7 @@ async def to_code_micronova_listener(
async def to_code(config: ConfigType) -> None:
enable_rx_pin = await cg.gpio_pin_expression(config[CONF_ENABLE_RX_PIN])
var = cg.new_Pvariable(config[CONF_ID], enable_rx_pin)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await uart.new_uart_component(config, enable_rx_pin)
CORE.add_job(_final_step)

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