[bq25186] Add bq25186 1-Cell, 1A Linear Battery Charger component (part 1) (#15700)

Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Rodrigo Martín
2026-10-12 12:54:35 -10:00
committed by GitHub
co-authored by J. Nick Koston
parent 88668f448e
commit d08d324a83
9 changed files with 556 additions and 0 deletions
+1
View File
@@ -101,6 +101,7 @@ esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
esphome/components/bmp581_spi/* @danielkent-net @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/bq25186/* @Rapsssito
esphome/components/bq27220/* @rggammon
esphome/components/bridge/* @kbx81
esphome/components/bthome_mithermometer/* @nagyrobi
+314
View File
@@ -0,0 +1,314 @@
from collections.abc import Callable
from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.core import CORE, ID, Lambda
from esphome.types import ConfigType, TemplateArgsType
CODEOWNERS = ["@Rapsssito"]
DEPENDENCIES = ["i2c"]
DOMAIN = "bq25186"
MULTI_CONF = True
CONF_CONFIGURE_ON_BOOT = "configure_on_boot"
CONF_BATTERY_REGULATION_VOLTAGE = "battery_regulation_voltage"
CONF_FAST_CHARGE_CURRENT = "fast_charge_current"
CONF_CHARGE_ENABLED = "charge_enabled"
CONF_FLASH_CHARGING_MODE = "flash_charging_mode"
CONF_PRECHARGE_IS_ITERM = "precharge_is_iterm"
CONF_TERMINATION_PERCENT = "termination_percent"
CONF_VINDPM_MODE = "vindpm_mode"
CONF_THERMAL_REGULATION = "thermal_regulation"
CONF_BATTERY_OCP_LIMIT = "battery_ocp_limit"
CONF_BATTERY_UVLO = "battery_uvlo"
CONF_MASK_CHARGE_STATUS_INTERRUPT = "mask_charge_status_interrupt"
CONF_MASK_ILIM_INTERRUPT = "mask_ilim_interrupt"
CONF_MASK_VINDPM_INTERRUPT = "mask_vindpm_interrupt"
CONF_TS_AUTO_FUNCTION = "ts_auto_function"
CONF_VLOWV = "vlowv"
CONF_RECHARGE_THRESHOLD = "recharge_threshold"
CONF_DOUBLE_TIMER_DURING_DPM = "double_timer_during_dpm"
CONF_SAFETY_TIMER = "safety_timer"
CONF_LONG_PRESS_DURATION = "long_press_duration"
CONF_HW_RESET_REQUIRES_VIN = "hw_reset_requires_vin"
CONF_AUTOWAKE = "autowake"
CONF_INPUT_CURRENT_LIMIT = "input_current_limit"
CONF_PUSH_BUTTON_LONG_PRESS_ACTION = "push_button_long_press_action"
CONF_WAKE1_TIMER = "wake1_timer"
CONF_WAKE2_TIMER = "wake2_timer"
CONF_ENABLE_PUSH_BUTTON = "enable_push_button"
CONF_SYSTEM_REGULATION = "system_regulation"
CONF_SYS_MODE = "sys_mode"
CONF_DISABLE_VDPPM = "disable_vdppm"
CONF_TS_HOT = "ts_hot"
CONF_TS_COLD = "ts_cold"
CONF_TS_WARM_DISABLE = "ts_warm_disable"
CONF_TS_COOL_DISABLE = "ts_cool_disable"
CONF_TS_ICHG = "ts_ichg"
CONF_TS_VRCG = "ts_vrcg"
CONF_MASK_TS_INTERRUPT = "mask_ts_interrupt"
CONF_MASK_TREG_INTERRUPT = "mask_treg_interrupt"
CONF_MASK_BAT_INTERRUPT = "mask_bat_interrupt"
CONF_MASK_PG_INTERRUPT = "mask_pg_interrupt"
bq25186_ns = cg.esphome_ns.namespace("bq25186")
BQ25186Component = bq25186_ns.class_("BQ25186Component", cg.Component, i2c.I2CDevice)
REG_VBAT_CTRL = 0x03
REG_ICHG_CTRL = 0x04
REG_CHARGECTRL0 = 0x05
REG_CHARGECTRL1 = 0x06
REG_IC_CTRL = 0x07
REG_TMR_ILIM = 0x08
REG_SHIP_RST = 0x09
REG_SYS_REG = 0x0A
REG_TS_CONTROL = 0x0B
REG_MASK_ID = 0x0C
REGISTER_COUNT = REG_MASK_ID - REG_VBAT_CTRL + 1
WATCHDOG_DISABLED = 0x03
TERMINATION_PERCENT_OPTIONS = {"disabled": 0, "5%": 1, "10%": 2, "20%": 3}
VINDPM_MODE_OPTIONS = {"battery_tracking": 0, "4.5v": 1, "4.7v": 2, "disabled": 3}
THERM_REGULATION_OPTIONS = {"100c": 0, "80c": 1, "60c": 2, "disabled": 3}
BATTERY_OCP_LIMIT_OPTIONS = {"500ma": 0, "1000ma": 1, "1500ma": 2, "3000ma": 3}
BATTERY_UVLO_OPTIONS = {
"3.0v": 0,
"2.8v": 3,
"2.6v": 4,
"2.4v": 5,
"2.2v": 6,
"2.0v": 7,
}
VLOWV_OPTIONS = {"3.0v": 0, "2.8v": 1}
RECHARGE_THRESHOLD_OPTIONS = {"100mv": 0, "200mv": 1}
SAFETY_TIMER_OPTIONS = {"3h": 0, "6h": 1, "12h": 2, "disabled": 3}
LONG_PRESS_DURATION_OPTIONS = {"5s": 0, "10s": 1, "15s": 2, "20s": 3}
AUTOWAKE_OPTIONS = {"0.5s": 0, "1s": 1, "2s": 2, "4s": 3}
INPUT_CURRENT_LIMIT_OPTIONS = {
"50ma": 0,
"100ma": 1,
"200ma": 2,
"300ma": 3,
"400ma": 4,
"500ma": 5,
"665ma": 6,
"1050ma": 7,
}
PUSH_BUTTON_LONG_PRESS_ACTION_OPTIONS = {
"none": 0,
"hardware_reset": 1,
"ship_mode": 2,
"shutdown_mode": 3,
}
WAKE1_TIMER_OPTIONS = {"300ms": 0, "1s": 1}
WAKE2_TIMER_OPTIONS = {"2s": 0, "3s": 1}
SYSTEM_REGULATION_OPTIONS = {
"battery_tracking": 0,
"4.4v": 1,
"4.5v": 2,
"4.6v": 3,
"4.7v": 4,
"4.8v": 5,
"4.9v": 6,
"pass_through": 7,
}
SYS_MODE_OPTIONS = {
"normal": 0,
"battery_only": 1,
"floating_off": 2,
"pulldown_off": 3,
}
TS_HOT_OPTIONS = {"60c": 0, "65c": 1, "50c": 2, "45c": 3}
TS_COLD_OPTIONS = {"0c": 0, "3c": 1, "5c": 2, "-3c": 3}
TS_ICHG_OPTIONS = {"0.5x": 0, "0.2x": 1}
TS_VRCG_OPTIONS = {"-100mv": 0, "-200mv": 1}
def _on_voltage_step(volts: float) -> float:
millivolts = volts * 1000
if abs(millivolts - round(millivolts / 10) * 10) > 1e-6:
raise cv.Invalid("battery_regulation_voltage must be a multiple of 10 mV")
return volts
def _on_current_step(amps: float) -> float:
milliamps = amps * 1000
step = 1 if milliamps < 36 else 10
if abs(milliamps - round(milliamps / step) * step) > 1e-6 or 35 < milliamps < 40:
raise cv.Invalid(
"fast_charge_current must be a whole number of mA up to 35mA, then a multiple of 10 mA"
)
return amps
# (config key, register, bit shift, validator or enum options, default).
# VBAT_CTRL bit 7 (pg_mode) and SYS_REG bit 4 (pg_gpo_level) stay 0; they are
# driven at runtime, not from configuration.
FIELDS: tuple[tuple[str, int, int, Any, Any], ...] = (
(
CONF_BATTERY_REGULATION_VOLTAGE,
REG_VBAT_CTRL,
0,
cv.All(cv.voltage, cv.Range(min=3.5, max=4.65), _on_voltage_step),
"4.2V",
),
(CONF_CHARGE_ENABLED, REG_ICHG_CTRL, 7, cv.boolean, True),
(
CONF_FAST_CHARGE_CURRENT,
REG_ICHG_CTRL,
0,
cv.All(cv.current, cv.Range(min=0.005, max=1.0), _on_current_step),
"10mA",
),
(CONF_FLASH_CHARGING_MODE, REG_CHARGECTRL0, 7, cv.boolean, False),
(CONF_PRECHARGE_IS_ITERM, REG_CHARGECTRL0, 6, cv.boolean, False),
(CONF_TERMINATION_PERCENT, REG_CHARGECTRL0, 4, TERMINATION_PERCENT_OPTIONS, "10%"),
(CONF_VINDPM_MODE, REG_CHARGECTRL0, 2, VINDPM_MODE_OPTIONS, "4.5v"),
(CONF_THERMAL_REGULATION, REG_CHARGECTRL0, 0, THERM_REGULATION_OPTIONS, "100c"),
(CONF_BATTERY_OCP_LIMIT, REG_CHARGECTRL1, 6, BATTERY_OCP_LIMIT_OPTIONS, "3000ma"),
(CONF_BATTERY_UVLO, REG_CHARGECTRL1, 3, BATTERY_UVLO_OPTIONS, "3.0v"),
(CONF_MASK_CHARGE_STATUS_INTERRUPT, REG_CHARGECTRL1, 2, cv.boolean, True),
(CONF_MASK_ILIM_INTERRUPT, REG_CHARGECTRL1, 1, cv.boolean, True),
(CONF_MASK_VINDPM_INTERRUPT, REG_CHARGECTRL1, 0, cv.boolean, False),
(CONF_TS_AUTO_FUNCTION, REG_IC_CTRL, 7, cv.boolean, True),
(CONF_VLOWV, REG_IC_CTRL, 6, VLOWV_OPTIONS, "3.0v"),
(CONF_RECHARGE_THRESHOLD, REG_IC_CTRL, 5, RECHARGE_THRESHOLD_OPTIONS, "100mv"),
(CONF_DOUBLE_TIMER_DURING_DPM, REG_IC_CTRL, 4, cv.boolean, False),
(CONF_SAFETY_TIMER, REG_IC_CTRL, 2, SAFETY_TIMER_OPTIONS, "6h"),
(CONF_LONG_PRESS_DURATION, REG_TMR_ILIM, 6, LONG_PRESS_DURATION_OPTIONS, "10s"),
(CONF_HW_RESET_REQUIRES_VIN, REG_TMR_ILIM, 5, cv.boolean, False),
(CONF_AUTOWAKE, REG_TMR_ILIM, 3, AUTOWAKE_OPTIONS, "1s"),
(CONF_INPUT_CURRENT_LIMIT, REG_TMR_ILIM, 0, INPUT_CURRENT_LIMIT_OPTIONS, "500ma"),
(
CONF_PUSH_BUTTON_LONG_PRESS_ACTION,
REG_SHIP_RST,
3,
PUSH_BUTTON_LONG_PRESS_ACTION_OPTIONS,
"ship_mode",
),
(CONF_WAKE1_TIMER, REG_SHIP_RST, 2, WAKE1_TIMER_OPTIONS, "300ms"),
(CONF_WAKE2_TIMER, REG_SHIP_RST, 1, WAKE2_TIMER_OPTIONS, "2s"),
(CONF_ENABLE_PUSH_BUTTON, REG_SHIP_RST, 0, cv.boolean, True),
(CONF_SYSTEM_REGULATION, REG_SYS_REG, 5, SYSTEM_REGULATION_OPTIONS, "4.5v"),
(CONF_SYS_MODE, REG_SYS_REG, 2, SYS_MODE_OPTIONS, "normal"),
(CONF_DISABLE_VDPPM, REG_SYS_REG, 0, cv.boolean, False),
(CONF_TS_HOT, REG_TS_CONTROL, 6, TS_HOT_OPTIONS, "60c"),
(CONF_TS_COLD, REG_TS_CONTROL, 4, TS_COLD_OPTIONS, "0c"),
(CONF_TS_WARM_DISABLE, REG_TS_CONTROL, 3, cv.boolean, False),
(CONF_TS_COOL_DISABLE, REG_TS_CONTROL, 2, cv.boolean, False),
(CONF_TS_ICHG, REG_TS_CONTROL, 1, TS_ICHG_OPTIONS, "0.5x"),
(CONF_TS_VRCG, REG_TS_CONTROL, 0, TS_VRCG_OPTIONS, "-100mv"),
(CONF_MASK_TS_INTERRUPT, REG_MASK_ID, 7, cv.boolean, False),
(CONF_MASK_TREG_INTERRUPT, REG_MASK_ID, 6, cv.boolean, True),
(CONF_MASK_BAT_INTERRUPT, REG_MASK_ID, 5, cv.boolean, False),
(CONF_MASK_PG_INTERRUPT, REG_MASK_ID, 4, cv.boolean, False),
)
def _battery_regulation_code(volts: float) -> int:
"""VBAT_CTRL[6:0]: 3.5 V plus 10 mV per step."""
return (round(volts * 1000) - 3500) // 10
def _fast_charge_code(amps: float) -> int:
"""ICHG_CTRL[6:0]: 1 mA steps from 5 mA to 35 mA, then 10 mA steps from 40 mA."""
milliamps = round(amps * 1000)
if milliamps <= 35:
return milliamps - 5
return milliamps // 10 + 27
_ENCODERS: dict[str, Callable[[Any], int]] = {
CONF_BATTERY_REGULATION_VOLTAGE: _battery_regulation_code,
CONF_FAST_CHARGE_CURRENT: _fast_charge_code,
CONF_CHARGE_ENABLED: lambda enabled: not enabled,
}
def pack_registers(config: ConfigType) -> list[int]:
"""Pack the validated field values into the VBAT_CTRL..MASK_ID register bytes."""
regs = [0] * REGISTER_COUNT
# Watchdog disabled (IC_CTRL[1:0] = 3): nothing talks to the chip after boot yet, and a running
# watchdog restores the power-on defaults after 160 s. Part 2 adds the option with the polling.
regs[REG_IC_CTRL - REG_VBAT_CTRL] = WATCHDOG_DISABLED
for key, register, shift, validator, _ in FIELDS:
value = config[key]
if isinstance(validator, dict):
value = validator[value]
elif (encode := _ENCODERS.get(key)) is not None:
value = encode(value)
regs[register - REG_VBAT_CTRL] |= int(value) << shift
return regs
def _field_schema(with_defaults: bool) -> cv.Schema:
schema = {}
for key, _, _, validator, default in FIELDS:
if isinstance(validator, dict):
validator = cv.enum(validator, lower=True)
optional = (
cv.Optional(key, default=default) if with_defaults else cv.Optional(key)
)
schema[optional] = validator
return cv.Schema(schema)
BQ25186_CONFIG_SCHEMA = _field_schema(with_defaults=True)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BQ25186Component),
cv.Optional(CONF_CONFIGURE_ON_BOOT, default=True): cv.boolean,
}
)
.extend(BQ25186_CONFIG_SCHEMA)
.extend(i2c.i2c_device_schema(0x6A))
)
CONFIGURE_ACTION_SCHEMA = automation.maybe_simple_id(
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(BQ25186Component),
}
).extend(_field_schema(with_defaults=False))
)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if config[CONF_CONFIGURE_ON_BOOT]:
cg.add(var.set_setup_registers(cg.ArrayInitializer(*pack_registers(config))))
@automation.register_action(
"bq25186.apply_configuration",
automation.LambdaAction,
CONFIGURE_ACTION_SCHEMA,
synchronous=True,
)
async def bq25186_configure_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> cg.MockObj:
parent = await cg.get_variable(config[CONF_ID])
# Options the action leaves out fall back to the component's configuration
path = CORE.config.get_path_for_id(config[CONF_ID])[:-1]
parent_config = CORE.config.get_config_for_path(path)
regs = pack_registers({**parent_config, **config})
text = str(cg.statement(parent.apply_config(cg.ArrayInitializer(*regs))))
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return automation.new_lambda_pvariable(
action_id, lambda_, automation.StatelessLambdaAction, template_arg
)
+79
View File
@@ -0,0 +1,79 @@
#include "bq25186.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::bq25186 {
ESPHOME_LOG_TAG(TAG, "bq25186");
// Bits the configuration owns in each register; the rest are runtime state (VBAT_CTRL pg_mode,
// SYS_REG GPO level), reset triggers (SHIP_RST) or the device id (MASK_ID) and are kept as read
static const uint8_t BQ25186_CONFIG_MASKS[BQ25186_CONFIG_REGISTER_COUNT] PROGMEM = {
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0xEF, 0xFF, 0xF0,
};
bool BQ25186Component::write_register_(uint8_t reg, uint8_t value) {
if (!this->write_byte(reg, value)) {
ESP_LOGW(TAG, "Failed to write register 0x%02X", reg);
return false;
}
return true;
}
bool BQ25186Component::update_register_(uint8_t reg, uint8_t mask, uint8_t value) {
uint8_t reg_value;
if (!this->read_byte(reg, &reg_value)) {
ESP_LOGW(TAG, "Failed to read register 0x%02X", reg);
return false;
}
reg_value = (reg_value & ~mask) | (value & mask);
return this->write_register_(reg, reg_value);
}
bool BQ25186Component::apply_config(const BQ25186ConfigRegisters &regs) {
// Setup rejects an unknown chip; the action must not program it before that check or after it failed
if (!this->is_ready() || this->is_failed()) {
ESP_LOGW(TAG, "Not applying configuration, setup has not succeeded");
return false;
}
for (size_t i = 0; i < regs.size(); i++) {
const uint8_t reg = BQ25186_REG_VBAT_CTRL + i;
const uint8_t mask = progmem_read_byte(&BQ25186_CONFIG_MASKS[i]);
const bool ok = mask == 0xFF ? this->write_register_(reg, regs[i]) : this->update_register_(reg, mask, regs[i]);
if (!ok) {
this->status_set_warning(LOG_STR("Configuration partly applied"));
return false;
}
}
this->status_clear_warning();
return true;
}
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"));
return;
}
// A failed write only sets the warning status: the chip has identified itself, so a later
// apply_configuration may retry
if (this->configure_on_boot_)
this->apply_config(this->setup_regs_);
}
void BQ25186Component::dump_config() {
ESP_LOGCONFIG(TAG, "BQ25186:");
LOG_I2C_DEVICE(this);
}
} // namespace esphome::bq25186
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include <array>
namespace esphome::bq25186 {
static constexpr uint8_t BQ25186_REG_VBAT_CTRL = 0x03;
static constexpr uint8_t BQ25186_REG_ICHG_CTRL = 0x04;
static constexpr uint8_t BQ25186_REG_CHARGECTRL0 = 0x05;
static constexpr uint8_t BQ25186_REG_CHARGECTRL1 = 0x06;
static constexpr uint8_t BQ25186_REG_IC_CTRL = 0x07;
static constexpr uint8_t BQ25186_REG_TMR_ILIM = 0x08;
static constexpr uint8_t BQ25186_REG_SHIP_RST = 0x09;
static constexpr uint8_t BQ25186_REG_SYS_REG = 0x0A;
static constexpr uint8_t BQ25186_REG_TS_CONTROL = 0x0B;
static constexpr uint8_t BQ25186_REG_MASK_ID = 0x0C;
static constexpr size_t BQ25186_CONFIG_REGISTER_COUNT = BQ25186_REG_MASK_ID - BQ25186_REG_VBAT_CTRL + 1;
// Register values for VBAT_CTRL..MASK_ID, packed by code generation
using BQ25186ConfigRegisters = std::array<uint8_t, BQ25186_CONFIG_REGISTER_COUNT>;
class BQ25186Component : public Component, public i2c::I2CDevice {
public:
void setup() override;
void dump_config() override;
void set_setup_registers(const BQ25186ConfigRegisters &regs) {
this->setup_regs_ = regs;
this->configure_on_boot_ = true;
}
bool apply_config(const BQ25186ConfigRegisters &regs);
protected:
bool write_register_(uint8_t reg, uint8_t value);
bool update_register_(uint8_t reg, uint8_t mask, uint8_t value);
BQ25186ConfigRegisters setup_regs_{};
bool configure_on_boot_{false};
};
} // namespace esphome::bq25186
@@ -0,0 +1,87 @@
"""Tests for the bq25186 register packer."""
import pytest
from esphome import config_validation as cv
from esphome.components.bq25186 import (
BQ25186_CONFIG_SCHEMA,
CONF_BATTERY_REGULATION_VOLTAGE,
CONF_CHARGE_ENABLED,
CONF_FAST_CHARGE_CURRENT,
CONF_MASK_TS_INTERRUPT,
CONF_PUSH_BUTTON_LONG_PRESS_ACTION,
REG_ICHG_CTRL,
REG_MASK_ID,
REG_SHIP_RST,
REG_VBAT_CTRL,
pack_registers,
)
from esphome.types import ConfigType
# Datasheet power-on values for VBAT_CTRL..MASK_ID, except that IC_CTRL has the
# watchdog disabled (0x87 instead of 0x84) and MASK_ID carries only its
# configurable bits 7:4 (0x40 instead of 0x41 with the device id).
DEFAULT_REGISTERS = [0x46, 0x05, 0x24, 0xC6, 0x87, 0x4D, 0x11, 0x40, 0x00, 0x40]
def _packed(reg: int, config: ConfigType | None = None) -> int:
regs = pack_registers(BQ25186_CONFIG_SCHEMA(config or {}))
return regs[reg - REG_VBAT_CTRL]
def test_defaults_match_power_on_values() -> None:
assert pack_registers(BQ25186_CONFIG_SCHEMA({})) == DEFAULT_REGISTERS
def test_battery_regulation_voltage_code() -> None:
assert _packed(REG_VBAT_CTRL, {CONF_BATTERY_REGULATION_VOLTAGE: "4.1V"}) == 0x3C
assert _packed(REG_VBAT_CTRL, {CONF_BATTERY_REGULATION_VOLTAGE: "3.5V"}) == 0x00
assert _packed(REG_VBAT_CTRL, {CONF_BATTERY_REGULATION_VOLTAGE: "4.65V"}) == 0x73
def test_fast_charge_current_code() -> None:
# 1 mA steps from 5 mA to 35 mA
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "5mA"}) == 0x00
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "20mA"}) == 0x0F
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "35mA"}) == 0x1E
# 10 mA steps from 40 mA
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "40mA"}) == 0x1F
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "200mA"}) == 0x2F
assert _packed(REG_ICHG_CTRL, {CONF_FAST_CHARGE_CURRENT: "1A"}) == 0x7F
def test_charge_disabled_sets_bit_7() -> None:
assert _packed(REG_ICHG_CTRL, {CONF_CHARGE_ENABLED: False}) == 0x85
assert _packed(REG_ICHG_CTRL, {CONF_CHARGE_ENABLED: True}) == 0x05
def test_masked_registers_only_carry_configurable_bits() -> None:
config: ConfigType = {
CONF_PUSH_BUTTON_LONG_PRESS_ACTION: "shutdown_mode",
CONF_MASK_TS_INTERRUPT: True,
}
# SHIP_RST bits 7:5 are reset triggers
ship_rst = _packed(REG_SHIP_RST, config)
assert ship_rst == 0x19
assert ship_rst & ~0x1F == 0
# MASK_ID bits 3:0 are the device id
mask_id = _packed(REG_MASK_ID, config)
assert mask_id == 0xC0
assert mask_id & ~0xF0 == 0
@pytest.mark.parametrize(
"key, value",
[
(CONF_BATTERY_REGULATION_VOLTAGE, "4.105V"),
(CONF_FAST_CHARGE_CURRENT, "36mA"),
(CONF_FAST_CHARGE_CURRENT, "39mA"),
(CONF_FAST_CHARGE_CURRENT, "49mA"),
(CONF_FAST_CHARGE_CURRENT, "5.5mA"),
(CONF_FAST_CHARGE_CURRENT, "34.6mA"),
(CONF_BATTERY_REGULATION_VOLTAGE, "4.2005V"),
],
)
def test_values_between_steps_are_rejected(key: str, value: str) -> None:
with pytest.raises(cv.Invalid):
BQ25186_CONFIG_SCHEMA({key: value})
+25
View File
@@ -0,0 +1,25 @@
esphome:
on_boot:
priority: -100
then:
# Just the defined configuration
- bq25186.apply_configuration: bq25186_charger
# Just the defined configuration with some overrides
- bq25186.apply_configuration:
id: bq25186_charger
battery_regulation_voltage: 4.1V
fast_charge_current: 200mA
bq25186:
- id: bq25186_charger
configure_on_boot: false
fast_charge_current: 150mA
battery_regulation_voltage: 4.2V
input_current_limit: 500ma
system_regulation: 4.5v
charge_enabled: true
# Second charger with the boot-time write enabled
- id: bq25186_boot
address: 0x6B
configure_on_boot: true
fast_charge_current: 20mA
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
bq25186: !include common.yaml
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
bq25186: !include common.yaml