Merge remote-tracking branch 'origin/dev' into esp8266-entity-names-flash

This commit is contained in:
J. Nick Koston
2026-10-18 07:33:08 -10:00
1106 changed files with 24334 additions and 5726 deletions
+4 -1
View File
@@ -395,6 +395,8 @@ jobs:
- common
- determine-jobs
if: needs.determine-jobs.outputs.integration-tests == 'true'
# A hung test is failed by pytest's --timeout below; this bounds the rest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
@@ -471,7 +473,8 @@ jobs:
exit 1
fi
echo "Bucket ${{ matrix.bucket.name }}: running ${#test_files[@]} integration tests"
pytest -vv --no-cov --tb=native --durations=30 -n auto --dist worksteal \
# A hung test fails after 30 min: the 15 min shared build lock wait plus a cold compile
pytest -vv --no-cov --tb=native --durations=30 -n auto --dist worksteal --timeout=1800 \
--junitxml=junit-integration.xml "${test_files[@]}"
- name: Upload test artifacts
# Tests that compare rendered output write the image they actually got here, so a
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
days-before-close: 7
stale-label: stale
exempt-label: not-stale
conflict-label: needs-rebase
conflict-label: has-conflicts
ignored-users: esphbot,codecov-commenter
stale-pr-message: >
There hasn't been any activity on this pull request recently. This
+37 -6
View File
@@ -287,7 +287,8 @@ file does, and it is the authority when they disagree. The most useful starting
`from esphome.components.my_component import DOMAIN`. Keep it in alphabetical order with the other
metadata constants. CI (`lint_component_domain`) fails without it.
- `DEPENDENCIES`: List of required components
- `AUTO_LOAD`: Components to automatically load
- `AUTO_LOAD`: Components to automatically load. Not a way to put entities under the component's
own key; see "Entities of a hub" below.
- `CONFLICTS_WITH`: Incompatible components
- `CODEOWNERS`: GitHub usernames responsible for maintenance
- `MULTI_CONF`: Whether multiple instances are allowed
@@ -379,6 +380,26 @@ file does, and it is the authority when they disagree. The most useful starting
var = await switch.new_switch(config)
```
- **Entities of a hub:** a new component's entities go in platform files (`sensor.py`,
`binary_sensor.py`, ...) that take the hub's id, never under the hub's own key with their platforms
added to `AUTO_LOAD` to make that work. Auto loading a platform because the C++ uses its type is
fine. See https://developers.esphome.io/architecture/components/#exposing-multiple-entities.
```yaml
# Bad - entities under the hub's key
my_hub:
connected:
name: Connected
# Good - the hub, then one platform entry that holds its entities
my_hub:
id: hub1
binary_sensor:
- platform: my_hub
my_hub_id: hub1 # optional with a single hub
connected:
name: Connected
```
- **Optional child entities of a hub:** bind the config once with `sensor.sub_sensors(config)` (or
`sub_binary_sensors`, `sub_text_sensors`, `sub_buttons`, `sub_switches`, `sub_numbers`,
`sub_selects` in their domains), adding `parent=hub` for entities that derive from `Parented<T>`,
@@ -389,15 +410,25 @@ file does, and it is the authority when they disagree. The most useful starting
itself, `hub` for one fetched with `cg.get_variable`) so the calls fit on one line. Loops whose
setter also takes an index, such as `set_gate_threshold(x, n)`, stay as they are.
```python
# my_hub/sensor.py
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
hub = await cg.get_variable(config[CONF_MY_HUB_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
await sensors(CONF_HUMIDITY, var.set_humidity_sensor)
buttons = button.sub_buttons(config, parent=var)
await buttons(CONF_RESTART, var.set_restart_button)
await sensors(CONF_TEMPERATURE, hub.set_temperature_sensor)
await sensors(CONF_HUMIDITY, hub.set_humidity_sensor)
# my_hub/button.py
async def to_code(config):
hub = await cg.get_variable(config[CONF_MY_HUB_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(CONF_RESTART, hub.set_restart_button)
```
- **Pins and referenced components:** set them with `await cg.add_pin(var.set_cs_pin, config[CONF_CS_PIN])`
and `await cg.add_variable(var.set_bus, config[CONF_BUS_ID])`; both skip a `None` value, so pass
`config.get(KEY)` for an optional key, and return what they created. Use `cg.register_parented` for `set_parent`.
* **Automations (Triggers, Actions, Conditions):**
Automations have three building blocks: **Triggers** (fire when something happens), **Actions** (do something), and **Conditions** (check if something is true).
+15 -1
View File
@@ -30,6 +30,9 @@ esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_base/* @jeromelaban @ncareau
esphome/components/airthings_wave_mini/* @ncareau
esphome/components/airthings_wave_plus/* @jeromelaban @precurse
esphome/components/airton/* @Procsiab
esphome/components/airton/select/* @Procsiab
esphome/components/airton/switch/* @Procsiab
esphome/components/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/alpha3/* @jan-hofmeier
esphome/components/am2315c/* @swoboda1337
@@ -157,6 +160,9 @@ esphome/components/delonghi/* @grob6000
esphome/components/dew_point/* @CFlix
esphome/components/dfplayer/* @glmnet
esphome/components/dfrobot_sen0395/* @niklasweber
esphome/components/dfrobot_sen0465_base/* @tzabbi
esphome/components/dfrobot_sen0465_i2c/* @tzabbi
esphome/components/dfrobot_sen0465_uart/* @tzabbi
esphome/components/dfrobot_sen0609/* @remcom
esphome/components/dht/* @OttoWinter
esphome/components/display_menu_base/* @numo68
@@ -252,6 +258,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/haier_tundra/* @fauxpark
esphome/components/haier_tundra/button/* @fauxpark
esphome/components/haier_tundra/switch/* @fauxpark
esphome/components/haier_water_heater/* @TheLexus
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
@@ -390,6 +397,7 @@ esphome/components/micro_wake_word/* @jesserockz @kahrendt
esphome/components/micronova/* @edenhaus @jorre05
esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz
esphome/components/mics_5524/* @nliaudat
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/midea_portasplit/* @Fexiven
@@ -423,6 +431,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
@@ -507,6 +518,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
@@ -679,10 +691,11 @@ esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
esphome/components/version/* @esphome/core
esphome/components/vevor_dtx/* @Bofr5555
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
@@ -721,6 +734,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
+126 -3
View File
@@ -14,6 +14,7 @@ from esphome.const import (
CONF_COUNT,
CONF_ELSE,
CONF_ID,
CONF_IF,
CONF_THEN,
CONF_TIME,
CONF_TIMEOUT,
@@ -34,6 +35,9 @@ from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
from esphome.types import ConfigType, SafeExpType
from esphome.util import Registry
CONF_CHOOSE = "choose"
CONF_CHOICE_ID = "choice_id"
def progmem_bytes(name: str, data: bytes | list[int]) -> MockObj:
"""Shared PROGMEM table for constant bytes; equal payloads share one, empty is nullptr."""
@@ -840,10 +844,39 @@ async def delay_action_to_code(
return var
def _wrap_bare_if_condition(value: Any) -> Any:
"""Allow a single condition directly under `if:` in place of `condition: {...}`."""
if not isinstance(value, dict):
return value
# `any` and `all` are also registered conditions, but here they are the if action's own keys
bare = [
key
for key in value
if key in CONDITION_REGISTRY and key not in (CONF_ANY, CONF_ALL)
]
if not bare:
return value
if len(bare) > 1:
raise cv.Invalid(
f"Only one condition may be given directly under 'if', found {', '.join(bare)}. "
f"Use '{CONF_CONDITION}', '{CONF_ALL}' or '{CONF_ANY}' to combine conditions"
)
if other := [k for k in (CONF_CONDITION, CONF_ANY, CONF_ALL) if k in value]:
raise cv.Invalid(
f"'{bare[0]}' given directly under 'if' cannot be used with '{other[0]}'. "
f"Move it into '{other[0]}'"
)
key = bare[0]
result = {k: v for k, v in value.items() if k != key}
result[CONF_CONDITION] = {key: value[key]}
return result
@register_action(
"if",
IfAction,
cv.All(
_wrap_bare_if_condition,
{
cv.Exclusive(
CONF_CONDITION, CONF_CONDITION
@@ -870,14 +903,104 @@ async def if_action_to_code(
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
condition = await build_condition(config[cond_conf], template_arg, args)
var = cg.new_Pvariable(action_id, if_template_arg, condition)
then_actions = else_actions = None
if CONF_THEN in config:
for action in await build_action_list(config[CONF_THEN], template_arg, args):
then_actions = await build_action_list(config[CONF_THEN], template_arg, args)
if has_else:
else_actions = await build_action_list(config[CONF_ELSE], template_arg, args)
_add_if_branches(var, then_actions, else_actions)
return var
def _add_if_branches(
var: MockObj,
then_actions: list[MockObj] | None,
else_actions: list[MockObj] | None,
) -> None:
"""Add the branches of an IfAction one action at a time; None leaves a branch out."""
if then_actions is not None:
for action in then_actions:
cg.add(var.add_then_action(action))
cg.add(var.finish_then())
if has_else:
for action in await build_action_list(config[CONF_ELSE], template_arg, args):
if else_actions is not None:
for action in else_actions:
cg.add(var.add_else_action(action))
cg.add(var.finish_else())
def _validate_choice(choice: ConfigType) -> ConfigType:
"""A choice is either `if` with `then`, or `else` on its own."""
if CONF_ELSE in choice:
for key in (CONF_IF, CONF_THEN):
if key in choice:
raise cv.Invalid(f"'{key}' cannot be used with 'else'", path=[key])
return choice
for key in (CONF_IF, CONF_THEN):
if key not in choice:
raise cv.Invalid(f"'{key}' is required", path=[key])
return choice
def _validate_choose_else(choices: list[ConfigType]) -> list[ConfigType]:
"""`else` may only be the last choice, and not the only one."""
for index, choice in enumerate(choices[:-1]):
if CONF_ELSE in choice:
raise cv.Invalid(
"Only the last choice may be 'else'", path=[index, CONF_ELSE]
)
if len(choices) == 1 and CONF_ELSE in choices[0]:
raise cv.Invalid(
"At least one choice must have 'if' and 'then'", path=[0, CONF_ELSE]
)
return choices
@register_action(
CONF_CHOOSE,
IfAction,
cv.All(
cv.ensure_list(
cv.All(
cv.Schema(
{
cv.GenerateID(CONF_CHOICE_ID): cv.declare_id(IfAction),
cv.Optional(CONF_IF): validate_potentially_and_condition,
cv.Optional(CONF_THEN): validate_action_list,
cv.Optional(CONF_ELSE): validate_action_list,
}
),
_validate_choice,
)
),
cv.Length(min=1),
_validate_choose_else,
),
synchronous=True,
)
async def choose_action_to_code(
config: list[ConfigType],
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
# Built as a chain of if/else actions: each choice's else branch holds the next choice,
# and the last choice's else branch holds the `else` actions, if any.
else_actions: list[MockObj] | None = None
if CONF_ELSE in config[-1]:
else_actions = await build_action_list(
config[-1][CONF_ELSE], template_arg, args
)
config = config[:-1]
var: MockObj | None = None
for index in reversed(range(len(config))):
choice = config[index]
if_template_arg = cg.TemplateArguments(else_actions is not None, *template_arg)
condition = await build_condition(choice[CONF_IF], template_arg, args)
choice_id = action_id if index == 0 else choice[CONF_CHOICE_ID]
var = cg.new_Pvariable(choice_id, if_template_arg, condition)
then_actions = await build_action_list(choice[CONF_THEN], template_arg, args)
_add_if_branches(var, then_actions, else_actions)
else_actions = [var]
return var
+3
View File
@@ -53,11 +53,14 @@ from esphome.cpp_generator import ( # noqa: F401
with_local_variable,
)
from esphome.cpp_helpers import ( # noqa: F401
add_pin,
add_variable,
build_registry_entry,
build_registry_list,
extract_registry_entry_config,
get_slot_count,
gpio_pin_expression,
new_bus_component,
past_safe_mode,
register_component,
register_parented,
+3 -8
View File
@@ -23,11 +23,6 @@ async def to_code(config: ConfigType) -> None:
await cg.register_component(var, config)
await stepper.register_stepper(var, config)
step_pin = await cg.gpio_pin_expression(config[CONF_STEP_PIN])
cg.add(var.set_step_pin(step_pin))
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin))
if sleep_pin_config := config.get(CONF_SLEEP_PIN):
sleep_pin = await cg.gpio_pin_expression(sleep_pin_config)
cg.add(var.set_sleep_pin(sleep_pin))
await cg.add_pin(var.set_step_pin, config[CONF_STEP_PIN])
await cg.add_pin(var.set_dir_pin, config[CONF_DIR_PIN])
await cg.add_pin(var.set_sleep_pin, config.get(CONF_SLEEP_PIN))
@@ -19,11 +19,7 @@ AbsoluteHumidityComponent = absolute_humidity_ns.class_(
SaturationVaporPressureEquation = absolute_humidity_ns.enum(
"SaturationVaporPressureEquation"
)
EQUATION = {
"BUCK": SaturationVaporPressureEquation.BUCK,
"TETENS": SaturationVaporPressureEquation.TETENS,
"WOBUS": SaturationVaporPressureEquation.WOBUS,
}
EQUATION = SaturationVaporPressureEquation.options("BUCK", "TETENS", "WOBUS")
CONFIG_SCHEMA = (
sensor.sensor_schema(
+5 -9
View File
@@ -14,11 +14,9 @@ ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
DimMethod = ac_dimmer_ns.enum("DimMethod")
DIM_METHODS = {
"LEADING_PULSE": DimMethod.DIM_METHOD_LEADING_PULSE,
"LEADING": DimMethod.DIM_METHOD_LEADING,
"TRAILING": DimMethod.DIM_METHOD_TRAILING,
}
DIM_METHODS = DimMethod.options(
"LEADING_PULSE", "LEADING", "TRAILING", prefix="DIM_METHOD_"
)
ZC_INTERRUPT_TYPES = {
"RISING": gpio_ns.INTERRUPT_RISING_EDGE,
@@ -70,10 +68,8 @@ async def to_code(config: ConfigType) -> None:
config[CONF_MIN_POWER] = 0.1
await output.register_output(var, config)
pin = await cg.gpio_pin_expression(config[CONF_GATE_PIN])
cg.add(var.set_gate_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
cg.add(var.set_zero_cross_pin(pin))
await cg.add_pin(var.set_gate_pin, config[CONF_GATE_PIN])
await cg.add_pin(var.set_zero_cross_pin, config[CONF_ZERO_CROSS_PIN])
cg.add(var.set_zero_cross_interrupt_type(config[CONF_ZERO_CROSS_INTERRUPT_TYPE]))
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
cg.add(var.set_method(config[CONF_METHOD]))
+1 -5
View File
@@ -46,11 +46,7 @@ ATTENUATION_MODES = {
sampling_mode = adc_ns.enum("SamplingMode", is_class=True)
SAMPLING_MODES = {
"avg": sampling_mode.AVG,
"min": sampling_mode.MIN,
"max": sampling_mode.MAX,
}
SAMPLING_MODES = sampling_mode.options("avg", "min", "max")
adc_unit_t = cg.global_ns.enum("adc_unit_t", is_class=True)
+1 -2
View File
@@ -164,8 +164,7 @@ async def to_code(config: ConfigType) -> None:
# Remove before 2027.2.0
cg.add(var.set_is_temperature())
elif not CORE.is_nrf52 or config[CONF_PIN][CONF_NUMBER] not in EXTRA_ADC:
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
await cg.add_pin(var.set_pin, config[CONF_PIN])
# Skip the setters when the config matches the C++ initializers.
if config[CONF_RAW]:
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["spi"]
@@ -20,6 +19,4 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await spi.new_spi_component(config)
@@ -56,8 +56,4 @@ async def to_code(config: ConfigType) -> None:
)
cg.add(var.set_pixel_mapper(pixel_mapper_template_))
if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda(
lambda_config, [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
await display.add_writer(var, config)
+1 -7
View File
@@ -11,12 +11,6 @@
namespace esphome::ade7880 {
// adapted from https://stackoverflow.com/a/55912127/1886371
template<size_t Bits, typename T> inline T sign_extend(const T &v) noexcept {
using S = struct { signed Val : Bits; };
return reinterpret_cast<const S *>(&v)->Val;
}
// Register types
// unsigned 8-bit (uint8_t)
// signed 10-bit - 16-bit ZP on wire (int16_t, needs sign extension)
@@ -49,7 +43,7 @@ uint16_t ADE7880::read_u16_register16_(uint16_t a_register) {
int32_t ADE7880::read_s24zp_register16_(uint16_t a_register) {
// s24zp means 24 bit signed value in the lower 24 bits of a 32-bit register
int32_t in;
uint32_t in;
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
return sign_extend<24>(convert_big_endian(in));
}
+4 -13
View File
@@ -282,20 +282,11 @@ async def power_channel(config: ConfigType) -> MockObj:
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)
var = await i2c.new_i2c_component(config)
if irq0_pin := config.get(CONF_IRQ0_PIN):
pin = await cg.gpio_pin_expression(irq0_pin)
cg.add(var.set_irq0_pin(pin))
pin = await cg.gpio_pin_expression(config[CONF_IRQ1_PIN])
cg.add(var.set_irq1_pin(pin))
if reset_pin := config.get(CONF_RESET_PIN):
pin = await cg.gpio_pin_expression(reset_pin)
cg.add(var.set_reset_pin(pin))
await cg.add_pin(var.set_irq0_pin, config.get(CONF_IRQ0_PIN))
await cg.add_pin(var.set_irq1_pin, config[CONF_IRQ1_PIN])
await cg.add_pin(var.set_reset_pin, config.get(CONF_RESET_PIN))
if frequency := config.get(CONF_FREQUENCY):
cg.add(var.set_frequency(frequency))
+1 -3
View File
@@ -169,9 +169,7 @@ ADE7953_CONFIG_SCHEMA = cv.Schema(
async def register_ade7953(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin))
await cg.add_pin(var.set_irq_pin, config.get(CONF_IRQ_PIN))
cg.add(var.set_pga_v(PGA_GAINS[config.get(CONF_VOLTAGE_PGA_GAIN)]))
cg.add(var.set_pga_ia(PGA_GAINS[config.get(CONF_CURRENT_PGA_GAIN_A)]))
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -27,8 +26,6 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_continuous_mode(config[CONF_CONTINUOUS_MODE]))
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import spi
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
CODEOWNERS = ["@solomondg1"]
@@ -26,6 +25,4 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
await spi.new_spi_component(config)
+4 -9
View File
@@ -4,7 +4,6 @@ from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_ID,
CONF_MODE,
CONF_TVOC,
CONF_VALUE,
@@ -58,9 +57,7 @@ FINAL_VALIDATE_SCHEMA = i2c.final_validate_device_schema("ags10", max_frequency=
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_TVOC, var.set_tvoc)
@@ -84,11 +81,9 @@ automation.register_apply_action(
)
AGS10SetZeroPointActionMode = ags10_ns.enum("AGS10SetZeroPointActionMode")
AGS10_SET_ZERO_POINT_ACTION_MODE = {
"FACTORY_DEFAULT": AGS10SetZeroPointActionMode.FACTORY_DEFAULT,
"CURRENT_VALUE": AGS10SetZeroPointActionMode.CURRENT_VALUE,
"CUSTOM_VALUE": AGS10SetZeroPointActionMode.CUSTOM_VALUE,
}
AGS10_SET_ZERO_POINT_ACTION_MODE = AGS10SetZeroPointActionMode.options(
"FACTORY_DEFAULT", "CURRENT_VALUE", "CUSTOM_VALUE"
)
AGS10_SET_ZERO_POINT_SCHEMA = cv.Schema(
{
+2 -8
View File
@@ -3,7 +3,6 @@ from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
CONF_VARIANT,
DEVICE_CLASS_HUMIDITY,
@@ -20,10 +19,7 @@ aht10_ns = cg.esphome_ns.namespace("aht10")
AHT10Component = aht10_ns.class_("AHT10Component", cg.PollingComponent, i2c.I2CDevice)
AHT10Variant = aht10_ns.enum("AHT10Variant")
AHT10_VARIANTS = {
"AHT10": AHT10Variant.AHT10,
"AHT20": AHT10Variant.AHT20,
}
AHT10_VARIANTS = AHT10Variant.options("AHT10", "AHT20")
CONFIG_SCHEMA = (
cv.Schema(
@@ -52,9 +48,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_variant(config[CONF_VARIANT]))
sensors = sensor.sub_sensors(config)
+2 -4
View File
@@ -3,7 +3,7 @@ import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.audio_dac import AudioDac
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE
from esphome.const import CONF_MODE
from esphome.types import ConfigType
CODEOWNERS = ["@kbx81"]
@@ -41,6 +41,4 @@ 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 i2c.register_i2c_device(var, config)
await i2c.new_i2c_component(config)
@@ -29,10 +29,7 @@ AirthingsWavePlus = airthings_wave_plus_ns.class_(
CONF_DEVICE_TYPE = "device_type"
WaveDeviceType = airthings_wave_plus_ns.enum("WaveDeviceType")
DEVICE_TYPES = {
"WAVE_PLUS": WaveDeviceType.WAVE_PLUS,
"WAVE_GEN2": WaveDeviceType.WAVE_GEN2,
}
DEVICE_TYPES = WaveDeviceType.options("WAVE_PLUS", "WAVE_GEN2")
def validate_wave_gen2_config(config: ConfigType) -> ConfigType:
+2
View File
@@ -0,0 +1,2 @@
CODEOWNERS = ["@Procsiab"]
DOMAIN = "airton"
+319
View File
@@ -0,0 +1,319 @@
#include "airton.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::airton {
ESPHOME_LOG_TAG(TAG, "airton.climate");
// Distinct from the climate's own RESTORE_STATE_VERSION, which shares the entity's preference key
static constexpr uint32_t RESTORE_SETTINGS_VERSION = 0x2E86A1F3UL;
// From IRutils.h of IRremoteESP8266 library
static uint8_t checksum(const uint8_t *frame) {
return static_cast<uint8_t>(0x7F - sum_bytes(frame, AIRTON_STATE_FRAME_SIZE - 1)) ^ 0x2C;
}
void AirtonClimate::setup() {
climate_ir::ClimateIR::setup();
// The flap position is the one source of truth for the swing mode the climate state restored
this->settings_pref_ = this->make_entity_preference<AirtonSettings>(RESTORE_SETTINGS_VERSION);
this->settings_pref_.load(&this->settings_);
this->sync_swing_mode_();
this->publish_settings_();
this->publish_state();
}
void AirtonClimate::publish_settings_() {
#ifdef USE_SWITCH
if (this->sleep_mode_switch_ != nullptr)
this->sleep_mode_switch_->publish_state(this->settings_.sleep_state);
if (this->display_switch_ != nullptr)
this->display_switch_->publish_state(this->settings_.display_state);
#endif
#ifdef USE_SELECT
if (this->vertical_direction_select_ != nullptr)
this->vertical_direction_select_->publish_state(static_cast<size_t>(this->settings_.vertical_direction));
#endif
}
void AirtonClimate::save_settings_() { this->settings_pref_.save(&this->settings_); }
void AirtonClimate::sync_swing_mode_() {
this->swing_mode = this->settings_.vertical_direction == VerticalDirection::VERTICAL_DIRECTION_SWING
? climate::CLIMATE_SWING_VERTICAL
: climate::CLIMATE_SWING_OFF;
}
// The unit has no sleep mode while it only fans or picks the mode itself
bool AirtonClimate::sleep_mode_allowed_() const {
return this->mode != climate::CLIMATE_MODE_FAN_ONLY && this->mode != climate::CLIMATE_MODE_HEAT_COOL;
}
// Each setter stores and saves before it publishes: an automation on the entity runs inside
// publish_state() and may set the value again, which must not be undone afterwards
bool AirtonClimate::set_sleep_mode_state(bool state) {
state = state && this->sleep_mode_allowed_();
const bool changed = state != this->settings_.sleep_state;
if (changed) {
this->settings_.sleep_state = state;
this->save_settings_();
}
#ifdef USE_SWITCH
if (this->sleep_mode_switch_ != nullptr)
this->sleep_mode_switch_->publish_state(state);
#endif
return changed;
}
bool AirtonClimate::set_display_state(bool state) {
const bool changed = state != this->settings_.display_state;
if (changed) {
this->settings_.display_state = state;
this->save_settings_();
}
#ifdef USE_SWITCH
if (this->display_switch_ != nullptr)
this->display_switch_->publish_state(state);
#endif
return changed;
}
bool AirtonClimate::set_vertical_direction_state(VerticalDirection direction) {
const bool changed = direction != this->settings_.vertical_direction;
if (changed) {
this->settings_.vertical_direction = direction;
this->sync_swing_mode_();
this->save_settings_();
}
#ifdef USE_SELECT
if (this->vertical_direction_select_ != nullptr)
this->vertical_direction_select_->publish_state(static_cast<size_t>(direction));
#endif
return changed;
}
void AirtonClimate::send_settings() {
this->transmit_state();
this->publish_state();
}
void AirtonClimate::control(const climate::ClimateCall &call) {
// ClimateIR::control() sends and publishes the whole state afterwards
// Only a change of swing mode moves the flap, so a repeated swing-off keeps a fixed position
if (auto swing_mode = call.get_swing_mode(); swing_mode.has_value() && *swing_mode != this->swing_mode) {
this->set_vertical_direction_state(*swing_mode == climate::CLIMATE_SWING_VERTICAL
? VerticalDirection::VERTICAL_DIRECTION_SWING
: VerticalDirection::VERTICAL_DIRECTION_OFF);
}
ClimateIR::control(call);
}
void AirtonClimate::transmit_state() {
// Entering fan-only or automatic mode drops sleep mode, as the remote does
if (!this->sleep_mode_allowed_())
this->set_sleep_mode_state(false);
// Sampled valid state
// Power: On, Mode: 2 (Dry), Fan: 1 (Quiet), Temp: 20C, Swing(V): On, Econo: Off, Turbo: Off, Light: On, Health: On,
// Sleep: Off. 0x74C461041A11D3
// The turbo bit above the fan speed stays clear, as the remote sends it
uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {
AIRTON_HEADER_BYTE0,
AIRTON_HEADER_BYTE1,
static_cast<uint8_t>(this->operation_mode_() | (this->fan_speed_() << 4)),
this->temperature_(),
static_cast<uint8_t>(this->settings_.vertical_direction),
this->operation_settings_(),
0,
};
remote_state[6] = checksum(remote_state);
ESP_LOGV(TAG, "Sending: %02X %02X %02X %02X %02X %02X %02X", remote_state[6], remote_state[5], remote_state[4],
remote_state[3], remote_state[2], remote_state[1], remote_state[0]);
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->set_carrier_frequency(AIRTON_IR_FREQUENCY);
data->mark(AIRTON_HEADER_MARK);
data->space(AIRTON_HEADER_SPACE);
for (uint8_t payload_byte : remote_state) {
for (uint8_t payload_bit_cursor = 0; payload_bit_cursor < 8; payload_bit_cursor++) {
data->mark(AIRTON_BIT_MARK);
bool bit = payload_byte & (1 << payload_bit_cursor);
data->space(bit ? AIRTON_ONE_SPACE : AIRTON_ZERO_SPACE);
}
}
data->mark(AIRTON_BIT_MARK);
data->space(AIRTON_MESSAGE_SPACE);
transmit.perform();
}
uint8_t AirtonClimate::operation_mode_() {
uint8_t operating_mode = 0b1000; // First bit is for power state
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
operating_mode |= AIRTON_MODE_COOL;
break;
case climate::CLIMATE_MODE_DRY:
operating_mode |= AIRTON_MODE_DRY;
break;
case climate::CLIMATE_MODE_HEAT:
operating_mode |= AIRTON_MODE_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= AIRTON_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
operating_mode |= AIRTON_MODE_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
operating_mode = 0b0111 & this->previous_mode_; // Set previous mode with power state bit off
}
this->previous_mode_ = operating_mode;
return operating_mode;
}
uint8_t AirtonClimate::fan_speed_() {
uint8_t fan_speed;
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_AUTO)) {
case climate::CLIMATE_FAN_LOW:
fan_speed = AIRTON_FAN_1;
break;
case climate::CLIMATE_FAN_MEDIUM:
fan_speed = AIRTON_FAN_3;
break;
case climate::CLIMATE_FAN_HIGH:
fan_speed = AIRTON_FAN_5;
break;
case climate::CLIMATE_FAN_AUTO:
default:
fan_speed = AIRTON_FAN_AUTO;
}
return fan_speed;
}
uint8_t AirtonClimate::temperature_() {
switch (this->mode) {
case climate::CLIMATE_MODE_HEAT_COOL:
return AIRTON_AUTO_MODE_TEMP - AIRTON_TEMP_MIN;
default:
return static_cast<uint8_t>(roundf(clamp<float>(this->target_temperature, AIRTON_TEMP_MIN, AIRTON_TEMP_MAX))) -
AIRTON_TEMP_MIN;
}
}
// The bits of this packet's byte have the following meanings (from MSB to LSB)
// Light, Health, Unknown, HeatOn, Unknown, NotAutoOn, Sleep, Econo
uint8_t AirtonClimate::operation_settings_() {
uint8_t settings = 0;
if (this->mode == climate::CLIMATE_MODE_HEAT) { // Set heating bit if on the corresponding mode
settings |= (1 << 4);
}
if (this->settings_.display_state) {
settings |= (1 << 7);
}
if (this->settings_.sleep_state) {
settings |= (1 << 1);
}
settings |= 0b01000000; // Health, as the remote always sends it
if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) {
settings |= 0b00000100; // NotAutoOn: clear only while the unit picks the mode itself
}
return settings;
}
void AirtonClimate::parse_state_frame_(const uint8_t *frame) {
uint8_t mode = frame[2];
// A later "off" repeats the mode the remote last sent, as the remote itself does
this->previous_mode_ = mode & 0b00001111;
if (mode & 0b00001000) { // The power bit
switch (mode & 0b00000111) {
case AIRTON_MODE_COOL:
this->mode = climate::CLIMATE_MODE_COOL;
break;
case AIRTON_MODE_DRY:
this->mode = climate::CLIMATE_MODE_DRY;
break;
case AIRTON_MODE_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case AIRTON_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case AIRTON_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
}
} else {
this->mode = climate::CLIMATE_MODE_OFF;
}
uint8_t fan_mode = (frame[2] & 0b01110000) >> 4;
switch (fan_mode) {
case AIRTON_FAN_1:
case AIRTON_FAN_2:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case AIRTON_FAN_3:
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
break;
case AIRTON_FAN_4:
case AIRTON_FAN_5:
this->fan_mode = climate::CLIMATE_FAN_HIGH;
break;
case AIRTON_FAN_AUTO:
this->fan_mode = climate::CLIMATE_FAN_AUTO;
break;
}
this->target_temperature = (frame[3] & 0b00001111) + AIRTON_TEMP_MIN;
const uint8_t direction = frame[4] & 0b00001111;
if (direction <= static_cast<uint8_t>(VerticalDirection::VERTICAL_DIRECTION_DOWN)) {
this->set_vertical_direction_state(static_cast<VerticalDirection>(direction));
} else {
ESP_LOGV(TAG, "Unknown vertical direction %u", direction);
}
this->set_display_state((frame[5] & 0b10000000) != 0);
this->set_sleep_mode_state((frame[5] & 0b00000010) != 0);
this->publish_state();
}
bool AirtonClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t remote_state[AIRTON_STATE_FRAME_SIZE] = {};
if (!data.expect_item(AIRTON_HEADER_MARK, AIRTON_HEADER_SPACE)) {
ESP_LOGV(TAG, "Wrong header encoding detected!");
return false;
}
for (int i = 0; i < AIRTON_STATE_FRAME_SIZE; i++) {
for (int j = 0; j < 8; j++) {
if (data.expect_item(AIRTON_BIT_MARK, AIRTON_ONE_SPACE)) {
remote_state[i] |= 1 << j;
} else if (!data.expect_item(AIRTON_BIT_MARK, AIRTON_ZERO_SPACE)) {
ESP_LOGV(TAG, "Wrong modulation encoding for: Byte %d, bit %d", i, j);
return false;
}
}
}
if (remote_state[0] != AIRTON_HEADER_BYTE0 || remote_state[1] != AIRTON_HEADER_BYTE1) {
ESP_LOGV(TAG, "Wrong header contents: %02X %02X", remote_state[1], remote_state[0]);
return false;
}
const uint8_t expected = checksum(remote_state);
if (remote_state[AIRTON_STATE_FRAME_SIZE - 1] != expected) {
ESP_LOGV(TAG, "Checksum error: calculated %02X, received %02X", expected,
remote_state[AIRTON_STATE_FRAME_SIZE - 1]);
return false;
}
ESP_LOGV(TAG, "Received: %02X %02X %02X %02X %02X %02X %02X", remote_state[6], remote_state[5], remote_state[4],
remote_state[3], remote_state[2], remote_state[1], remote_state[0]);
this->parse_state_frame_(remote_state);
return true;
}
} // namespace esphome::airton
+137
View File
@@ -0,0 +1,137 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
namespace esphome::airton {
// Values for Airton SMVH09B-2A2A3NH IR Controllers
static constexpr uint8_t AIRTON_TEMP_MIN = 16; // Celsius
static constexpr uint8_t AIRTON_TEMP_MAX = 31; // Celsius
static constexpr uint8_t AIRTON_AUTO_MODE_TEMP = 25; // The fixed setpoint the unit uses in auto mode
static constexpr uint8_t AIRTON_MODE_AUTO = 0b000;
static constexpr uint8_t AIRTON_MODE_COOL = 0b001;
static constexpr uint8_t AIRTON_MODE_HEAT = 0b100;
static constexpr uint8_t AIRTON_MODE_DRY = 0b010;
static constexpr uint8_t AIRTON_MODE_FAN = 0b011;
static constexpr uint8_t AIRTON_FAN_AUTO = 0b000;
static constexpr uint8_t AIRTON_FAN_1 = 0b001;
static constexpr uint8_t AIRTON_FAN_2 = 0b010;
static constexpr uint8_t AIRTON_FAN_3 = 0b011;
static constexpr uint8_t AIRTON_FAN_4 = 0b100;
static constexpr uint8_t AIRTON_FAN_5 = 0b101;
static constexpr uint32_t AIRTON_IR_FREQUENCY = 38000;
static constexpr uint32_t AIRTON_HEADER_MARK = 6630;
static constexpr uint32_t AIRTON_HEADER_SPACE = 3350;
static constexpr uint32_t AIRTON_BIT_MARK = 400;
static constexpr uint32_t AIRTON_ONE_SPACE = 1260;
static constexpr uint32_t AIRTON_ZERO_SPACE = 430;
static constexpr uint32_t AIRTON_MESSAGE_SPACE = 100000;
static constexpr uint8_t AIRTON_STATE_FRAME_SIZE = 7;
// Every frame starts with these two bytes
static constexpr uint8_t AIRTON_HEADER_BYTE0 = 0xD3;
static constexpr uint8_t AIRTON_HEADER_BYTE1 = 0x11;
// Position of the air outlet flap; the values are the nibble the frame carries and the index
// of the option in the vertical direction select
enum class VerticalDirection : uint8_t {
VERTICAL_DIRECTION_OFF = 0,
VERTICAL_DIRECTION_SWING = 1,
VERTICAL_DIRECTION_UP = 2,
VERTICAL_DIRECTION_MIDDLE_UP = 3,
VERTICAL_DIRECTION_MIDDLE = 4,
VERTICAL_DIRECTION_MIDDLE_DOWN = 5,
VERTICAL_DIRECTION_DOWN = 6,
};
// The on/off settings the switch platform exposes
enum class AirtonSetting : uint8_t {
AIRTON_SETTING_SLEEP_MODE,
AIRTON_SETTING_DISPLAY,
};
// The settings the unit keeps that the climate state does not carry; saved to flash when they change
struct AirtonSettings {
bool sleep_state;
bool display_state;
VerticalDirection vertical_direction;
};
class AirtonClimate : public climate_ir::ClimateIR {
public:
AirtonClimate()
: climate_ir::ClimateIR(AIRTON_TEMP_MIN, AIRTON_TEMP_MAX, 1.0f, true, true,
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
void setup() override;
void control(const climate::ClimateCall &call) override;
// The direction the settings start from until a saved one is restored in setup()
void set_default_vertical_direction(VerticalDirection direction) { this->settings_.vertical_direction = direction; }
// The setters update the state, publish their entity and save; they return whether anything changed.
// A change made here (switch, select, action) is then sent with send_settings(); a frame from the
// remote only reports what the unit already did.
bool set_sleep_mode_state(bool state);
bool set_display_state(bool state);
bool set_vertical_direction_state(VerticalDirection direction);
void send_settings();
void set_setting(AirtonSetting setting, bool state) {
const bool changed = setting == AirtonSetting::AIRTON_SETTING_DISPLAY ? this->set_display_state(state)
: this->set_sleep_mode_state(state);
if (changed)
this->send_settings();
}
#ifdef USE_SWITCH
void set_sleep_mode_switch(switch_::Switch *sw) { this->sleep_mode_switch_ = sw; }
void set_display_switch(switch_::Switch *sw) { this->display_switch_ = sw; }
#endif
#ifdef USE_SELECT
void set_vertical_direction_select(select::Select *sel) { this->vertical_direction_select_ = sel; }
#endif
protected:
void publish_settings_();
void save_settings_();
void sync_swing_mode_();
bool sleep_mode_allowed_() const;
void transmit_state() override;
uint8_t operation_mode_();
uint8_t fan_speed_();
uint8_t temperature_();
uint8_t operation_settings_();
bool on_receive(remote_base::RemoteReceiveData data) override;
void parse_state_frame_(const uint8_t *frame);
#ifdef USE_SWITCH
switch_::Switch *sleep_mode_switch_{nullptr};
switch_::Switch *display_switch_{nullptr};
#endif
#ifdef USE_SELECT
select::Select *vertical_direction_select_{nullptr};
#endif
ESPPreferenceObject settings_pref_;
// The unit's own defaults: display lit, no sleep mode, flap still
AirtonSettings settings_{false, true, VerticalDirection::VERTICAL_DIRECTION_OFF};
// The last mode sent, so turning off repeats it with the power bit cleared
uint8_t previous_mode_{0};
};
} // namespace esphome::airton
+70
View File
@@ -0,0 +1,70 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.types import ConfigType
AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@Procsiab"]
airton_ns = cg.esphome_ns.namespace("airton")
AirtonClimate = airton_ns.class_("AirtonClimate", climate_ir.ClimateIR)
CONF_AIRTON_ID = "airton_id"
CONF_SLEEP_MODE = "sleep_mode"
CONF_VERTICAL_DIRECTION = "vertical_direction"
VerticalDirection = airton_ns.enum("VerticalDirection", is_class=True)
# In the order of the enum, so the option index is the direction value
VERTICAL_DIRECTIONS = {
"off": VerticalDirection.VERTICAL_DIRECTION_OFF,
"swing": VerticalDirection.VERTICAL_DIRECTION_SWING,
"up": VerticalDirection.VERTICAL_DIRECTION_UP,
"middle-up": VerticalDirection.VERTICAL_DIRECTION_MIDDLE_UP,
"middle": VerticalDirection.VERTICAL_DIRECTION_MIDDLE,
"middle-down": VerticalDirection.VERTICAL_DIRECTION_MIDDLE_DOWN,
"down": VerticalDirection.VERTICAL_DIRECTION_DOWN,
}
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(AirtonClimate).extend(
{
cv.Optional(CONF_VERTICAL_DIRECTION, default="off"): cv.enum(
VERTICAL_DIRECTIONS
),
}
)
AIRTON_ACTION_SCHEMA = automation.maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AirtonClimate),
}
)
for _name, _call in (
(
"climate.airton.display_on",
"set_setting(esphome::airton::AirtonSetting::AIRTON_SETTING_DISPLAY, true)",
),
(
"climate.airton.display_off",
"set_setting(esphome::airton::AirtonSetting::AIRTON_SETTING_DISPLAY, false)",
),
(
"climate.airton.sleep_on",
"set_setting(esphome::airton::AirtonSetting::AIRTON_SETTING_SLEEP_MODE, true)",
),
(
"climate.airton.sleep_off",
"set_setting(esphome::airton::AirtonSetting::AIRTON_SETTING_SLEEP_MODE, false)",
),
):
automation.register_apply_action(
_name, AIRTON_ACTION_SCHEMA, automation.ApplyCall(_call)
)
async def to_code(config: ConfigType) -> None:
var = await climate_ir.new_climate_ir(config)
# The default until a saved setting is restored in setup(); off is the member's initial value
if (direction := config[CONF_VERTICAL_DIRECTION]) != "off":
cg.add(var.set_default_vertical_direction(direction))
@@ -0,0 +1,37 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from ..climate import (
CONF_AIRTON_ID,
CONF_VERTICAL_DIRECTION,
VERTICAL_DIRECTIONS,
AirtonClimate,
airton_ns,
)
CODEOWNERS = ["@Procsiab"]
VerticalDirectionSelect = airton_ns.class_("VerticalDirectionSelect", select.Select)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate),
cv.Optional(CONF_VERTICAL_DIRECTION): select.select_schema(
VerticalDirectionSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AIRTON_ID])
selects = select.sub_selects(config, parent=hub)
await selects(
CONF_VERTICAL_DIRECTION,
hub.set_vertical_direction_select,
options=list(VERTICAL_DIRECTIONS),
)
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../airton.h"
namespace esphome::airton {
class VerticalDirectionSelect : public select::Select, public Parented<AirtonClimate> {
protected:
// The options are in the order of the enum, so the index is the direction; the parent publishes the state
void control(size_t index) override {
if (this->parent_->set_vertical_direction_state(static_cast<VerticalDirection>(index)))
this->parent_->send_settings();
}
};
} // namespace esphome::airton
@@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_DISPLAY, ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from ..climate import CONF_AIRTON_ID, CONF_SLEEP_MODE, AirtonClimate, airton_ns
CODEOWNERS = ["@Procsiab"]
AirtonSwitch = airton_ns.class_("AirtonSwitch", switch.Switch)
AirtonSetting = airton_ns.enum("AirtonSetting", is_class=True)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_AIRTON_ID): cv.use_id(AirtonClimate),
cv.Optional(CONF_DISPLAY): switch.switch_schema(
AirtonSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
cv.Optional(CONF_SLEEP_MODE): switch.switch_schema(
AirtonSwitch,
entity_category=ENTITY_CATEGORY_CONFIG,
default_restore_mode="DISABLED",
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AIRTON_ID])
switches = switch.sub_switches(config, parent=hub)
await switches(
CONF_DISPLAY, hub.set_display_switch, AirtonSetting.AIRTON_SETTING_DISPLAY
)
await switches(
CONF_SLEEP_MODE,
hub.set_sleep_mode_switch,
AirtonSetting.AIRTON_SETTING_SLEEP_MODE,
)
@@ -0,0 +1,19 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../airton.h"
namespace esphome::airton {
// One of the unit's on/off settings; the parent sends the state and publishes it back
class AirtonSwitch : public switch_::Switch, public Parented<AirtonClimate> {
public:
explicit AirtonSwitch(AirtonSetting setting) : setting_(setting) {}
protected:
void write_state(bool state) override { this->parent_->set_setting(this->setting_, state); }
AirtonSetting setting_;
};
} // namespace esphome::airton
+1 -1
View File
@@ -64,7 +64,7 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
uint32_t raw;
raw = (data[1] << 12) | (data[2] << 4) | (data[3] >> 4);
humidity = raw * 9.5367431640625e-5;
raw = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
raw = encode_uint24(data[3] & 0x0F, data[4], data[5]);
temperature = raw * 1.9073486328125e-4 - 50;
return crc8(data, 6, 0xFF, 0x31, true) == data[6];
}
+1 -4
View File
@@ -3,7 +3,6 @@ from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
@@ -42,9 +41,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
+1 -4
View File
@@ -3,7 +3,6 @@ from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HUMIDITY,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
@@ -44,9 +43,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
+1 -1
View File
@@ -19,7 +19,7 @@ CONFIG_SCHEMA = (
cover.cover_schema(Am43Component)
.extend(
{
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_PIN, default=8888): cv.uint16_t,
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
)
+2 -11
View File
@@ -28,15 +28,6 @@ static constexpr uint32_t APC1_NO_DATA_TIMEOUT_MS = 15000;
// Drop a partial frame when the bytes stop mid-way
static constexpr uint32_t APC1_RX_IDLE_TIMEOUT_MS = 200;
// Frames and commands end with the 16-bit sum of every byte before it
static uint16_t apc1_checksum(const uint8_t *data, size_t len) {
uint16_t sum = 0;
for (size_t i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}
void APC1Component::setup() {
if (this->set_pin_ != nullptr) {
this->set_pin_->setup();
@@ -157,7 +148,7 @@ void APC1Component::loop() {
void APC1Component::parse_frame_(uint16_t total_len) {
uint16_t expected_checksum = encode_uint16(this->rx_buffer_[total_len - 2], this->rx_buffer_[total_len - 1]);
uint16_t calculated_checksum = apc1_checksum(this->rx_buffer_.data(), total_len - 2);
uint16_t calculated_checksum = sum16_bytes(this->rx_buffer_.data(), total_len - 2);
if (expected_checksum != calculated_checksum) {
ESP_LOGW(TAG, "APC1 checksum mismatch: calculated 0x%04X != expected 0x%04X", calculated_checksum,
expected_checksum);
@@ -343,7 +334,7 @@ void APC1Component::send_command_(APC1Command cmd, uint16_t data) {
0,
0,
};
uint16_t checksum = apc1_checksum(buf.data(), 5);
uint16_t checksum = sum16_bytes(buf.data(), 5);
buf[5] = static_cast<uint8_t>((checksum >> 8) & 0xFF);
buf[6] = static_cast<uint8_t>(checksum & 0xFF);
this->write_array(buf);
+3 -11
View File
@@ -7,7 +7,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_ECO2,
CONF_HUMIDITY,
CONF_ID,
CONF_PM_0_3UM,
CONF_PM_0_5UM,
CONF_PM_1_0,
@@ -165,9 +164,7 @@ FINAL_VALIDATE_SCHEMA = final_validate
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_PM_1_0, var.set_pm_1_0_sensor)
@@ -194,13 +191,8 @@ async def to_code(config: ConfigType) -> None:
await sensors(CONF_RS3, var.set_rs3_sensor)
await sensors(CONF_ERROR_CODE, var.set_error_code_sensor)
if (set_pin_config := config.get(CONF_SET_PIN)) is not None:
set_pin = await cg.gpio_pin_expression(set_pin_config)
cg.add(var.set_set_pin(set_pin))
if (reset_pin_config := config.get(CONF_RESET_PIN)) is not None:
reset_pin = await cg.gpio_pin_expression(reset_pin_config)
cg.add(var.set_reset_pin(reset_pin))
await cg.add_pin(var.set_set_pin, config.get(CONF_SET_PIN))
await cg.add_pin(var.set_reset_pin, config.get(CONF_RESET_PIN))
APC1_ACTION_SCHEMA = maybe_simple_id(
@@ -32,40 +32,35 @@ void APCProteousCover::loop() {
if (!this->read_byte(&data)) {
continue;
}
if (data == '\r') {
if (this->rx_len_ != 0) {
this->rx_buffer_[this->rx_len_] = '\0';
ESP_LOGVV(TAG, "rx: '%s'", this->rx_buffer_);
this->parse_response_();
this->rx_len_ = 0;
}
} else if (data != '\n') {
if (this->rx_len_ >= MAX_RESPONSE_LEN) {
ESP_LOGVV(TAG, "rx overflow, discarding");
this->rx_len_ = 0;
}
this->rx_buffer_[this->rx_len_++] = static_cast<char>(data);
const char *line = this->line_reader_.feed(data);
if (this->line_reader_.overflowed()) {
// Start over from this byte, so a frame right after unterminated noise is still read
ESP_LOGVV(TAG, "rx overflow, discarding");
this->line_reader_.restart_with(data);
} else if (line != nullptr && line[0] != '\0') {
ESP_LOGVV(TAG, "rx: '%s'", line);
this->parse_response_(line);
}
}
}
void APCProteousCover::parse_response_() {
void APCProteousCover::parse_response_(const char *line) {
// Expected format: "?s=XX" or "?x=XX" where XX is a hex value
if (this->rx_len_ < 4 || this->rx_buffer_[0] != '?' || this->rx_buffer_[2] != '=') {
ESP_LOGV(TAG, "Invalid response: %s", this->rx_buffer_);
if (line[0] != '?' || line[1] == '\0' || line[2] != '=' || line[3] == '\0') {
ESP_LOGV(TAG, "Invalid response: %s", line);
return;
}
const char *hex_str = this->rx_buffer_ + 3;
const char *hex_str = line + 3;
char *end_ptr;
uint8_t value = static_cast<uint8_t>(strtol(hex_str, &end_ptr, 16));
if (end_ptr == hex_str) {
ESP_LOGW(TAG, "Failed to parse hex value: %s", this->rx_buffer_);
ESP_LOGW(TAG, "Failed to parse hex value: %s", line);
return;
}
bool state_changed = false;
char type = this->rx_buffer_[1];
char type = line[1];
if (type == 's') {
// bit 0 = operating, bit 1 = direction (0 = opening, 1 = closing)
CoverOperation new_operation = COVER_OPERATION_IDLE;
@@ -154,11 +149,10 @@ void APCProteousCover::update() {
return;
}
// The line is idle, so anything left in the buffer is an unterminated echo
if (this->rx_len_ != 0) {
this->rx_buffer_[this->rx_len_] = '\0';
ESP_LOGVV(TAG, "discarding stale rx '%s' before query", this->rx_buffer_);
this->rx_len_ = 0;
if (this->line_reader_.length() != 0) {
ESP_LOGVV(TAG, "discarding stale rx '%s' before query", this->line_reader_.partial());
}
this->line_reader_.reset();
const char *query = this->query_s_next_ ? QUERY_S : QUERY_X;
ESP_LOGVV(TAG, "tx: %s", query);
@@ -1,6 +1,7 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/line_reader.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/cover/cover.h"
@@ -17,7 +18,7 @@ class APCProteousCover : public cover::Cover, public PollingComponent, public ua
protected:
void control(const cover::CoverCall &call) override;
void parse_response_();
void parse_response_(const char *line);
void send_command_(const char *cmd);
void write_command_(const char *cmd, uint32_t now);
void stop_cmd_(uint32_t now);
@@ -43,8 +44,7 @@ class APCProteousCover : public cover::Cover, public PollingComponent, public ua
const char *pending_command_{nullptr};
uint32_t pending_command_time_{0};
uint32_t last_command_tx_{0};
char rx_buffer_[MAX_RESPONSE_LEN + 1];
uint8_t rx_len_{0};
LineReader<MAX_RESPONSE_LEN + 1, '\r', '\n'> line_reader_;
uint8_t command_retries_{0};
bool query_s_next_{true};
};
+2 -9
View File
@@ -52,16 +52,9 @@ enum { // APDS9306 registers
}
void APDS9306::setup() {
uint8_t id;
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
// 0xB1 for APDS9306, 0xB3 for APDS9306-065; they differ only in bit 1
if (!this->check_chip_id(this, APDS9306_PART_ID, 0xB1, 0xFD))
return;
}
if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065
this->mark_failed(LOG_STR("APDS9306 has invalid id"));
return;
}
// ALS resolution and measurement, see datasheet or init.py for options
uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07);
+5 -12
View File
@@ -1,5 +1,6 @@
#include "apds9930.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <algorithm>
@@ -64,16 +65,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 |
@@ -119,8 +112,8 @@ void APDS9930Component::update() {
const uint8_t status = raw[0];
if (this->illuminance_sensor_ != nullptr && (status & APDS9930_AVALID) != 0) {
const uint16_t ch0 = encode_uint16(raw[2], raw[1]);
const uint16_t ch1 = encode_uint16(raw[4], raw[3]);
const uint16_t ch0 = encode_value_le<uint16_t>(&raw[1]);
const uint16_t ch1 = encode_value_le<uint16_t>(&raw[3]);
if (ch0 >= ALS_SATURATION || ch1 >= ALS_SATURATION) {
// A saturated channel would give a lux value that is too low, not too high
ESP_LOGW(TAG, "Light channel saturated, lower ambient_light_gain");
@@ -133,7 +126,7 @@ void APDS9930Component::update() {
}
if (this->proximity_sensor_ != nullptr && (status & APDS9930_PVALID) != 0) {
this->proximity_sensor_->publish_state(encode_uint16(raw[6], raw[5]));
this->proximity_sensor_->publish_state(encode_value_le<uint16_t>(&raw[5]));
}
}
+1 -4
View File
@@ -3,7 +3,6 @@ from esphome.components import i2c, sensor
from esphome.components.const import UNIT_COUNTS
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_ILLUMINANCE,
DEVICE_CLASS_ILLUMINANCE,
STATE_CLASS_MEASUREMENT,
@@ -59,9 +58,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_led_drive(config[CONF_LED_DRIVE]))
cg.add(var.set_proximity_gain(config[CONF_PROXIMITY_GAIN]))
cg.add(var.set_ambient_gain(config[CONF_AMBIENT_LIGHT_GAIN]))
+1 -4
View File
@@ -1,7 +1,6 @@
import esphome.codegen as cg
from esphome.components import i2c
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -60,9 +59,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_led_drive(config[CONF_LED_DRIVE]))
cg.add(var.set_proximity_gain(config[CONF_PROXIMITY_GAIN]))
cg.add(var.set_ambient_gain(config[CONF_AMBIENT_LIGHT_GAIN]))
+3
View File
@@ -65,6 +65,9 @@ void APIServer::socket_failed_(const LogString *msg) {
}
void APIServer::setup() {
#if defined(USE_API_HOMEASSISTANT_STATES) && defined(HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT)
this->state_subs_.reserve(HOMEASSISTANT_STATE_SUBSCRIPTION_COUNT);
#endif
#ifdef USE_API_NOISE
// Always reserve the slot: flash preferences are positional on esp8266, so
// a yaml key build must keep the layout of a runtime key build
+1 -2
View File
@@ -48,8 +48,7 @@ AS3935_SCHEMA = cv.Schema(
async def setup_as3935(var: MockObj, config: ConfigType) -> None:
await cg.register_component(var, config)
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN])
cg.add(var.set_irq_pin(irq_pin))
await cg.add_pin(var.set_irq_pin, config[CONF_IRQ_PIN])
cg.add(var.set_indoor(config[CONF_INDOOR]))
cg.add(var.set_noise_level(config[CONF_NOISE_LEVEL]))
cg.add(var.set_watchdog_threshold(config[CONF_WATCHDOG_THRESHOLD]))
+2 -7
View File
@@ -9,7 +9,6 @@ from esphome.const import (
CONF_DIR_PIN,
CONF_DIRECTION,
CONF_HYSTERESIS,
CONF_ID,
CONF_POWER_MODE,
CONF_RANGE,
CONF_WATCHDOG,
@@ -209,9 +208,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 i2c.register_i2c_device(var, config)
var = await i2c.new_i2c_component(config)
cg.add(var.set_direction(config[CONF_DIRECTION]))
cg.add(var.set_watchdog(config[CONF_WATCHDOG]))
@@ -221,9 +218,7 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_fast_filter(config[CONF_FAST_FILTER]))
cg.add(var.set_start_position(config[CONF_START_POSITION]))
if dir_pin_config := config.get(CONF_DIR_PIN):
pin = await cg.gpio_pin_expression(dir_pin_config)
cg.add(var.set_dir_pin(pin))
await cg.add_pin(var.set_dir_pin, config.get(CONF_DIR_PIN))
if (end_position_config := config.get(CONF_END_POSITION, None)) is not None:
cg.add(var.set_end_position(end_position_config))
+1 -4
View File
@@ -30,10 +30,7 @@ CONF_END_POSITION = "end_position"
CONF_OUT_OF_RANGE_MODE = "out_of_range_mode"
OutOfRangeMode = as5600_ns.enum("OutRangeMode")
OUT_OF_RANGE_MODES = {
"MIN_MAX": OutOfRangeMode.OUT_RANGE_MODE_MIN_MAX,
"NAN": OutOfRangeMode.OUT_RANGE_MODE_NAN,
}
OUT_OF_RANGE_MODES = OutOfRangeMode.options("MIN_MAX", "NAN", prefix="OUT_RANGE_MODE_")
CONF_AS5600_ID = "as5600_id"
+5 -13
View File
@@ -9,14 +9,8 @@ ESPHOME_LOG_TAG(TAG, "as7341");
void AS7341Component::setup() {
LOG_I2C_DEVICE(this);
// Verify device ID
uint8_t id;
this->read_byte(AS7341_ID, &id);
ESP_LOGCONFIG(TAG, " Read ID: 0x%X", id);
if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) {
this->mark_failed(LOG_STR("Wrong chip ID"));
if (!this->check_chip_id(this, AS7341_ID, AS7341_CHIP_ID << 2, 0xFC))
return;
}
// Power on (enter IDLE state)
if (!this->enable_power(true)) {
@@ -101,16 +95,16 @@ uint8_t AS7341Component::get_atime() {
}
uint16_t AS7341Component::get_astep() {
uint16_t data;
this->read_byte_16(AS7341_ASTEP, &data);
return this->swap_bytes(data);
uint16_t data = 0;
this->read_byte_16_le(AS7341_ASTEP, &data);
return data;
}
bool AS7341Component::setup_gain(AS7341Gain gain) { return this->write_byte(AS7341_CFG1, gain); }
bool AS7341Component::setup_atime(uint8_t atime) { return this->write_byte(AS7341_ATIME, atime); }
bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16(AS7341_ASTEP, swap_bytes(astep)); }
bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16_le(AS7341_ASTEP, astep); }
bool AS7341Component::read_channels(uint16_t *data) {
this->set_smux_low_channels(true);
@@ -259,6 +253,4 @@ bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position)
return this->write_byte(address, data);
}
uint16_t AS7341Component::swap_bytes(uint16_t data) { return (data >> 8) | (data << 8); }
} // namespace esphome::as7341
-1
View File
@@ -118,7 +118,6 @@ class AS7341Component final : public PollingComponent, public i2c::I2CDevice {
bool write_register_bit(uint8_t address, bool value, uint8_t bit_position);
bool set_register_bit(uint8_t address, uint8_t bit_position);
bool clear_register_bit(uint8_t address, uint8_t bit_position);
uint16_t swap_bytes(uint16_t data);
protected:
sensor::Sensor *f1_{nullptr};
+2 -5
View File
@@ -5,7 +5,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_CLEAR,
CONF_GAIN,
CONF_ID,
DEVICE_CLASS_EMPTY,
ICON_BRIGHTNESS_5,
STATE_CLASS_MEASUREMENT,
@@ -74,7 +73,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_CLEAR): SENSOR_SCHEMA,
cv.Optional(CONF_NIR): SENSOR_SCHEMA,
cv.Optional(CONF_GAIN, default="X8"): cv.enum(GAIN_OPTIONS),
cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
cv.Optional(CONF_ATIME, default=29): cv.uint8_t,
cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
}
)
@@ -97,9 +96,7 @@ SENSORS = {
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(var.set_atime(config[CONF_ATIME]))
+5 -11
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_CALIBRATION,
CONF_GAIN,
CONF_GLASS_ATTENUATION_FACTOR,
CONF_ID,
CONF_TYPE,
CONF_UPDATE_INTERVAL,
)
@@ -55,12 +54,9 @@ GAINS_41 = {
GAINS_43 = {**GAINS_41, "X1024": Gain.GAIN_1024X, "X2048": Gain.GAIN_2048X}
Normalization = as734x_ns.enum("Normalization", True)
NORMALIZATIONS = {
"NONE": Normalization.NORMALIZATION_NONE,
"ALL": Normalization.NORMALIZATION_ALL,
"BANDS": Normalization.NORMALIZATION_BANDS,
"CLEAR": Normalization.NORMALIZATION_CLEAR,
}
NORMALIZATIONS = Normalization.options(
"NONE", "ALL", "BANDS", "CLEAR", prefix="NORMALIZATION_"
)
# Per-channel correction for the golden device, measured by the manufacturer.
DEFAULT_CHANNEL_CORRECTION_43 = [
@@ -131,7 +127,7 @@ def _schema(
{
cv.GenerateID(): cv.declare_id(AS734XComponent),
cv.Optional(CONF_GAIN, default="X8"): cv.enum(gains),
cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255),
cv.Optional(CONF_ATIME, default=29): cv.uint8_t,
cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534),
cv.Optional(CONF_CALIBRATION, default={}): _calibration_schema(bands),
cv.Optional(CONF_NORMALIZE_BASIC_COUNTS, default="NONE"): cv.enum(
@@ -169,9 +165,7 @@ def _per_band(
async def to_code(config: ConfigType) -> None:
chip = config[CONF_TYPE]
cg.add_define("USE_AS7341" if chip == "AS7341" else "USE_AS7343")
var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f"&as734x::{chip}_CHIP"))
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
var = await i2c.new_i2c_component(config, cg.RawExpression(f"&as734x::{chip}_CHIP"))
cg.add(var.set_gain(config[CONF_GAIN]))
cg.add(var.set_atime(config[CONF_ATIME]))
cg.add(var.set_astep(config[CONF_ASTEP]))
+2 -2
View File
@@ -277,7 +277,7 @@ bool AS734XComponent::update_bits_(uint8_t reg, uint8_t mask, bool value) {
bool AS734XComponent::write_exposure_() {
const ChipInfo *chip = this->chip_;
if (!this->select_bank_(REG_ATIME) || !this->write_byte(REG_ATIME, this->atime_) ||
!this->write_byte_16(pgm(chip->astep_reg), byteswap(this->astep_)) ||
!this->write_byte_16_le(pgm(chip->astep_reg), this->astep_) ||
!this->write_byte(pgm(chip->cfg1_reg), this->gain_)) {
return false;
}
@@ -459,7 +459,7 @@ void AS734XComponent::read_step_() {
if (frame[0] & ASTATUS_ASAT) {
ESP_LOGV(TAG, "Step %u saturated", this->step_);
}
auto slot_value = [&frame](uint8_t slot) { return encode_uint16(frame[2 + 2 * slot], frame[1 + 2 * slot]); };
auto slot_value = [&frame](uint8_t slot) { return encode_value_le<uint16_t>(&frame[1 + 2 * slot]); };
const uint8_t *readout = pgm(this->chip_->readout) + this->step_ * NUM_BANDS;
const uint8_t extra_clear = pgm(this->chip_->extra_clear_slot);
for (uint8_t band = 0; band < NUM_BANDS; band++) {
+2 -4
View File
@@ -73,9 +73,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 i2c.register_i2c_device(var, config)
await i2c.new_i2c_component(config)
# Actions
@@ -93,7 +91,7 @@ def _megahertz(value: float) -> int:
def _microamps(value: float) -> int:
return int(value * 1000000)
return round(value * 1000000)
RADAR_SETTINGS_SCHEMA = cv.Schema(
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_FORWARD_ACTIVE_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_POWER_FACTOR,
@@ -125,9 +124,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)
var = await spi.new_spi_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -7
View File
@@ -27,13 +27,7 @@ audio_ns = cg.esphome_ns.namespace("audio")
AudioFile = audio_ns.struct("AudioFile")
AudioFileType = audio_ns.enum("AudioFileType", is_class=True)
AUDIO_FILE_TYPE_ENUM = {
"NONE": AudioFileType.NONE,
"WAV": AudioFileType.WAV,
"MP3": AudioFileType.MP3,
"FLAC": AudioFileType.FLAC,
"OPUS": AudioFileType.OPUS,
}
AUDIO_FILE_TYPE_ENUM = AudioFileType.options("NONE", "WAV", "MP3", "FLAC", "OPUS")
MEMORY_PSRAM = "psram"
MEMORY_INTERNAL = "internal"
@@ -31,7 +31,5 @@ async def to_code(config: ConfigType) -> None:
await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN):
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))
await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN))
await cg.add_pin(var.set_reset_pin, config.get(CONF_RESET_PIN))
@@ -12,14 +12,10 @@ DEPENDENCIES = ["bedjet"]
BedJetClimate = bedjet_ns.class_("BedJetClimate", climate.Climate, cg.PollingComponent)
BedjetHeatMode = bedjet_ns.enum("BedjetHeatMode")
BedjetTemperatureSource = bedjet_ns.enum("BedjetTemperatureSource")
BEDJET_HEAT_MODES = {
"heat": BedjetHeatMode.HEAT_MODE_HEAT,
"extended": BedjetHeatMode.HEAT_MODE_EXTENDED,
}
BEDJET_TEMPERATURE_SOURCES = {
"outlet": BedjetTemperatureSource.TEMPERATURE_SOURCE_OUTLET,
"ambient": BedjetTemperatureSource.TEMPERATURE_SOURCE_AMBIENT,
}
BEDJET_HEAT_MODES = BedjetHeatMode.options("heat", "extended", prefix="HEAT_MODE_")
BEDJET_TEMPERATURE_SOURCES = BedjetTemperatureSource.options(
"outlet", "ambient", prefix="TEMPERATURE_SOURCE_"
)
CONFIG_SCHEMA = (
climate.climate_schema(BedJetClimate)
+2 -9
View File
@@ -6,7 +6,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_GAIN,
CONF_GLASS_ATTENUATION_FACTOR,
CONF_ID,
CONF_INTEGRATION_TIME,
)
from esphome.types import ConfigType
@@ -25,11 +24,7 @@ BH1745Component = bh1745_ns.class_(
)
AdcGain = bh1745_ns.enum("AdcGain", is_class=True)
ADC_GAINS = {
"1X": AdcGain.ADC_GAIN_1X,
"2X": AdcGain.ADC_GAIN_2X,
"16X": AdcGain.ADC_GAIN_16X,
}
ADC_GAINS = AdcGain.options("1X", "2X", "16X", prefix="ADC_GAIN_")
MeasurementTime = bh1745_ns.enum("MeasurementTime", is_class=True)
MEASUREMENT_TIMES = {
@@ -66,9 +61,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
cg.add(var.set_adc_gain(config[CONF_GAIN]))
cg.add(var.set_measurement_time(config[CONF_INTEGRATION_TIME]))
+7 -15
View File
@@ -63,15 +63,8 @@ static float calculate_cct(uint16_t red, uint16_t green, uint16_t blue, bool low
}
void BH1745Component::setup() {
uint8_t manufacturer_id;
if (!this->read_byte(REG_MANUFACTURER_ID, &manufacturer_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
if (!this->check_chip_id(this, REG_MANUFACTURER_ID, MANUFACTURER_ID))
return;
}
if (manufacturer_id != MANUFACTURER_ID) {
this->mark_failed(LOG_STR("Wrong manufacturer ID"));
return;
}
uint8_t system_control;
if (!this->read_byte(REG_SYSTEM_CONTROL, &system_control) ||
(system_control & SYSTEM_CONTROL_PART_ID_MASK) != PART_ID) {
@@ -140,18 +133,17 @@ void BH1745Component::update() {
if ((mode_control2 & MODE_CONTROL2_VALID) == 0)
return;
uint8_t buffer[BH1745_CHANNELS * 2];
if (!this->read_bytes(REG_RED_DATA_LSB, buffer, sizeof(buffer))) {
uint16_t buffer[BH1745_CHANNELS];
if (!this->read_bytes_16_le(REG_RED_DATA_LSB, buffer, BH1745_CHANNELS)) {
this->status_set_warning(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
this->status_clear_warning();
// Each channel is sent low byte first.
const uint16_t red = encode_uint16(buffer[1], buffer[0]);
const uint16_t green = encode_uint16(buffer[3], buffer[2]);
const uint16_t blue = encode_uint16(buffer[5], buffer[4]);
const uint16_t clear = encode_uint16(buffer[7], buffer[6]);
const uint16_t red = buffer[0];
const uint16_t green = buffer[1];
const uint16_t blue = buffer[2];
const uint16_t clear = buffer[3];
ESP_LOGV(TAG, "Red: %u, Green: %u, Blue: %u, Clear: %u", red, green, blue, clear);
if (this->red_counts_sensor_ != nullptr)
+5 -6
View File
@@ -1,13 +1,14 @@
#include "bl0906.h"
#include "constants.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::bl0906 {
ESPHOME_LOG_TAG(TAG, "bl0906");
constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
constexpr uint32_t to_uint32_t(ube24_t input) { return encode_uint24(input.h, input.m, input.l); }
constexpr int32_t to_int32_t(sbe24_t input) {
return static_cast<int32_t>(encode_uint32((uint8_t) input.h, input.m, input.l, 0)) >> 8;
@@ -20,7 +21,7 @@ constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data)
void BL0906::loop() {
while (this->available())
this->flush();
this->read();
if (this->current_stage_ == STAGE_IDLE) {
// Woken up between cycles to drain the action queue. Go back to sleep.
@@ -92,7 +93,7 @@ void BL0906::advance_stage_() {
void BL0906::setup() {
while (this->available())
this->flush();
this->read();
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
@@ -133,9 +134,7 @@ void BL0906::handle_actions_() {
}
}
while (this->available()) {
this->read();
}
this->discard_available();
this->action_queue_.clear();
}
+1 -3
View File
@@ -147,9 +147,7 @@ automation.register_apply_action(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
await sensors(CONF_TEMPERATURE, var.set_temperature_sensor)
+4 -8
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_MODE,
CONF_VOLTAGE,
@@ -35,10 +34,9 @@ CONF_ENERGY_TOTAL = "energy_total"
bl0939_ns = cg.esphome_ns.namespace("bl0939")
BL0939 = bl0939_ns.class_("BL0939", cg.PollingComponent, uart.UARTDevice)
BL0939Mode = bl0939_ns.enum("BL0939Mode", is_class=True)
MODE_OPTIONS = {
"direct": BL0939Mode.BL0939_MODE_DIRECT,
"current_transformer": BL0939Mode.BL0939_MODE_CURRENT_TRANSFORMER,
}
MODE_OPTIONS = BL0939Mode.options(
"direct", "current_transformer", prefix="BL0939_MODE_"
)
# Must match the mode_ member initializer in bl0939.h; set_mode() is skipped
# when the config already equals it.
DEFAULT_MODE = "direct"
@@ -113,9 +111,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
if (mode := config[CONF_MODE]) != DEFAULT_MODE:
cg.add(var.set_mode(mode))
+1 -4
View File
@@ -5,7 +5,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_EXTERNAL_TEMPERATURE,
CONF_ID,
CONF_INTERNAL_TEMPERATURE,
CONF_POWER,
CONF_REFERENCE_VOLTAGE,
@@ -227,9 +226,7 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+1 -4
View File
@@ -6,7 +6,6 @@ from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER,
CONF_RESET,
@@ -97,9 +96,7 @@ CONFIG_SCHEMA = (
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
var = await uart.new_uart_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
+10
View File
@@ -1,3 +1,6 @@
from collections.abc import Coroutine
from typing import Any
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
@@ -171,6 +174,13 @@ async def register_ble_node(var, config):
cg.add(parent.register_ble_node(var))
def new_ble_node_component(
config: ConfigType, *args: Any
) -> Coroutine[Any, Any, cg.MockObj]:
"""Construct, register and attach to the BLE client; args go to the constructor."""
return cg.new_bus_component(register_ble_node, config, *args)
BLE_WRITE_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
@@ -24,8 +24,9 @@ AES-CCM decryption for encrypted advertisements is provided portably in
ble_aes_ccm.h.
"""
from collections.abc import Callable
from collections.abc import Callable, Coroutine
import re
from typing import Any
import esphome.codegen as cg
from esphome.components.const import CONF_WINDOW
@@ -182,6 +183,13 @@ async def register_ble_device(var: cg.MockObj, config: ConfigType) -> cg.MockObj
return var
def new_ble_device_component(
config: ConfigType, *args: Any
) -> Coroutine[Any, Any, cg.MockObj]:
"""Construct, register and attach to the BLE hub; args go to the constructor."""
return cg.new_bus_component(register_ble_device, config, *args)
# ---- shared validation / codegen helpers (platform-neutral) ----
+2 -4
View File
@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, time
import esphome.config_validation as cv
from esphome.const import CONF_DURATION, CONF_ID
from esphome.const import CONF_DURATION
from esphome.types import ConfigType
DEPENDENCIES = ["i2c"]
@@ -50,7 +50,5 @@ 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 i2c.register_i2c_device(var, config)
var = await i2c.new_i2c_component(config)
await time.register_time(var, config)
+6 -15
View File
@@ -24,23 +24,14 @@ DOMAIN = "bme280_base"
bme280_ns = cg.esphome_ns.namespace("bme280_base")
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
"1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
"2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
"4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
"8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
"16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
}
OVERSAMPLING_OPTIONS = BME280Oversampling.options(
"NONE", "1X", "2X", "4X", "8X", "16X", prefix="BME280_OVERSAMPLING_"
)
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
"2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
"4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
"8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
"16X": BME280IIRFilter.BME280_IIR_FILTER_16X,
}
IIR_FILTER_OPTIONS = BME280IIRFilter.options(
"OFF", "2X", "4X", "8X", "16X", prefix="BME280_IIR_FILTER_"
)
CONFIG_SCHEMA_BASE = cv.Schema(
{
@@ -3,6 +3,7 @@
#include "bme280_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <esphome/components/sensor/sensor.h>
#include <esphome/core/component.h>
@@ -144,11 +145,10 @@ void BME280Component::setup() {
this->calibration_.h1 = read_u8_(BME280_REGISTER_DIG_H1);
this->calibration_.h2 = read_s16_le_(BME280_REGISTER_DIG_H2);
this->calibration_.h3 = read_u8_(BME280_REGISTER_DIG_H3);
// h4 and h5 are signed 12-bit values; shift left then arithmetic right shift to sign-extend
int16_t h4_raw = read_u8_(BME280_REGISTER_DIG_H4) << 4 | (read_u8_(BME280_REGISTER_DIG_H4 + 1) & 0x0F);
this->calibration_.h4 = static_cast<int16_t>(h4_raw << 4) >> 4;
int16_t h5_raw = read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4);
this->calibration_.h5 = static_cast<int16_t>(h5_raw << 4) >> 4;
this->calibration_.h4 =
sign_extend<12>(read_u8_(BME280_REGISTER_DIG_H4) << 4 | (read_u8_(BME280_REGISTER_DIG_H4 + 1) & 0x0F));
this->calibration_.h5 =
sign_extend<12>(read_u8_(BME280_REGISTER_DIG_H5 + 1) << 4 | (read_u8_(BME280_REGISTER_DIG_H5) >> 4));
this->calibration_.h6 = read_u8_(BME280_REGISTER_DIG_H6);
uint8_t humid_control_val = 0;
@@ -335,8 +335,8 @@ uint8_t BME280Component::read_u8_(uint8_t a_register) {
}
uint16_t BME280Component::read_u16_le_(uint8_t a_register) {
uint16_t data = 0;
this->read_byte_16(a_register, &data);
return (data >> 8) | (data << 8);
this->read_byte_16_le(a_register, &data);
return data;
}
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
+1 -1
View File
@@ -91,7 +91,7 @@ class BME280Component : public PollingComponent {
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
virtual bool read_byte_16_le(uint8_t a_register, uint16_t *data) = 0;
BME280CalibrationData calibration_;
BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X};
+2 -2
View File
@@ -16,8 +16,8 @@ bool BME280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
bool BME280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
return I2CDevice::read_bytes(a_register, data, len);
};
bool BME280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
return I2CDevice::read_byte_16(a_register, data);
bool BME280I2CComponent::read_byte_16_le(uint8_t a_register, uint16_t *data) {
return I2CDevice::read_byte_16_le(a_register, data);
};
void BME280I2CComponent::dump_config() {
+1 -1
View File
@@ -12,7 +12,7 @@ class BME280I2CComponent final : public esphome::bme280_base::BME280Component, p
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
bool read_byte_16_le(uint8_t a_register, uint16_t *data) override;
void dump_config() override;
};
+4 -3
View File
@@ -3,6 +3,7 @@
#include "bme280_spi.h"
#include <esphome/components/bme280_base/bme280_base.h>
#include "esphome/core/helpers.h"
namespace esphome::bme280_spi {
@@ -51,11 +52,11 @@ bool BME280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t le
return true;
}
bool BME280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
bool BME280SPIComponent::read_byte_16_le(uint8_t a_register, uint16_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0);
((uint8_t *) data)[0] = this->transfer_byte(0);
const uint8_t lsb = this->transfer_byte(0);
*data = encode_uint16(this->transfer_byte(0), lsb);
this->disable();
return true;
}
+1 -1
View File
@@ -12,7 +12,7 @@ class BME280SPIComponent final : public esphome::bme280_base::BME280Component,
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
bool read_byte_16_le(uint8_t a_register, uint16_t *data) override;
};
} // namespace esphome::bme280_spi
+7 -22
View File
@@ -7,7 +7,6 @@ from esphome.const import (
CONF_GAS_RESISTANCE,
CONF_HEATER,
CONF_HUMIDITY,
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
@@ -28,26 +27,14 @@ DEPENDENCIES = ["i2c"]
bme680_ns = cg.esphome_ns.namespace("bme680")
BME680Oversampling = bme680_ns.enum("BME680Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": BME680Oversampling.BME680_OVERSAMPLING_NONE,
"1X": BME680Oversampling.BME680_OVERSAMPLING_1X,
"2X": BME680Oversampling.BME680_OVERSAMPLING_2X,
"4X": BME680Oversampling.BME680_OVERSAMPLING_4X,
"8X": BME680Oversampling.BME680_OVERSAMPLING_8X,
"16X": BME680Oversampling.BME680_OVERSAMPLING_16X,
}
OVERSAMPLING_OPTIONS = BME680Oversampling.options(
"NONE", "1X", "2X", "4X", "8X", "16X", prefix="BME680_OVERSAMPLING_"
)
BME680IIRFilter = bme680_ns.enum("BME680IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": BME680IIRFilter.BME680_IIR_FILTER_OFF,
"1X": BME680IIRFilter.BME680_IIR_FILTER_1X,
"3X": BME680IIRFilter.BME680_IIR_FILTER_3X,
"7X": BME680IIRFilter.BME680_IIR_FILTER_7X,
"15X": BME680IIRFilter.BME680_IIR_FILTER_15X,
"31X": BME680IIRFilter.BME680_IIR_FILTER_31X,
"63X": BME680IIRFilter.BME680_IIR_FILTER_63X,
"127X": BME680IIRFilter.BME680_IIR_FILTER_127X,
}
IIR_FILTER_OPTIONS = BME680IIRFilter.options(
"OFF", "1X", "3X", "7X", "15X", "31X", "63X", "127X", prefix="BME680_IIR_FILTER_"
)
BME680Component = bme680_ns.class_(
"BME680Component", cg.PollingComponent, i2c.I2CDevice
@@ -127,9 +114,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
if await sensors(CONF_TEMPERATURE, var.set_temperature_sensor):
+3 -11
View File
@@ -19,10 +19,7 @@ CONF_SUPPLY_VOLTAGE = "supply_voltage"
bme680_bsec_ns = cg.esphome_ns.namespace("bme680_bsec")
IAQMode = bme680_bsec_ns.enum("IAQMode")
IAQ_MODE_OPTIONS = {
"STATIC": IAQMode.IAQ_MODE_STATIC,
"MOBILE": IAQMode.IAQ_MODE_MOBILE,
}
IAQ_MODE_OPTIONS = IAQMode.options("STATIC", "MOBILE", prefix="IAQ_MODE_")
SupplyVoltage = bme680_bsec_ns.enum("SupplyVoltage")
SUPPLY_VOLTAGE_OPTIONS = {
@@ -31,10 +28,7 @@ SUPPLY_VOLTAGE_OPTIONS = {
}
SampleRate = bme680_bsec_ns.enum("SampleRate")
SAMPLE_RATE_OPTIONS = {
"LP": SampleRate.SAMPLE_RATE_LP,
"ULP": SampleRate.SAMPLE_RATE_ULP,
}
SAMPLE_RATE_OPTIONS = SampleRate.options("LP", "ULP", prefix="SAMPLE_RATE_")
BME680BSECComponent = bme680_bsec_ns.class_(
"BME680BSECComponent", cg.Component, i2c.I2CDevice
@@ -79,9 +73,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 i2c.register_i2c_device(var, config)
var = await i2c.new_i2c_component(config)
cg.add(var.set_device_id(str(config[CONF_ID])))
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
+6 -16
View File
@@ -36,28 +36,18 @@ BME68xBSEC2Component = bme68x_bsec2_ns.class_("BME68xBSEC2Component", cg.Compone
MODEL_OPTIONS = ["bme680", "bme688"]
AlgorithmOutput = bme68x_bsec2_ns.enum("AlgorithmOutput")
ALGORITHM_OUTPUT_OPTIONS = {
"classification": AlgorithmOutput.ALGORITHM_OUTPUT_CLASSIFICATION,
"regression": AlgorithmOutput.ALGORITHM_OUTPUT_REGRESSION,
}
ALGORITHM_OUTPUT_OPTIONS = AlgorithmOutput.options(
"classification", "regression", prefix="ALGORITHM_OUTPUT_"
)
OperatingAge = bme68x_bsec2_ns.enum("OperatingAge")
OPERATING_AGE_OPTIONS = {
"4d": OperatingAge.OPERATING_AGE_4D,
"28d": OperatingAge.OPERATING_AGE_28D,
}
OPERATING_AGE_OPTIONS = OperatingAge.options("4d", "28d", prefix="OPERATING_AGE_")
SampleRate = bme68x_bsec2_ns.enum("SampleRate")
SAMPLE_RATE_OPTIONS = {
"LP": SampleRate.SAMPLE_RATE_LP,
"ULP": SampleRate.SAMPLE_RATE_ULP,
}
SAMPLE_RATE_OPTIONS = SampleRate.options("LP", "ULP", prefix="SAMPLE_RATE_")
Voltage = bme68x_bsec2_ns.enum("Voltage")
VOLTAGE_OPTIONS = {
"1.8V": Voltage.VOLTAGE_1_8V,
"3.3V": Voltage.VOLTAGE_3_3V,
}
VOLTAGE_OPTIONS = Voltage.options("1.8V", "3.3V", prefix="VOLTAGE_")
ALGORITHM_OUTPUT_FILE_NAME = {
"classification": "sel",
+2 -15
View File
@@ -196,19 +196,6 @@ void BMI160Component::dump_config() {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
}
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
uint8_t raw_data[len * 2];
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
if (err != i2c::ERROR_OK) {
return err;
}
for (int i = 0; i < len; i++) {
value[i] = (int16_t) ((uint16_t) raw_data[i * 2] | ((uint16_t) raw_data[i * 2 + 1] << 8));
}
return err;
}
void BMI160Component::update() {
if (!this->setup_complete_) {
return;
@@ -216,7 +203,7 @@ void BMI160Component::update() {
ESP_LOGV(TAG, " Updating BMI160");
int16_t data[6];
if (this->read_le_int16_(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6) != i2c::ERROR_OK) {
if (!this->read_bytes_16_le(BMI160_REGISTER_DATA_GYRO_X_LSB, data, 6)) {
this->status_set_warning();
return;
}
@@ -229,7 +216,7 @@ void BMI160Component::update() {
float accel_z = (float) data[5] / (float) INT16_MAX * 16 * GRAVITY_EARTH;
int16_t raw_temperature;
if (this->read_le_int16_(BMI160_REGISTER_DATA_TEMP_LSB, &raw_temperature, 1) != i2c::ERROR_OK) {
if (!this->read_bytes_16_le(BMI160_REGISTER_DATA_TEMP_LSB, &raw_temperature, 1)) {
this->status_set_warning();
return;
}
-3
View File
@@ -34,9 +34,6 @@ class BMI160Component final : public PollingComponent, public i2c::I2CDevice {
/// Run one setup stage; false on an I2C failure.
bool setup_stage_(int stage);
bool setup_complete_{false};
/** reads `len` 16-bit little-endian integers from the given i2c register */
i2c::ErrorCode read_le_int16_(uint8_t reg, int16_t *value, uint8_t len);
};
} // namespace esphome::bmi160
+1 -4
View File
@@ -8,7 +8,6 @@ from esphome.const import (
CONF_GYROSCOPE_X,
CONF_GYROSCOPE_Y,
CONF_GYROSCOPE_Z,
CONF_ID,
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
ICON_ACCELERATION_X,
@@ -84,9 +83,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_ACCELERATION_X, var.set_accel_x_sensor)
+1 -10
View File
@@ -78,17 +78,8 @@ bool BMI270Component::load_config_file_() {
void BMI270Component::setup() {
MotionComponent::setup();
// 1. Verify chip ID
uint8_t chip_id = 0;
if (!this->read_byte(BMI270_REG_CHIP_ID, &chip_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
if (!this->check_chip_id(this, BMI270_REG_CHIP_ID, BMI270_CHIP_ID_VALUE))
return;
}
if (chip_id != BMI270_CHIP_ID_VALUE) {
ESP_LOGE(TAG, "Wrong chip ID: 0x%02X (expected 0x%02X)", chip_id, BMI270_CHIP_ID_VALUE);
this->mark_failed(LOG_STR("Wrong chip ID"));
return;
}
ESP_LOGD(TAG, "Chip ID: 0x%02X", chip_id);
// 2. Soft-reset via CMD register (0x7E = 0xB6)
if (!this->write_byte(BMI270_REG_CMD, BMI270_CMD_SOFT_RESET)) {
-1
View File
@@ -136,7 +136,6 @@ class BMI270Component final : public motion::MotionComponent, public i2c::I2CDev
// Lifecycle
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
// Configuration setters
void set_accel_range(BMI270AccelRange r) { this->accel_range_ = r; }
+1 -1
View File
@@ -61,7 +61,7 @@ struct BMM150Trim {
// A little-endian 16-bit trim word, addressed by its register
inline uint16_t bmm150_trim_word(const uint8_t *regs, uint8_t reg) {
const uint8_t offset = reg - BMM150_REG_TRIM_START;
return encode_uint16(regs[offset + 1], regs[offset]);
return encode_value_le<uint16_t>(&regs[offset]);
}
// Decodes the trim registers; `regs` holds the bytes of registers 0x5D onwards (at least 21).
+3 -6
View File
@@ -20,12 +20,9 @@ CONF_AUX_DEVICE = "aux_device"
# Enum proxies (must match the C++ enum values exactly)
BMI270AuxDevice = bmi270_ns.enum("BMI270AuxDevice")
BMI270AccelRange = bmi270_ns.enum("BMI270AccelRange")
ACCEL_RANGE_OPTIONS = {
"2G": BMI270AccelRange.BMI270_ACCEL_RANGE_2G,
"4G": BMI270AccelRange.BMI270_ACCEL_RANGE_4G,
"8G": BMI270AccelRange.BMI270_ACCEL_RANGE_8G,
"16G": BMI270AccelRange.BMI270_ACCEL_RANGE_16G,
}
ACCEL_RANGE_OPTIONS = BMI270AccelRange.options(
"2G", "4G", "8G", "16G", prefix="BMI270_ACCEL_RANGE_"
)
BMI270GyroRange = bmi270_ns.enum("BMI270GyroRange")
GYRO_RANGE_OPTIONS = {
+2 -1
View File
@@ -1,4 +1,5 @@
#include "bmp085.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::bmp085 {
@@ -93,7 +94,7 @@ void BMP085Component::read_pressure_() {
return;
}
uint32_t value = (uint32_t(buffer[0]) << 16) | (uint32_t(buffer[1]) << 8) | uint32_t(buffer[2]);
uint32_t value = encode_uint24(buffer[0], buffer[1], buffer[2]);
if ((value >> 5) == 0) {
ESP_LOGW(TAG, "Invalid pressure!");
this->status_set_warning();
+1 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_PRESSURE,
@@ -44,9 +43,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_TEMPERATURE, var.set_temperature)
+6 -15
View File
@@ -21,23 +21,14 @@ DOMAIN = "bmp280_base"
bmp280_ns = cg.esphome_ns.namespace("bmp280_base")
BMP280Oversampling = bmp280_ns.enum("BMP280Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": BMP280Oversampling.BMP280_OVERSAMPLING_NONE,
"1X": BMP280Oversampling.BMP280_OVERSAMPLING_1X,
"2X": BMP280Oversampling.BMP280_OVERSAMPLING_2X,
"4X": BMP280Oversampling.BMP280_OVERSAMPLING_4X,
"8X": BMP280Oversampling.BMP280_OVERSAMPLING_8X,
"16X": BMP280Oversampling.BMP280_OVERSAMPLING_16X,
}
OVERSAMPLING_OPTIONS = BMP280Oversampling.options(
"NONE", "1X", "2X", "4X", "8X", "16X", prefix="BMP280_OVERSAMPLING_"
)
BMP280IIRFilter = bmp280_ns.enum("BMP280IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": BMP280IIRFilter.BMP280_IIR_FILTER_OFF,
"2X": BMP280IIRFilter.BMP280_IIR_FILTER_2X,
"4X": BMP280IIRFilter.BMP280_IIR_FILTER_4X,
"8X": BMP280IIRFilter.BMP280_IIR_FILTER_8X,
"16X": BMP280IIRFilter.BMP280_IIR_FILTER_16X,
}
IIR_FILTER_OPTIONS = BMP280IIRFilter.options(
"OFF", "2X", "4X", "8X", "16X", prefix="BMP280_IIR_FILTER_"
)
CONFIG_SCHEMA_BASE = cv.Schema(
{
@@ -247,8 +247,8 @@ uint8_t BMP280Component::read_u8_(uint8_t a_register) {
}
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
uint16_t data = 0;
this->bmp_read_byte_16(a_register, &data);
return (data >> 8) | (data << 8);
this->bmp_read_byte_16_le(a_register, &data);
return data;
}
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
+1 -1
View File
@@ -69,7 +69,7 @@ class BMP280Component : public PollingComponent {
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
virtual bool bmp_read_byte_16_le(uint8_t a_register, uint16_t *data) = 0;
/// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine);
+1 -1
View File
@@ -16,7 +16,7 @@ class BMP280I2CComponent final : public esphome::bmp280_base::BMP280Component, p
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
return read_bytes(a_register, data, len);
}
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
bool bmp_read_byte_16_le(uint8_t a_register, uint16_t *data) override { return read_byte_16_le(a_register, data); }
void dump_config() override;
};
+4 -3
View File
@@ -3,6 +3,7 @@
#include "bmp280_spi.h"
#include <esphome/components/bmp280_base/bmp280_base.h>
#include "esphome/core/helpers.h"
namespace esphome::bmp280_spi {
@@ -51,11 +52,11 @@ bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_
return true;
}
bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
bool BMP280SPIComponent::bmp_read_byte_16_le(uint8_t a_register, uint16_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0);
((uint8_t *) data)[0] = this->transfer_byte(0);
const uint8_t lsb = this->transfer_byte(0);
*data = encode_uint16(this->transfer_byte(0), lsb);
this->disable();
return true;
}
+1 -1
View File
@@ -12,7 +12,7 @@ class BMP280SPIComponent final : public esphome::bmp280_base::BMP280Component,
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
bool bmp_read_byte_16_le(uint8_t a_register, uint16_t *data) override;
};
} // namespace esphome::bmp280_spi
@@ -7,6 +7,7 @@
#include "bmp3xx_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cinttypes>
@@ -222,7 +223,7 @@ bool BMP3XXComponent::get_temperature(float &temperature) {
return false;
}
// Copy the temperature data into the adc variables
int32_t adc_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
int32_t adc_temp = encode_uint24(data[2], data[1], data[0]);
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
return true;
@@ -249,8 +250,8 @@ bool BMP3XXComponent::get_measurements(float &temperature, float &pressure) {
return false;
}
// Copy the temperature and pressure data into the adc variables
int32_t adc_pres = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
int32_t adc_temp = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
int32_t adc_pres = encode_uint24(data[2], data[1], data[0]);
int32_t adc_temp = encode_uint24(data[5], data[4], data[3]);
// Temperature compensation (function from BMP388 datasheet)
temperature = bmp388_compensate_temperature_((float) adc_temp);
@@ -12,6 +12,7 @@
#include "bmp581_base.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
@@ -410,7 +411,7 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
// pressure MSB is in data[5], LSB is in data[4], XLSB in data[3]
int32_t raw_press = (int32_t) data[5] << 16 | (int32_t) data[4] << 8 | (int32_t) data[3];
int32_t raw_press = encode_uint24(data[5], data[4], data[3]);
pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
return true;
+2 -4
View File
@@ -34,9 +34,7 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
cg.add(var.set_data_pin(data))
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
cg.add(var.set_clock_pin(clock))
await cg.add_pin(var.set_data_pin, config[CONF_DATA_PIN])
await cg.add_pin(var.set_clock_pin, config[CONF_CLOCK_PIN])
cg.add(var.set_max_power_color_channels(config[CONF_MAX_POWER_COLOR_CHANNELS]))
cg.add(var.set_max_power_white_channels(config[CONF_MAX_POWER_WHITE_CHANNELS]))
+2 -4
View File
@@ -25,7 +25,5 @@ async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
cg.add(var.set_data_pin(data))
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
cg.add(var.set_clock_pin(clock))
await cg.add_pin(var.set_data_pin, config[CONF_DATA_PIN])
await cg.add_pin(var.set_clock_pin, config[CONF_CLOCK_PIN])
+1 -3
View File
@@ -338,9 +338,7 @@ CONFIGURE_ACTION_SCHEMA = automation.maybe_simple_id(
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)
var = await i2c.new_i2c_component(config)
if config[CONF_CONFIGURE_ON_BOOT]:
cg.add(var.set_setup_registers(cg.ArrayInitializer(*pack_registers(config))))
+1 -11
View File
@@ -79,19 +79,9 @@ bool BQ25186Component::apply_config(const BQ25186ConfigRegisters &regs) {
}
void BQ25186Component::setup() {
uint8_t mask_id;
if (!this->read_byte(BQ25186_REG_MASK_ID, &mask_id)) {
this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
return;
}
// Another charger at this address could lay its registers out differently, so do not write into it
const uint8_t device_id = mask_id & 0x0F;
if (device_id != 0x01) {
ESP_LOGE(TAG, "Unexpected device id 0x%X (expected 0x1)", device_id);
this->mark_failed(LOG_STR("Not a BQ25186"));
if (!this->check_chip_id(this, BQ25186_REG_MASK_ID, 0x01, 0x0F))
return;
}
// A failed write only sets the warning status: the chip has identified itself, so a later
// apply_configuration may retry
+1 -10
View File
@@ -25,15 +25,6 @@ static constexpr uint8_t REG_MAC_DATA = 0x40;
static constexpr uint16_t DEVICE_NUMBER = 0x0220;
static constexpr uint16_t TIME_TO_EMPTY_NOT_APPLICABLE = 0xFFFF;
bool BQ27220Component::read_word_(uint8_t reg, uint16_t &value) {
uint8_t data[2];
if (this->read_register(reg, data, 2) != i2c::ERROR_OK) {
return false;
}
value = encode_uint16(data[1], data[0]);
return true;
}
void BQ27220Component::setup() {
const uint8_t device_number_cmd[2] = {0x01, 0x00}; // DEVICE_NUMBER subcommand, little-endian
if (this->write_register(REG_CONTROL, device_number_cmd, 2) != i2c::ERROR_OK) {
@@ -42,7 +33,7 @@ void BQ27220Component::setup() {
}
delay(2); // SLUUBD4A §5.3: the subcommand result needs a moment before it can be read
uint16_t device_number = 0;
if (!this->read_word_(REG_MAC_DATA, device_number)) {
if (!this->read_byte_16_le(REG_MAC_DATA, &device_number)) {
this->mark_failed(LOG_STR("Failed to read device number from BQ27220"));
return;
}
+1 -2
View File
@@ -25,14 +25,13 @@ class BQ27220Component final : public PollingComponent, public i2c::I2CDevice {
void dump_config() override;
protected:
bool read_word_(uint8_t reg, uint16_t &value);
/// Read a standard command register and publish it through the sensor, if one is configured.
/// A failed read publishes NAN and clears `ok`.
template<typename F> void publish_(sensor::Sensor *sensor, uint8_t reg, bool &ok, F convert) {
if (sensor == nullptr)
return;
uint16_t raw;
if (!this->read_word_(reg, raw)) {
if (!this->read_byte_16_le(reg, &raw)) {
ok = false;
sensor->publish_state(NAN);
return;
+1 -4
View File
@@ -5,7 +5,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_BATTERY_LEVEL,
CONF_CURRENT,
CONF_ID,
CONF_TEMPERATURE,
CONF_VOLTAGE,
DEVICE_CLASS_BATTERY,
@@ -108,9 +107,7 @@ CONFIG_SCHEMA = (
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)
var = await i2c.new_i2c_component(config)
sensors = sensor.sub_sensors(config)
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)

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