mirror of
https://github.com/esphome/esphome.git
synced 2026-10-19 12:42:50 +00:00
Merge branch 'dev' into i2c-write-registers-migrate
This commit is contained in:
+6
-1
@@ -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,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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -79,19 +79,9 @@ bool BQ25186Component::apply_config(const BQ25186ConfigRegisters ®s) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user