mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[sy6970] Service the I2C watchdog so charge/LED settings persist (#19424)
Co-authored-by: Willem Oldemans <> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
Willem Oldemans
Claude Sonnet 5
J. Nick Koston
J. Nick Koston
parent
f45e6f0bf4
commit
7b591b26d6
@@ -17,12 +17,22 @@ CONF_CHARGE_CURRENT = "charge_current"
|
||||
CONF_PRECHARGE_CURRENT = "precharge_current"
|
||||
CONF_CHARGE_ENABLED = "charge_enabled"
|
||||
CONF_ENABLE_ADC = "enable_adc"
|
||||
CONF_I2C_WATCHDOG_TIMEOUT = "i2c_watchdog_timeout"
|
||||
|
||||
sy6970_ns = cg.esphome_ns.namespace("sy6970")
|
||||
SY6970Component = sy6970_ns.class_(
|
||||
"SY6970Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
SY6970Listener = sy6970_ns.class_("SY6970Listener")
|
||||
I2CWatchdogTimeout = sy6970_ns.enum("I2CWatchdogTimeout")
|
||||
|
||||
# REG07[5:4]; the component kicks the watchdog itself, so the chip's 40S default can stay on
|
||||
I2C_WATCHDOG_TIMEOUTS = {
|
||||
"DISABLED": I2CWatchdogTimeout.I2C_WATCHDOG_DISABLED,
|
||||
"40S": I2CWatchdogTimeout.I2C_WATCHDOG_40S,
|
||||
"80S": I2CWatchdogTimeout.I2C_WATCHDOG_80S,
|
||||
"160S": I2CWatchdogTimeout.I2C_WATCHDOG_160S,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -43,6 +53,9 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
cv.Optional(CONF_CHARGE_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ENABLE_ADC, default=True): cv.boolean,
|
||||
cv.Optional(CONF_I2C_WATCHDOG_TIMEOUT, default="40S"): cv.enum(
|
||||
I2C_WATCHDOG_TIMEOUTS, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
@@ -60,6 +73,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
config[CONF_PRECHARGE_CURRENT],
|
||||
config[CONF_CHARGE_ENABLED],
|
||||
config[CONF_ENABLE_ADC],
|
||||
config[CONF_I2C_WATCHDOG_TIMEOUT],
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
@@ -6,6 +6,25 @@ namespace esphome::sy6970 {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "sy6970");
|
||||
|
||||
static const LogString *i2c_watchdog_timeout_to_string(I2CWatchdogTimeout timeout) {
|
||||
switch (timeout) {
|
||||
case I2C_WATCHDOG_DISABLED:
|
||||
return LOG_STR("Disabled");
|
||||
case I2C_WATCHDOG_40S:
|
||||
return LOG_STR("40s");
|
||||
case I2C_WATCHDOG_80S:
|
||||
return LOG_STR("80s");
|
||||
case I2C_WATCHDOG_160S:
|
||||
return LOG_STR("160s");
|
||||
default:
|
||||
return LOG_STR("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t I2C_WATCHDOG_KICK_INTERVAL_ID = 0;
|
||||
// Well under the 40 s default, so a failed kick or a timeout the chip did not accept still leaves margin
|
||||
static constexpr uint32_t I2C_WATCHDOG_KICK_INTERVAL_MS = 10000;
|
||||
|
||||
bool SY6970Component::read_all_registers_() {
|
||||
// Read all registers from 0x00 to 0x14 in one transaction (21 bytes)
|
||||
// This includes unused registers 0x0F, 0x10 for performance
|
||||
@@ -51,6 +70,11 @@ void SY6970Component::setup() {
|
||||
ESP_LOGW(TAG, "Unexpected chip ID: 0x%02X (expected 0x00)", chip_id);
|
||||
}
|
||||
|
||||
// Service the watchdog first, so the settings below are not reverted while they are written
|
||||
ESP_LOGV(TAG, "Setting I2C watchdog timeout to %s",
|
||||
LOG_STR_ARG(i2c_watchdog_timeout_to_string(this->i2c_watchdog_timeout_)));
|
||||
this->set_i2c_watchdog_timeout(this->i2c_watchdog_timeout_);
|
||||
|
||||
// Apply configuration options (all have defaults now)
|
||||
ESP_LOGV(TAG, "Setting LED enabled to %s", ONOFF(this->led_enabled_));
|
||||
this->set_led_enabled(this->led_enabled_);
|
||||
@@ -85,9 +109,11 @@ void SY6970Component::dump_config() {
|
||||
" Charge Current: %u mA\n"
|
||||
" Precharge Current: %u mA\n"
|
||||
" Charge Enabled: %s\n"
|
||||
" ADC Enabled: %s",
|
||||
" ADC Enabled: %s\n"
|
||||
" I2C Watchdog Timeout: %s",
|
||||
ONOFF(this->led_enabled_), this->input_current_limit_, this->charge_voltage_, this->charge_current_,
|
||||
this->precharge_current_, ONOFF(this->charge_enabled_), ONOFF(this->enable_adc_));
|
||||
this->precharge_current_, ONOFF(this->charge_enabled_), ONOFF(this->enable_adc_),
|
||||
LOG_STR_ARG(i2c_watchdog_timeout_to_string(this->i2c_watchdog_timeout_)));
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (this->is_failed()) {
|
||||
@@ -198,4 +224,44 @@ void SY6970Component::set_enable_adc_measure(bool enabled) {
|
||||
this->update_register_(SY6970_REG_ADC_CONTROL, 0xC0, enabled ? 0xC0 : 0x00);
|
||||
}
|
||||
|
||||
void SY6970Component::set_i2c_watchdog_timeout(I2CWatchdogTimeout timeout) {
|
||||
if (this->is_failed())
|
||||
return;
|
||||
|
||||
if (timeout != I2C_WATCHDOG_DISABLED) {
|
||||
// Changing the timeout does not restart the chip's timer, which may be close to expiring
|
||||
this->reset_i2c_watchdog();
|
||||
}
|
||||
|
||||
// REG07 bits 5:4 (WATCHDOG[1:0])
|
||||
bool written = this->update_register_(SY6970_REG_TIMER_CONTROL, 0x30, static_cast<uint8_t>(timeout) << 4);
|
||||
if (!written) {
|
||||
ESP_LOGW(TAG, "Failed to set I2C watchdog timeout to %s", LOG_STR_ARG(i2c_watchdog_timeout_to_string(timeout)));
|
||||
}
|
||||
|
||||
if (timeout == I2C_WATCHDOG_DISABLED && written) {
|
||||
this->i2c_watchdog_timeout_ = timeout;
|
||||
this->cancel_interval(I2C_WATCHDOG_KICK_INTERVAL_ID);
|
||||
return;
|
||||
}
|
||||
if (timeout != I2C_WATCHDOG_DISABLED) {
|
||||
this->i2c_watchdog_timeout_ = timeout;
|
||||
}
|
||||
// Keeps kicking after a failed write too: the chip may still be on its 40 s default
|
||||
this->set_interval(I2C_WATCHDOG_KICK_INTERVAL_ID, I2C_WATCHDOG_KICK_INTERVAL_MS,
|
||||
[this]() { this->reset_i2c_watchdog(); });
|
||||
}
|
||||
|
||||
bool SY6970Component::reset_i2c_watchdog() {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
// WD_RST, self-clearing
|
||||
if (!this->update_register_(SY6970_REG_SYS_CONTROL, 0x40, 0x40)) {
|
||||
ESP_LOGW(TAG, "I2C watchdog reset failed, settings may revert");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
|
||||
@@ -62,6 +62,15 @@ enum ChargeStatus {
|
||||
CHARGE_STATUS_CHARGE_DONE = 3,
|
||||
};
|
||||
|
||||
// REG07[5:4]. When the watchdog elapses the chip reverts CHG_CONFIG and STAT_DIS to their defaults,
|
||||
// so an enabled watchdog is kicked on its own interval.
|
||||
enum I2CWatchdogTimeout {
|
||||
I2C_WATCHDOG_DISABLED = 0,
|
||||
I2C_WATCHDOG_40S = 1,
|
||||
I2C_WATCHDOG_80S = 2,
|
||||
I2C_WATCHDOG_160S = 3,
|
||||
};
|
||||
|
||||
// Structure to hold all register data read in one transaction
|
||||
struct SY6970Data {
|
||||
uint8_t registers[21]; // Registers 0x00-0x14 (includes unused 0x0F, 0x10)
|
||||
@@ -76,14 +85,16 @@ class SY6970Listener {
|
||||
class SY6970Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
SY6970Component(bool led_enabled, uint16_t input_current_limit, uint16_t charge_voltage, uint16_t charge_current,
|
||||
uint16_t precharge_current, bool charge_enabled, bool enable_adc)
|
||||
uint16_t precharge_current, bool charge_enabled, bool enable_adc,
|
||||
I2CWatchdogTimeout i2c_watchdog_timeout)
|
||||
: led_enabled_(led_enabled),
|
||||
input_current_limit_(input_current_limit),
|
||||
charge_voltage_(charge_voltage),
|
||||
charge_current_(charge_current),
|
||||
precharge_current_(precharge_current),
|
||||
charge_enabled_(charge_enabled),
|
||||
enable_adc_(enable_adc) {}
|
||||
enable_adc_(enable_adc),
|
||||
i2c_watchdog_timeout_(i2c_watchdog_timeout) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
@@ -99,6 +110,10 @@ class SY6970Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_charge_enabled(bool enabled);
|
||||
void set_led_enabled(bool enabled);
|
||||
void set_enable_adc_measure(bool enabled = true);
|
||||
void set_i2c_watchdog_timeout(I2CWatchdogTimeout timeout);
|
||||
|
||||
/// Kick the I2C watchdog (REG03 bit 6); runs on its own interval while enabled. Returns false when not written.
|
||||
bool reset_i2c_watchdog();
|
||||
|
||||
protected:
|
||||
bool read_all_registers_();
|
||||
@@ -116,6 +131,7 @@ class SY6970Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
uint16_t precharge_current_;
|
||||
bool charge_enabled_;
|
||||
bool enable_adc_;
|
||||
I2CWatchdogTimeout i2c_watchdog_timeout_;
|
||||
};
|
||||
|
||||
} // namespace esphome::sy6970
|
||||
|
||||
@@ -9,6 +9,7 @@ sy6970:
|
||||
precharge_current: 128
|
||||
charge_enabled: true
|
||||
enable_adc: true
|
||||
i2c_watchdog_timeout: 80S
|
||||
update_interval: 5s
|
||||
|
||||
sensor:
|
||||
|
||||
Reference in New Issue
Block a user