Merge branch 'dev' into bump-2023.8.0b1

This commit is contained in:
Jesse Hills 2023-08-10 17:30:26 +12:00
commit 0f506ea8eb
No known key found for this signature in database
GPG key ID: BEAAE804EFD8E83A
193 changed files with 4451 additions and 1515 deletions

View file

@ -5,9 +5,12 @@ Checks: >-
-altera-*, -altera-*,
-android-*, -android-*,
-boost-*, -boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-narrowing-conversions, -bugprone-narrowing-conversions,
-bugprone-signed-char-misuse, -bugprone-signed-char-misuse,
-cert-dcl50-cpp, -cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp, -cert-err58-cpp,
-cert-oop57-cpp, -cert-oop57-cpp,
-cert-str34-c, -cert-str34-c,
@ -15,6 +18,7 @@ Checks: >-
-clang-analyzer-osx.*, -clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor, -clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor, -clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-shadow-field, -clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable, -clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter, -clang-diagnostic-unused-parameter,
@ -25,6 +29,7 @@ Checks: >-
-cppcoreguidelines-macro-usage, -cppcoreguidelines-macro-usage,
-cppcoreguidelines-narrowing-conversions, -cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-bounds-pointer-arithmetic,
@ -36,6 +41,7 @@ Checks: >-
-cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions, -cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance, -fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator, -fuchsia-overloaded-operator,
-fuchsia-statically-constructed-objects, -fuchsia-statically-constructed-objects,
@ -68,6 +74,7 @@ Checks: >-
-modernize-use-nodiscard, -modernize-use-nodiscard,
-mpi-*, -mpi-*,
-objc-*, -objc-*,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static, -readability-convert-member-functions-to-static,
-readability-else-after-return, -readability-else-after-return,
-readability-function-cognitive-complexity, -readability-function-cognitive-complexity,
@ -82,8 +89,6 @@ WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false AnalyzeTemporaryDtors: false
FormatStyle: google FormatStyle: google
CheckOptions: CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold - key: google-readability-function-size.StatementThreshold
value: '800' value: '800'
- key: google-runtime-int.TypeSuffix - key: google-runtime-int.TypeSuffix
@ -158,3 +163,9 @@ CheckOptions:
value: '' value: ''
- key: readability-qualified-auto.AddConstToQualified - key: readability-qualified-auto.AddConstToQualified
value: 0 value: 0
- key: readability-identifier-length.MinimumVariableNameLength
value: 0
- key: readability-identifier-length.MinimumParameterNameLength
value: 0
- key: readability-identifier-length.MinimumLoopCounterNameLength
value: 0

View file

@ -305,7 +305,7 @@ jobs:
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }} key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
- name: Install clang-tidy - name: Install clang-tidy
run: sudo apt-get install clang-tidy-11 run: sudo apt-get install clang-tidy-14
- name: Register problem matchers - name: Register problem matchers
run: | run: |

2
.gitignore vendored
View file

@ -130,3 +130,5 @@ sdkconfig.*
!sdkconfig.defaults !sdkconfig.defaults
.tests/ .tests/
/components

View file

@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.3.0 rev: 23.7.0
hooks: hooks:
- id: black - id: black
args: args:
@ -27,7 +27,7 @@ repos:
- --branch=release - --branch=release
- --branch=beta - --branch=beta
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.7.0 rev: v3.10.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]

View file

@ -11,6 +11,7 @@ esphome/*.py @esphome/core
esphome/core/* @esphome/core esphome/core/* @esphome/core
# Integrations # Integrations
esphome/components/a01nyub/* @MrSuicideParrot
esphome/components/absolute_humidity/* @DAVe3283 esphome/components/absolute_humidity/* @DAVe3283
esphome/components/ac_dimmer/* @glmnet esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core esphome/components/adc/* @esphome/core
@ -48,6 +49,7 @@ esphome/components/ble_client/* @buxtronix
esphome/components/bluetooth_proxy/* @jesserockz esphome/components/bluetooth_proxy/* @jesserockz
esphome/components/bme680_bsec/* @trvrnrth esphome/components/bme680_bsec/* @trvrnrth
esphome/components/bmp3xx/* @martgras esphome/components/bmp3xx/* @martgras
esphome/components/bmp581/* @kahrendt
esphome/components/bp1658cj/* @Cossid esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
@ -100,6 +102,7 @@ esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi esphome/components/feedback/* @ianchi
esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/fingerprint_grow/* @OnFreund @loongyh
esphome/components/fs3000/* @kahrendt esphome/components/fs3000/* @kahrendt
esphome/components/gcja5/* @gcormier
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
esphome/components/gpio/* @esphome/core esphome/components/gpio/* @esphome/core

View file

@ -22,16 +22,22 @@ RUN \
python3=3.9.2-3 \ python3=3.9.2-3 \
python3-pip=20.3.4-4+deb11u1 \ python3-pip=20.3.4-4+deb11u1 \
python3-setuptools=52.0.0-4 \ python3-setuptools=52.0.0-4 \
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
python3-cryptography=3.3.2-1 \ python3-cryptography=3.3.2-1 \
python3-venv=3.9.2-3 \ python3-venv=3.9.2-3 \
iputils-ping=3:20210202-1 \ iputils-ping=3:20210202-1 \
git=1:2.30.2-1+deb11u2 \ git=1:2.30.2-1+deb11u2 \
curl=7.74.0-1.3+deb11u7 \ curl=7.74.0-1.3+deb11u7 \
openssh-client=1:8.4p1-5+deb11u1 \ openssh-client=1:8.4p1-5+deb11u1 \
libcairo2=1.16.0-5 \ python3-cffi=1.14.5-1; \
python3-cffi=1.14.5-1 \ if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
&& rm -rf \ apt-get install -y --no-install-recommends \
build-essential=12.9 \
python3-dev=3.9.2-3 \
zlib1g-dev=1:1.2.11.dfsg-2+deb11u2 \
libjpeg-dev=1:2.0.6-4 \
libcairo2=1.16.0-5; \
fi; \
rm -rf \
/tmp/* \ /tmp/* \
/var/{cache,log}/* \ /var/{cache,log}/* \
/var/lib/apt/lists/* /var/lib/apt/lists/*

View file

@ -365,10 +365,16 @@ def command_wizard(args):
def command_config(args, config): def command_config(args, config):
_LOGGER.info("Configuration is valid!")
if not CORE.verbose: if not CORE.verbose:
config = strip_default_ids(config) config = strip_default_ids(config)
safe_print(yaml_util.dump(config, args.show_secrets)) output = yaml_util.dump(config, args.show_secrets)
# add the console decoration so the front-end can hide the secrets
if not args.show_secrets:
output = re.sub(
r"(password|key|psk|ssid)\:\s(.*)", r"\1: \\033[5m\2\\033[6m", output
)
safe_print(output)
_LOGGER.info("Configuration is valid!")
return 0 return 0

View file

@ -0,0 +1 @@
CODEOWNERS = ["@MrSuicideParrot"]

View file

@ -0,0 +1,57 @@
// Datasheet https://wiki.dfrobot.com/A01NYUB%20Waterproof%20Ultrasonic%20Sensor%20SKU:%20SEN0313
#include "a01nyub.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace a01nyub {
static const char *const TAG = "a01nyub.sensor";
static const uint8_t MAX_DATA_LENGTH_BYTES = 4;
void A01nyubComponent::loop() {
uint8_t data;
while (this->available() > 0) {
if (this->read_byte(&data)) {
buffer_.push_back(data);
this->check_buffer_();
}
}
}
void A01nyubComponent::check_buffer_() {
if (this->buffer_.size() >= MAX_DATA_LENGTH_BYTES) {
size_t i;
for (i = 0; i < this->buffer_.size(); i++) {
// Look for the first packet
if (this->buffer_[i] == 0xFF) {
if (i + 1 + 3 < this->buffer_.size()) { // Packet is not complete
return; // Wait for completion
}
uint8_t checksum = (this->buffer_[i] + this->buffer_[i + 1] + this->buffer_[i + 2]) & 0xFF;
if (this->buffer_[i + 3] == checksum) {
float distance = (this->buffer_[i + 1] << 8) + this->buffer_[i + 2];
if (distance > 280) {
float meters = distance / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %f mm, %f m", distance, meters);
this->publish_state(meters);
} else {
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
}
}
break;
}
}
this->buffer_.clear();
}
}
void A01nyubComponent::dump_config() {
ESP_LOGCONFIG(TAG, "A01nyub Sensor:");
LOG_SENSOR(" ", "Distance", this);
}
} // namespace a01nyub
} // namespace esphome

View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace a01nyub {
class A01nyubComponent : public sensor::Sensor, public Component, public uart::UARTDevice {
public:
// Nothing really public.
// ========== INTERNAL METHODS ==========
void loop() override;
void dump_config() override;
protected:
void check_buffer_();
std::vector<uint8_t> buffer_;
};
} // namespace a01nyub
} // namespace esphome

View file

@ -0,0 +1,41 @@
import esphome.codegen as cg
from esphome.components import sensor, uart
from esphome.const import (
STATE_CLASS_MEASUREMENT,
UNIT_METER,
ICON_ARROW_EXPAND_VERTICAL,
DEVICE_CLASS_DISTANCE,
)
CODEOWNERS = ["@MrSuicideParrot"]
DEPENDENCIES = ["uart"]
a01nyub_ns = cg.esphome_ns.namespace("a01nyub")
A01nyubComponent = a01nyub_ns.class_(
"A01nyubComponent", sensor.Sensor, cg.Component, uart.UARTDevice
)
CONFIG_SCHEMA = sensor.sensor_schema(
A01nyubComponent,
unit_of_measurement=UNIT_METER,
icon=ICON_ARROW_EXPAND_VERTICAL,
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
device_class=DEVICE_CLASS_DISTANCE,
).extend(uart.UART_DEVICE_SCHEMA)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"a01nyub",
baud_rate=9600,
require_tx=False,
require_rx=True,
data_bits=8,
parity=None,
stop_bits=1,
)
async def to_code(config):
var = await sensor.new_sensor(config)
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

View file

@ -28,6 +28,6 @@ async def to_code(config):
dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN]) dir_pin = await cg.gpio_pin_expression(config[CONF_DIR_PIN])
cg.add(var.set_dir_pin(dir_pin)) cg.add(var.set_dir_pin(dir_pin))
if CONF_SLEEP_PIN in config: if sleep_pin_config := config.get(CONF_SLEEP_PIN):
sleep_pin = await cg.gpio_pin_expression(config[CONF_SLEEP_PIN]) sleep_pin = await cg.gpio_pin_expression(sleep_pin_config)
cg.add(var.set_sleep_pin(sleep_pin)) cg.add(var.set_sleep_pin(sleep_pin))

View file

@ -89,14 +89,13 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
if CONF_RAW in config: cg.add(var.set_output_raw(config[CONF_RAW]))
cg.add(var.set_output_raw(config[CONF_RAW]))
if CONF_ATTENUATION in config: if attenuation := config.get(CONF_ATTENUATION):
if config[CONF_ATTENUATION] == "auto": if attenuation == "auto":
cg.add(var.set_autorange(cg.global_ns.true)) cg.add(var.set_autorange(cg.global_ns.true))
else: else:
cg.add(var.set_attenuation(config[CONF_ATTENUATION])) cg.add(var.set_attenuation(attenuation))
if CORE.is_esp32: if CORE.is_esp32:
variant = get_esp32_variant() variant = get_esp32_variant()

View file

@ -48,16 +48,16 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await display.register_display(var, config) await display.register_display(var, config)
if CONF_PIXEL_MAPPER in config: if pixel_mapper := config.get(CONF_PIXEL_MAPPER):
pixel_mapper_template_ = await cg.process_lambda( pixel_mapper_template_ = await cg.process_lambda(
config[CONF_PIXEL_MAPPER], pixel_mapper,
[(int, "x"), (int, "y")], [(int, "x"), (int, "y")],
return_type=cg.int_, return_type=cg.int_,
) )
cg.add(var.set_pixel_mapper(pixel_mapper_template_)) cg.add(var.set_pixel_mapper(pixel_mapper_template_))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void lambda_config, [(display.DisplayRef, "it")], return_type=cg.void
) )
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))

View file

@ -72,8 +72,8 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_IRQ_PIN in config: if irq_pin_config := config.get(CONF_IRQ_PIN):
irq_pin = await cg.gpio_pin_expression(config[CONF_IRQ_PIN]) irq_pin = await cg.gpio_pin_expression(irq_pin_config)
cg.add(var.set_irq_pin(irq_pin)) cg.add(var.set_irq_pin(irq_pin))
for key in [ for key in [

View file

@ -45,10 +45,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View file

@ -1,5 +1,6 @@
#include "airthings_listener.h" #include "airthings_listener.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
#ifdef USE_ESP32 #ifdef USE_ESP32
@ -19,7 +20,7 @@ bool AirthingsListener::parse_device(const esp32_ble_tracker::ESPBTDevice &devic
sn |= ((uint32_t) it.data[2] << 16); sn |= ((uint32_t) it.data[2] << 16);
sn |= ((uint32_t) it.data[3] << 24); sn |= ((uint32_t) it.data[3] << 24);
ESP_LOGD(TAG, "Found AirThings device Serial:%u (MAC: %s)", sn, device.address_str().c_str()); ESP_LOGD(TAG, "Found AirThings device Serial:%" PRIu32 " (MAC: %s)", sn, device.address_str().c_str());
return true; return true;
} }
} }

View file

@ -16,6 +16,12 @@ IS_PLATFORM_COMPONENT = True
CONF_ON_TRIGGERED = "on_triggered" CONF_ON_TRIGGERED = "on_triggered"
CONF_ON_CLEARED = "on_cleared" CONF_ON_CLEARED = "on_cleared"
CONF_ON_ARMING = "on_arming"
CONF_ON_PENDING = "on_pending"
CONF_ON_ARMED_HOME = "on_armed_home"
CONF_ON_ARMED_NIGHT = "on_armed_night"
CONF_ON_ARMED_AWAY = "on_armed_away"
CONF_ON_DISARMED = "on_disarmed"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel") alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase) AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
@ -29,8 +35,27 @@ TriggeredTrigger = alarm_control_panel_ns.class_(
ClearedTrigger = alarm_control_panel_ns.class_( ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template() "ClearedTrigger", automation.Trigger.template()
) )
ArmingTrigger = alarm_control_panel_ns.class_(
"ArmingTrigger", automation.Trigger.template()
)
PendingTrigger = alarm_control_panel_ns.class_(
"PendingTrigger", automation.Trigger.template()
)
ArmedHomeTrigger = alarm_control_panel_ns.class_(
"ArmedHomeTrigger", automation.Trigger.template()
)
ArmedNightTrigger = alarm_control_panel_ns.class_(
"ArmedNightTrigger", automation.Trigger.template()
)
ArmedAwayTrigger = alarm_control_panel_ns.class_(
"ArmedAwayTrigger", automation.Trigger.template()
)
DisarmedTrigger = alarm_control_panel_ns.class_(
"DisarmedTrigger", automation.Trigger.template()
)
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action) ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action) ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
ArmNightAction = alarm_control_panel_ns.class_("ArmNightAction", automation.Action)
DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action) DisarmAction = alarm_control_panel_ns.class_("DisarmAction", automation.Action)
PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action) PendingAction = alarm_control_panel_ns.class_("PendingAction", automation.Action)
TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action) TriggeredAction = alarm_control_panel_ns.class_("TriggeredAction", automation.Action)
@ -51,6 +76,36 @@ ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
} }
), ),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation( cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
@ -81,6 +136,24 @@ async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_TRIGGERED, []): for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PENDING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_HOME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_AWAY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DISARMED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLEARED, []): for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
@ -99,8 +172,8 @@ async def register_alarm_control_panel(var, config):
async def alarm_action_arm_away_to_code(config, action_id, template_arg, args): async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var
@ -109,6 +182,18 @@ async def alarm_action_arm_away_to_code(config, action_id, template_arg, args):
"alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA "alarm_control_panel.arm_home", ArmHomeAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
) )
async def alarm_action_arm_home_to_code(config, action_id, template_arg, args): async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_))
return var
@automation.register_action(
"alarm_control_panel.arm_night", ArmNightAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)
async def alarm_action_arm_night_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if CONF_CODE in config:
@ -123,8 +208,8 @@ async def alarm_action_arm_home_to_code(config, action_id, template_arg, args):
async def alarm_action_disarm_to_code(config, action_id, template_arg, args): async def alarm_action_disarm_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_CODE in config: if code_config := config.get(CONF_CODE):
templatable_ = await cg.templatable(config[CONF_CODE], args, cg.std_string) templatable_ = await cg.templatable(code_config, args, cg.std_string)
cg.add(var.set_code(templatable_)) cg.add(var.set_code(templatable_))
return var return var

View file

@ -36,7 +36,20 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
this->state_callback_.call(); this->state_callback_.call();
if (state == ACP_STATE_TRIGGERED) { if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call(); this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
} }
if (prev_state == ACP_STATE_TRIGGERED) { if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call(); this->cleared_callback_.call();
} }
@ -55,6 +68,30 @@ void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callba
this->triggered_callback_.add(std::move(callback)); this->triggered_callback_.add(std::move(callback));
} }
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) { void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback)); this->cleared_callback_.add(std::move(callback));
} }

View file

@ -47,6 +47,42 @@ class AlarmControlPanel : public EntityBase {
*/ */
void add_on_triggered_callback(std::function<void()> &&callback); void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered /** Add a callback for when the state of the alarm_control_panel clears from triggered
* *
* @param callback The callback function * @param callback The callback function
@ -128,6 +164,18 @@ class AlarmControlPanel : public EntityBase {
CallbackManager<void()> state_callback_{}; CallbackManager<void()> state_callback_{};
// trigger callback // trigger callback
CallbackManager<void()> triggered_callback_{}; CallbackManager<void()> triggered_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback // clear callback
CallbackManager<void()> cleared_callback_{}; CallbackManager<void()> cleared_callback_{};
}; };

View file

@ -85,6 +85,11 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset(); this->state_.reset();
return; return;
} }
if (state == ACP_STATE_ARMED_NIGHT && (this->parent_->get_supported_features() & ACP_FEAT_ARM_NIGHT) == 0) {
ESP_LOGW(TAG, "Cannot arm night when not supported");
this->state_.reset();
return;
}
} }
} }

View file

@ -12,7 +12,7 @@ const LogString *alarm_control_panel_state_to_string(AlarmControlPanelState stat
case ACP_STATE_ARMED_AWAY: case ACP_STATE_ARMED_AWAY:
return LOG_STR("ARMED_AWAY"); return LOG_STR("ARMED_AWAY");
case ACP_STATE_ARMED_NIGHT: case ACP_STATE_ARMED_NIGHT:
return LOG_STR("NIGHT"); return LOG_STR("ARMED_NIGHT");
case ACP_STATE_ARMED_VACATION: case ACP_STATE_ARMED_VACATION:
return LOG_STR("ARMED_VACATION"); return LOG_STR("ARMED_VACATION");
case ACP_STATE_ARMED_CUSTOM_BYPASS: case ACP_STATE_ARMED_CUSTOM_BYPASS:

View file

@ -20,6 +20,48 @@ class TriggeredTrigger : public Trigger<> {
} }
}; };
class ArmingTrigger : public Trigger<> {
public:
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); });
}
};
class PendingTrigger : public Trigger<> {
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
}
};
class ArmedHomeTrigger : public Trigger<> {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
}
};
class ArmedNightTrigger : public Trigger<> {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
}
};
class ArmedAwayTrigger : public Trigger<> {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
}
};
class DisarmedTrigger : public Trigger<> {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
}
};
class ClearedTrigger : public Trigger<> { class ClearedTrigger : public Trigger<> {
public: public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) { explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@ -67,6 +109,26 @@ template<typename... Ts> class ArmHomeAction : public Action<Ts...> {
AlarmControlPanel *alarm_control_panel_; AlarmControlPanel *alarm_control_panel_;
}; };
template<typename... Ts> class ArmNightAction : public Action<Ts...> {
public:
explicit ArmNightAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}
TEMPLATABLE_VALUE(std::string, code)
void play(Ts... x) override {
auto call = this->alarm_control_panel_->make_call();
auto code = this->code_.optional_value(x...);
if (code.has_value()) {
call.set_code(code.value());
}
call.arm_night();
call.perform();
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
template<typename... Ts> class DisarmAction : public Action<Ts...> { template<typename... Ts> class DisarmAction : public Action<Ts...> {
public: public:
explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {} explicit DisarmAction(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {}

View file

@ -60,26 +60,26 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_FLOW in config: if flow_config := config.get(CONF_FLOW):
sens = await sensor.new_sensor(config[CONF_FLOW]) sens = await sensor.new_sensor(flow_config)
cg.add(var.set_flow_sensor(sens)) cg.add(var.set_flow_sensor(sens))
if CONF_HEAD in config: if head_config := config.get(CONF_HEAD):
sens = await sensor.new_sensor(config[CONF_HEAD]) sens = await sensor.new_sensor(head_config)
cg.add(var.set_head_sensor(sens)) cg.add(var.set_head_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(config[CONF_POWER]) sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(config[CONF_CURRENT]) sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_SPEED in config: if speed_config := config.get(CONF_SPEED):
sens = await sensor.new_sensor(config[CONF_SPEED]) sens = await sensor.new_sensor(speed_config)
cg.add(var.set_speed_sensor(sens)) cg.add(var.set_speed_sensor(sens))
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(config[CONF_VOLTAGE]) sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))

View file

@ -47,10 +47,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))

View file

@ -44,10 +44,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery(sens)) cg.add(var.set_battery(sens))
if CONF_ILLUMINANCE in config: if illuminance_config := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) sens = await sensor.new_sensor(illuminance_config)
cg.add(var.set_illuminance(sens)) cg.add(var.set_illuminance(sens))

View file

@ -115,8 +115,8 @@ async def animation_action_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren) var = cg.new_Pvariable(action_id, template_arg, paren)
if CONF_FRAME in config: if (frame := config.get(CONF_FRAME)) is not None:
template_ = await cg.templatable(config[CONF_FRAME], args, cg.uint16) template_ = await cg.templatable(frame, args, cg.uint16)
cg.add(var.set_frame(template_)) cg.add(var.set_frame(template_))
return var return var
@ -289,8 +289,8 @@ async def to_code(config):
espImage.IMAGE_TYPE[config[CONF_TYPE]], espImage.IMAGE_TYPE[config[CONF_TYPE]],
) )
cg.add(var.set_transparency(transparent)) cg.add(var.set_transparency(transparent))
if CONF_LOOP in config: if loop_config := config.get(CONF_LOOP):
start = config[CONF_LOOP][CONF_START_FRAME] start = loop_config[CONF_START_FRAME]
end = config[CONF_LOOP].get(CONF_END_FRAME, frames) end = loop_config.get(CONF_END_FRAME, frames)
count = config[CONF_LOOP].get(CONF_REPEAT, -1) count = loop_config.get(CONF_REPEAT, -1)
cg.add(var.set_loop(start, end, count)) cg.add(var.set_loop(start, end, count))

View file

@ -116,9 +116,8 @@ async def to_code(config):
cg.add(var.register_user_service(trigger)) cg.add(var.register_user_service(trigger))
await automation.build_automation(trigger, func_args, conf) await automation.build_automation(trigger, func_args, conf)
if CONF_ENCRYPTION in config: if encryption_config := config.get(CONF_ENCRYPTION):
conf = config[CONF_ENCRYPTION] decoded = base64.b64decode(encryption_config[CONF_KEY])
decoded = base64.b64decode(conf[CONF_KEY])
cg.add(var.set_noise_psk(list(decoded))) cg.add(var.set_noise_psk(list(decoded)))
cg.add_define("USE_API_NOISE") cg.add_define("USE_API_NOISE")
cg.add_library("esphome/noise-c", "0.1.4") cg.add_library("esphome/noise-c", "0.1.4")

View file

@ -31,12 +31,10 @@ CONFIG_SCHEMA = cv.Schema(
async def to_code(config): async def to_code(config):
hub = await cg.get_variable(config[CONF_AS3935_ID]) hub = await cg.get_variable(config[CONF_AS3935_ID])
if CONF_DISTANCE in config: if distance_config := config.get(CONF_DISTANCE):
conf = config[CONF_DISTANCE] sens = await sensor.new_sensor(distance_config)
distance_sensor = await sensor.new_sensor(conf) cg.add(hub.set_distance_sensor(sens))
cg.add(hub.set_distance_sensor(distance_sensor))
if CONF_LIGHTNING_ENERGY in config: if lightning_energy_config := config.get(CONF_LIGHTNING_ENERGY):
conf = config[CONF_LIGHTNING_ENERGY] sens = await sensor.new_sensor(lightning_energy_config)
lightning_energy_sensor = await sensor.new_sensor(conf) cg.add(hub.set_energy_sensor(sens))
cg.add(hub.set_energy_sensor(lightning_energy_sensor))

View file

@ -107,6 +107,6 @@ async def to_code(config):
cg.add(var.set_astep(config[CONF_ASTEP])) cg.add(var.set_astep(config[CONF_ASTEP]))
for conf_id, set_sensor_func in SENSORS.items(): for conf_id, set_sensor_func in SENSORS.items():
if conf_id in config: if sens_config := config.get(conf_id):
sens = await sensor.new_sensor(config[conf_id]) sens = await sensor.new_sensor(sens_config)
cg.add(getattr(var, set_sensor_func)(sens)) cg.add(getattr(var, set_sensor_func)(sens))

View file

@ -83,18 +83,18 @@ async def to_code(config):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(config[CONF_HUMIDITY]) sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))
if CONF_BATTERY_LEVEL in config: if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery_level(sens)) cg.add(var.set_battery_level(sens))
if CONF_BATTERY_VOLTAGE in config: if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE):
sens = await sensor.new_sensor(config[CONF_BATTERY_VOLTAGE]) sens = await sensor.new_sensor(battery_voltage_config)
cg.add(var.set_battery_voltage(sens)) cg.add(var.set_battery_voltage(sens))
if CONF_SIGNAL_STRENGTH in config: if signal_strength_config := config.get(CONF_SIGNAL_STRENGTH):
sens = await sensor.new_sensor(config[CONF_SIGNAL_STRENGTH]) sens = await sensor.new_sensor(signal_strength_config)
cg.add(var.set_signal_strength(sens)) cg.add(var.set_signal_strength(sens))

View file

@ -124,29 +124,29 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(config[CONF_VOLTAGE]) sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
sens = await sensor.new_sensor(config[CONF_CURRENT]) sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
sens = await sensor.new_sensor(config[CONF_POWER]) sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_REACTIVE_POWER in config: if reactive_power_config := config.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER]) sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(sens)) cg.add(var.set_reactive_power_sensor(sens))
if CONF_POWER_FACTOR in config: if power_factor_config := config.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(config[CONF_POWER_FACTOR]) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(sens)) cg.add(var.set_power_factor_sensor(sens))
if CONF_FORWARD_ACTIVE_ENERGY in config: if forward_active_energy_config := config.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(config[CONF_FORWARD_ACTIVE_ENERGY]) sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(sens)) cg.add(var.set_forward_active_energy_sensor(sens))
if CONF_REVERSE_ACTIVE_ENERGY in config: if reverse_active_energy_config := config.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(config[CONF_REVERSE_ACTIVE_ENERGY]) sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(sens)) cg.add(var.set_reverse_active_energy_sensor(sens))
if CONF_FREQUENCY in config: if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(config[CONF_FREQUENCY]) sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens)) cg.add(var.set_freq_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT])) cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT]))

View file

@ -151,33 +151,35 @@ async def to_code(config):
conf = config[phase] conf = config[phase]
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE])) cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT])) cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
if CONF_VOLTAGE in conf: if voltage_config := conf.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(conf[CONF_VOLTAGE]) sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(i, sens)) cg.add(var.set_voltage_sensor(i, sens))
if CONF_CURRENT in conf: if current_config := conf.get(CONF_CURRENT):
sens = await sensor.new_sensor(conf[CONF_CURRENT]) sens = await sensor.new_sensor(current_config)
cg.add(var.set_current_sensor(i, sens)) cg.add(var.set_current_sensor(i, sens))
if CONF_POWER in conf: if power_config := conf.get(CONF_POWER):
sens = await sensor.new_sensor(conf[CONF_POWER]) sens = await sensor.new_sensor(power_config)
cg.add(var.set_power_sensor(i, sens)) cg.add(var.set_power_sensor(i, sens))
if CONF_REACTIVE_POWER in conf: if reactive_power_config := conf.get(CONF_REACTIVE_POWER):
sens = await sensor.new_sensor(conf[CONF_REACTIVE_POWER]) sens = await sensor.new_sensor(reactive_power_config)
cg.add(var.set_reactive_power_sensor(i, sens)) cg.add(var.set_reactive_power_sensor(i, sens))
if CONF_POWER_FACTOR in conf: if power_factor_config := conf.get(CONF_POWER_FACTOR):
sens = await sensor.new_sensor(conf[CONF_POWER_FACTOR]) sens = await sensor.new_sensor(power_factor_config)
cg.add(var.set_power_factor_sensor(i, sens)) cg.add(var.set_power_factor_sensor(i, sens))
if CONF_FORWARD_ACTIVE_ENERGY in conf: if forward_active_energy_config := conf.get(CONF_FORWARD_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_FORWARD_ACTIVE_ENERGY]) sens = await sensor.new_sensor(forward_active_energy_config)
cg.add(var.set_forward_active_energy_sensor(i, sens)) cg.add(var.set_forward_active_energy_sensor(i, sens))
if CONF_REVERSE_ACTIVE_ENERGY in conf: if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY):
sens = await sensor.new_sensor(conf[CONF_REVERSE_ACTIVE_ENERGY]) sens = await sensor.new_sensor(reverse_active_energy_config)
cg.add(var.set_reverse_active_energy_sensor(i, sens)) cg.add(var.set_reverse_active_energy_sensor(i, sens))
if CONF_FREQUENCY in config:
sens = await sensor.new_sensor(config[CONF_FREQUENCY]) if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens)) cg.add(var.set_freq_sensor(sens))
if CONF_CHIP_TEMPERATURE in config: if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
sens = await sensor.new_sensor(config[CONF_CHIP_TEMPERATURE]) sens = await sensor.new_sensor(chip_temperature_config)
cg.add(var.set_chip_temperature_sensor(sens)) cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA])) cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))

View file

@ -87,6 +87,6 @@ async def to_code(config):
(CONF_MOISTURE, var.set_soil_moisture), (CONF_MOISTURE, var.set_soil_moisture),
(CONF_ILLUMINANCE, var.set_illuminance), (CONF_ILLUMINANCE, var.set_illuminance),
]: ]:
if config_key in config: if sensor_config := config.get(config_key):
sens = await sensor.new_sensor(config[config_key]) sens = await sensor.new_sensor(sensor_config)
cg.add(setter(sens)) cg.add(setter(sens))

View file

@ -57,19 +57,18 @@ async def to_code(config):
var.get_idle_trigger(), [], config[CONF_IDLE_ACTION] var.get_idle_trigger(), [], config[CONF_IDLE_ACTION]
) )
if CONF_COOL_ACTION in config: if cool_action_config := config.get(CONF_COOL_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_cool_trigger(), [], config[CONF_COOL_ACTION] var.get_cool_trigger(), [], cool_action_config
) )
cg.add(var.set_supports_cool(True)) cg.add(var.set_supports_cool(True))
if CONF_HEAT_ACTION in config: if heat_action_config := config.get(CONF_HEAT_ACTION):
await automation.build_automation( await automation.build_automation(
var.get_heat_trigger(), [], config[CONF_HEAT_ACTION] var.get_heat_trigger(), [], heat_action_config
) )
cg.add(var.set_supports_heat(True)) cg.add(var.set_supports_heat(True))
if CONF_AWAY_CONFIG in config: if away := config.get(CONF_AWAY_CONFIG):
away = config[CONF_AWAY_CONFIG]
away_config = BangBangClimateTargetTempConfig( away_config = BangBangClimateTargetTempConfig(
away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW], away[CONF_DEFAULT_TARGET_TEMPERATURE_LOW],
away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH], away[CONF_DEFAULT_TARGET_TEMPERATURE_HIGH],

View file

@ -45,8 +45,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await ble_client.register_ble_node(var, config) await ble_client.register_ble_node(var, config)
if CONF_TIME_ID in config: if time_id := config.get(CONF_TIME_ID):
time_ = await cg.get_variable(config[CONF_TIME_ID]) time_ = await cg.get_variable(time_id)
cg.add(var.set_time_id(time_)) cg.add(var.set_time_id(time_))
if CONF_RECEIVE_TIMEOUT in config: if (receive_timeout := config.get(CONF_RECEIVE_TIMEOUT)) is not None:
cg.add(var.set_status_timeout(config[CONF_RECEIVE_TIMEOUT])) cg.add(var.set_status_timeout(receive_timeout))

View file

@ -3,6 +3,7 @@
#include "bedjet_hub.h" #include "bedjet_hub.h"
#include "bedjet_child.h" #include "bedjet_child.h"
#include "bedjet_const.h" #include "bedjet_const.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace bedjet { namespace bedjet {
@ -373,7 +374,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) { if (this->last_notify_ == 0 || delta > MIN_NOTIFY_THROTTLE || this->force_refresh_) {
// Set reentrant flag to prevent processing multiple packets. // Set reentrant flag to prevent processing multiple packets.
this->processing_ = true; this->processing_ = true;
ESP_LOGVV(TAG, "[%s] Decoding packet: last=%d, delta=%d, force=%s", this->get_name().c_str(), ESP_LOGVV(TAG, "[%s] Decoding packet: last=%" PRId32 ", delta=%" PRId32 ", force=%s", this->get_name().c_str(),
this->last_notify_, delta, this->force_refresh_ ? "y" : "n"); this->last_notify_, delta, this->force_refresh_ ? "y" : "n");
bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len); bool needs_extra = this->codec_->decode_notify(param->notify.value, param->notify.value_len);
@ -523,11 +524,11 @@ void BedJetHub::dispatch_status_() {
ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str()); ESP_LOGI(TAG, "[%s] Still waiting for first GATT notify event.", this->get_name().c_str());
} else if (diff > NOTIFY_WARN_THRESHOLD) { } else if (diff > NOTIFY_WARN_THRESHOLD) {
ESP_LOGW(TAG, "[%s] Last GATT notify was %d seconds ago.", this->get_name().c_str(), diff / 1000); ESP_LOGW(TAG, "[%s] Last GATT notify was %" PRId32 " seconds ago.", this->get_name().c_str(), diff / 1000);
} }
if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) { if (this->timeout_ > 0 && diff > this->timeout_ && this->parent()->enabled) {
ESP_LOGW(TAG, "[%s] Timed out after %d sec. Retrying...", this->get_name().c_str(), this->timeout_); ESP_LOGW(TAG, "[%s] Timed out after %" PRId32 " sec. Retrying...", this->get_name().c_str(), this->timeout_);
// set_enabled(false) will only close the connection if state != IDLE. // set_enabled(false) will only close the connection if state != IDLE.
this->parent()->set_state(espbt::ClientState::CONNECTING); this->parent()->set_state(espbt::ClientState::CONNECTING);
this->parent()->set_enabled(false); this->parent()->set_enabled(false);

View file

@ -29,10 +29,10 @@ async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT]) output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_)) cg.add(var.set_output(output_))
if CONF_OSCILLATION_OUTPUT in config: if oscillation_output_id := config.get(CONF_OSCILLATION_OUTPUT):
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = await cg.get_variable(oscillation_output_id)
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config: if direction_output_id := config.get(CONF_DIRECTION_OUTPUT):
direction_output = await cg.get_variable(config[CONF_DIRECTION_OUTPUT]) direction_output = await cg.get_variable(direction_output_id)
cg.add(var.set_direction(direction_output)) cg.add(var.set_direction(direction_output))

View file

@ -467,14 +467,14 @@ def binary_sensor_schema(
async def setup_binary_sensor_core_(var, config): async def setup_binary_sensor_core_(var, config):
await setup_entity(var, config) await setup_entity(var, config)
if CONF_DEVICE_CLASS in config: if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(device_class))
if CONF_PUBLISH_INITIAL_STATE in config: if publish_initial_state := config.get(CONF_PUBLISH_INITIAL_STATE):
cg.add(var.set_publish_initial_state(config[CONF_PUBLISH_INITIAL_STATE])) cg.add(var.set_publish_initial_state(publish_initial_state))
if CONF_INVERTED in config: if inverted := config.get(CONF_INVERTED):
cg.add(var.set_inverted(config[CONF_INVERTED])) cg.add(var.set_inverted(inverted))
if CONF_FILTERS in config: if filters_config := config.get(CONF_FILTERS):
filters = await cg.build_registry_list(FILTER_REGISTRY, config[CONF_FILTERS]) filters = await cg.build_registry_list(FILTER_REGISTRY, filters_config)
cg.add(var.add_filters(filters)) cg.add(var.add_filters(filters))
for conf in config.get(CONF_ON_PRESS, []): for conf in config.get(CONF_ON_PRESS, []):
@ -518,8 +518,8 @@ async def setup_binary_sensor_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "x")], conf) await automation.build_automation(trigger, [(bool, "x")], conf)
if CONF_MQTT_ID in config: if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View file

@ -93,35 +93,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT_1 in config: if current_1_config := config.get(CONF_CURRENT_1):
conf = config[CONF_CURRENT_1] sens = await sensor.new_sensor(current_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_1(sens)) cg.add(var.set_current_sensor_1(sens))
if CONF_CURRENT_2 in config: if current_2_config := config.get(CONF_CURRENT_2):
conf = config[CONF_CURRENT_2] sens = await sensor.new_sensor(current_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor_2(sens)) cg.add(var.set_current_sensor_2(sens))
if CONF_ACTIVE_POWER_1 in config: if active_power_1_config := config.get(CONF_ACTIVE_POWER_1):
conf = config[CONF_ACTIVE_POWER_1] sens = await sensor.new_sensor(active_power_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_1(sens)) cg.add(var.set_power_sensor_1(sens))
if CONF_ACTIVE_POWER_2 in config: if active_power_2_config := config.get(CONF_ACTIVE_POWER_2):
conf = config[CONF_ACTIVE_POWER_2] sens = await sensor.new_sensor(active_power_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor_2(sens)) cg.add(var.set_power_sensor_2(sens))
if CONF_ENERGY_1 in config: if energy_1_config := config.get(CONF_ENERGY_1):
conf = config[CONF_ENERGY_1] sens = await sensor.new_sensor(energy_1_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_1(sens)) cg.add(var.set_energy_sensor_1(sens))
if CONF_ENERGY_2 in config: if energy_2_config := config.get(CONF_ENERGY_2):
conf = config[CONF_ENERGY_2] sens = await sensor.new_sensor(energy_2_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_2(sens)) cg.add(var.set_energy_sensor_2(sens))
if CONF_ENERGY_TOTAL in config: if energy_total_config := config.get(CONF_ENERGY_TOTAL):
conf = config[CONF_ENERGY_TOTAL] sens = await sensor.new_sensor(energy_total_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor_sum(sens)) cg.add(var.set_energy_sensor_sum(sens))

View file

@ -79,27 +79,21 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_INTERNAL_TEMPERATURE in config: if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE):
conf = config[CONF_INTERNAL_TEMPERATURE] sens = await sensor.new_sensor(internal_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_internal_temperature_sensor(sens)) cg.add(var.set_internal_temperature_sensor(sens))
if CONF_EXTERNAL_TEMPERATURE in config: if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
conf = config[CONF_EXTERNAL_TEMPERATURE] sens = await sensor.new_sensor(external_temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_external_temperature_sensor(sens)) cg.add(var.set_external_temperature_sensor(sens))

View file

@ -71,23 +71,18 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))
if CONF_FREQUENCY in config: if frequency_config := config.get(CONF_FREQUENCY):
conf = config[CONF_FREQUENCY] sens = await sensor.new_sensor(frequency_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_frequency_sensor(sens))

View file

@ -129,32 +129,18 @@ async def characteristic_sensor_to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config.get(CONF_DESCRIPTOR_UUID):
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
if CONF_LAMBDA in config: if lambda_config := config.get(CONF_LAMBDA):
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_ lambda_config, [(adv_data_t_const_ref, "x")], return_type=cg.float_
) )
cg.add(var.set_data_to_value(lambda_)) cg.add(var.set_data_to_value(lambda_))

View file

@ -88,27 +88,13 @@ async def to_code(config):
) )
cg.add(var.set_char_uuid128(uuid128)) cg.add(var.set_char_uuid128(uuid128))
if CONF_DESCRIPTOR_UUID in config: if descriptor_uuid := config:
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_descr_uuid16(esp32_ble_tracker.as_hex(descriptor_uuid)))
var.set_descr_uuid16( elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) cg.add(var.set_descr_uuid32(esp32_ble_tracker.as_hex(descriptor_uuid)))
) elif len(descriptor_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(descriptor_uuid)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid32_format
):
cg.add(
var.set_descr_uuid32(
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
)
)
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
esp32_ble_tracker.bt_uuid128_format
):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_DESCRIPTOR_UUID]
)
cg.add(var.set_descr_uuid128(uuid128)) cg.add(var.set_descr_uuid128(uuid128))
await cg.register_component(var, config) await cg.register_component(var, config)

View file

@ -55,35 +55,27 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MIN_RSSI in config: if min_rssi := config.get(CONF_MIN_RSSI):
cg.add(var.set_minimum_rssi(config[CONF_MIN_RSSI])) cg.add(var.set_minimum_rssi(min_rssi))
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View file

@ -57,32 +57,24 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config) await esp32_ble_tracker.register_ble_device(var, config)
if CONF_MAC_ADDRESS in config: if mac_address := config.get(CONF_MAC_ADDRESS):
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_address(mac_address.as_hex))
if CONF_SERVICE_UUID in config: if service_uuid := config.get(CONF_SERVICE_UUID):
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): if len(service_uuid) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add( cg.add(var.set_service_uuid16(esp32_ble_tracker.as_hex(service_uuid)))
var.set_service_uuid16( elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid32_format):
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]) cg.add(var.set_service_uuid32(esp32_ble_tracker.as_hex(service_uuid)))
) elif len(service_uuid) == len(esp32_ble_tracker.bt_uuid128_format):
) uuid128 = esp32_ble_tracker.as_reversed_hex_array(service_uuid)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
cg.add(
var.set_service_uuid32(
esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])
)
)
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(uuid128)) cg.add(var.set_service_uuid128(uuid128))
if CONF_IBEACON_UUID in config: if ibeacon_uuid := config.get(CONF_IBEACON_UUID):
ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(config[CONF_IBEACON_UUID])) ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid))
cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) cg.add(var.set_ibeacon_uuid(ibeacon_uuid))
if CONF_IBEACON_MAJOR in config: if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None:
cg.add(var.set_ibeacon_major(config[CONF_IBEACON_MAJOR])) cg.add(var.set_ibeacon_major(ibeacon_major))
if CONF_IBEACON_MINOR in config: if (ibeacon_minor := config.get(CONF_IBEACON_MINOR)) is not None:
cg.add(var.set_ibeacon_minor(config[CONF_IBEACON_MINOR])) cg.add(var.set_ibeacon_minor(ibeacon_minor))

View file

@ -98,22 +98,19 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View file

@ -130,27 +130,23 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
if CONF_HUMIDITY in config: if humidity_config := config.get(CONF_HUMIDITY):
conf = config[CONF_HUMIDITY] sens = await sensor.new_sensor(humidity_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_humidity_sensor(sens)) cg.add(var.set_humidity_sensor(sens))
cg.add(var.set_humidity_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
if CONF_GAS_RESISTANCE in config: if gas_resistance_config := config.get(CONF_GAS_RESISTANCE):
conf = config[CONF_GAS_RESISTANCE] sens = await sensor.new_sensor(gas_resistance_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_gas_resistance_sensor(sens)) cg.add(var.set_gas_resistance_sensor(sens))
cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]])) cg.add(var.set_iir_filter(IIR_FILTER_OPTIONS[config[CONF_IIR_FILTER]]))

View file

@ -108,12 +108,13 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await sensor.new_sensor(sensor_config)
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_sensor")(sens))
if CONF_SAMPLE_RATE in conf: if CONF_SAMPLE_RATE in sensor_config:
cg.add(getattr(hub, f"set_{key}_sample_rate")(conf[CONF_SAMPLE_RATE])) cg.add(
getattr(hub, f"set_{key}_sample_rate")(sensor_config[CONF_SAMPLE_RATE])
)
async def to_code(config): async def to_code(config):

View file

@ -21,9 +21,8 @@ CONFIG_SCHEMA = cv.Schema(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await text_sensor.new_text_sensor(sensor_config)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View file

@ -47,12 +47,10 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure(sens)) cg.add(var.set_pressure(sens))

View file

@ -83,16 +83,14 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))

View file

@ -87,14 +87,16 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER])) cg.add(var.set_iir_filter_config(config[CONF_IIR_FILTER]))
if CONF_TEMPERATURE in config: if temperature_config := config.get(CONF_TEMPERATURE):
conf = config[CONF_TEMPERATURE] sens = await sensor.new_sensor(temperature_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_temperature_sensor(sens)) cg.add(var.set_temperature_sensor(sens))
cg.add(var.set_temperature_oversampling_config(conf[CONF_OVERSAMPLING])) cg.add(
var.set_temperature_oversampling_config(
temperature_config[CONF_OVERSAMPLING]
)
)
if CONF_PRESSURE in config: if pressure_config := config.get(CONF_PRESSURE):
conf = config[CONF_PRESSURE] sens = await sensor.new_sensor(pressure_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling_config(conf[CONF_OVERSAMPLING])) cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))

View file

View file

@ -0,0 +1,596 @@
/*
* Adds support for Bosch's BMP581 high accuracy pressure and temperature sensor
* - Component structure based on ESPHome's BMP3XX component (as of March, 2023)
* - Implementation is easier as the sensor itself automatically compensates pressure for the temperature
* - Temperature and pressure data is converted via simple divison operations in this component
* - IIR filter level can independently be applied to temperature and pressure measurements
* - Bosch's BMP5-Sensor-API was consulted to verify that sensor configuration is done correctly
* - Copyright (c) 2022 Bosch Sensortec Gmbh, SPDX-License-Identifier: BSD-3-Clause
* - This component uses forced power mode only so measurements are synchronized by the host
* - All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
*/
#include "bmp581.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace bmp581 {
static const char *const TAG = "bmp581";
static const LogString *oversampling_to_str(Oversampling oversampling) {
switch (oversampling) {
case Oversampling::OVERSAMPLING_NONE:
return LOG_STR("None");
case Oversampling::OVERSAMPLING_X2:
return LOG_STR("2x");
case Oversampling::OVERSAMPLING_X4:
return LOG_STR("4x");
case Oversampling::OVERSAMPLING_X8:
return LOG_STR("8x");
case Oversampling::OVERSAMPLING_X16:
return LOG_STR("16x");
case Oversampling::OVERSAMPLING_X32:
return LOG_STR("32x");
case Oversampling::OVERSAMPLING_X64:
return LOG_STR("64x");
case Oversampling::OVERSAMPLING_X128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
static const LogString *iir_filter_to_str(IIRFilter filter) {
switch (filter) {
case IIRFilter::IIR_FILTER_OFF:
return LOG_STR("OFF");
case IIRFilter::IIR_FILTER_2:
return LOG_STR("2x");
case IIRFilter::IIR_FILTER_4:
return LOG_STR("4x");
case IIRFilter::IIR_FILTER_8:
return LOG_STR("8x");
case IIRFilter::IIR_FILTER_16:
return LOG_STR("16x");
case IIRFilter::IIR_FILTER_32:
return LOG_STR("32x");
case IIRFilter::IIR_FILTER_64:
return LOG_STR("64x");
case IIRFilter::IIR_FILTER_128:
return LOG_STR("128x");
default:
return LOG_STR("");
}
}
void BMP581Component::dump_config() {
ESP_LOGCONFIG(TAG, "BMP581:");
switch (this->error_code_) {
case NONE:
break;
case ERROR_COMMUNICATION_FAILED:
ESP_LOGE(TAG, " Communication with BMP581 failed!");
break;
case ERROR_WRONG_CHIP_ID:
ESP_LOGE(TAG, " BMP581 has wrong chip ID - please verify you are using a BMP 581");
break;
case ERROR_SENSOR_RESET:
ESP_LOGE(TAG, " BMP581 failed to reset");
break;
case ERROR_SENSOR_STATUS:
ESP_LOGE(TAG, " BMP581 sensor status failed, there were NVM problems");
break;
case ERROR_PRIME_IIR_FAILED:
ESP_LOGE(TAG, " BMP581's IIR Filter failed to prime with an initial measurement");
break;
default:
ESP_LOGE(TAG, " BMP581 error code %d", (int) this->error_code_);
break;
}
LOG_I2C_DEVICE(this);
LOG_UPDATE_INTERVAL(this);
ESP_LOGCONFIG(TAG, " Measurement conversion time: %ums", this->conversion_time_);
if (this->temperature_sensor_) {
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_temperature_level_)));
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->temperature_oversampling_)));
}
if (this->pressure_sensor_) {
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
ESP_LOGCONFIG(TAG, " IIR Filter: %s", LOG_STR_ARG(iir_filter_to_str(this->iir_pressure_level_)));
ESP_LOGCONFIG(TAG, " Oversampling: %s", LOG_STR_ARG(oversampling_to_str(this->pressure_oversampling_)));
}
}
void BMP581Component::setup() {
/*
* Setup goes through several stages, which follows the post-power-up procedure (page 18 of datasheet) and then sets
* configured options
* 1) Soft reboot
* 2) Verify ASIC chip ID matches BMP581
* 3) Verify sensor status (check if NVM is okay)
* 4) Enable data ready interrupt
* 5) Write oversampling settings and set internal configuration values
* 6) Configure and prime IIR Filter(s), if enabled
*/
this->error_code_ = NONE;
ESP_LOGCONFIG(TAG, "Setting up BMP581...");
////////////////////
// 1) Soft reboot //
////////////////////
// Power-On-Reboot bit is asserted if sensor successfully reset
if (!this->reset_()) {
ESP_LOGE(TAG, "BMP581 failed to reset");
this->error_code_ = ERROR_SENSOR_RESET;
this->mark_failed();
return;
}
///////////////////////////////////////////
// 2) Verify ASIC chip ID matches BMP581 //
///////////////////////////////////////////
uint8_t chip_id;
// read chip id from sensor
if (!this->read_byte(BMP581_CHIP_ID, &chip_id)) {
ESP_LOGE(TAG, "Failed to read chip id");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// verify id
if (chip_id != BMP581_ASIC_ID) {
ESP_LOGE(TAG, "Unknown chip ID, is this a BMP581?");
this->error_code_ = ERROR_WRONG_CHIP_ID;
this->mark_failed();
return;
}
////////////////////////////////////////////////////
// 3) Verify sensor status (check if NVM is okay) //
////////////////////////////////////////////////////
if (!this->read_byte(BMP581_STATUS, &this->status_.reg)) {
ESP_LOGE(TAG, "Failed to read status register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// verify status_nvm_rdy bit (it is asserted if boot was successful)
if (!(this->status_.bit.status_nvm_rdy)) {
ESP_LOGE(TAG, "NVM not ready after boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
return;
}
// verify status_nvm_err bit (it is asserted if an error is detected)
if (this->status_.bit.status_nvm_err) {
ESP_LOGE(TAG, "NVM error detected on boot");
this->error_code_ = ERROR_SENSOR_STATUS;
this->mark_failed();
return;
}
////////////////////////////////////
// 4) Enable data ready interrupt //
////////////////////////////////////
// enable the data ready interrupt source
if (!this->write_interrupt_source_settings_(true)) {
ESP_LOGE(TAG, "Failed to write interrupt source register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
//////////////////////////////////////////////////////////////////////////
// 5) Write oversampling settings and set internal configuration values //
//////////////////////////////////////////////////////////////////////////
// configure pressure readings, if sensor is defined
// otherwise, disable pressure oversampling
if (this->pressure_sensor_) {
this->osr_config_.bit.press_en = true;
} else {
this->pressure_oversampling_ = OVERSAMPLING_NONE;
}
// write oversampling settings
if (!this->write_oversampling_settings_(this->temperature_oversampling_, this->pressure_oversampling_)) {
ESP_LOGE(TAG, "Failed to write oversampling register");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
// set output data rate to 4 Hz=0x19 (page 65 of datasheet)
// - ?shouldn't? matter as this component only uses FORCED_MODE - datasheet is ambiguous
// - If in NORMAL_MODE or NONSTOP_MODE, then this would still allow deep standby to save power
// - will be written to BMP581 at next requested measurement
this->odr_config_.bit.odr = 0x19;
///////////////////////////////////////////////////////
/// 6) Configure and prime IIR Filter(s), if enabled //
///////////////////////////////////////////////////////
if ((this->iir_temperature_level_ != IIR_FILTER_OFF) || (this->iir_pressure_level_ != IIR_FILTER_OFF)) {
if (!this->write_iir_settings_(this->iir_temperature_level_, this->iir_pressure_level_)) {
ESP_LOGE(TAG, "Failed to write IIR configuration registers");
this->error_code_ = ERROR_COMMUNICATION_FAILED;
this->mark_failed();
return;
}
if (!this->prime_iir_filter_()) {
ESP_LOGE(TAG, "Failed to prime the IIR filter with an intiial measurement");
this->error_code_ = ERROR_PRIME_IIR_FAILED;
this->mark_failed();
return;
}
}
}
void BMP581Component::update() {
/*
* Each update goes through several stages
* 0) Verify either a temperature or pressure sensor is defined before proceeding
* 1) Request a measurement
* 2) Wait for measurement to finish (based on oversampling rates)
* 3) Read data registers for temperature and pressure, if applicable
* 4) Publish measurements to sensor(s), if applicable
*/
////////////////////////////////////////////////////////////////////////////////////
// 0) Verify either a temperature or pressure sensor is defined before proceeding //
////////////////////////////////////////////////////////////////////////////////////
if ((!this->temperature_sensor_) && (!this->pressure_sensor_)) {
return;
}
//////////////////////////////
// 1) Request a measurement //
//////////////////////////////
ESP_LOGVV(TAG, "Requesting a measurement from sensor");
if (!this->start_measurement_()) {
ESP_LOGW(TAG, "Failed to request forced measurement of sensor");
this->status_set_warning();
return;
}
//////////////////////////////////////////////////////////////////////
// 2) Wait for measurement to finish (based on oversampling rates) //
//////////////////////////////////////////////////////////////////////
ESP_LOGVV(TAG, "Measurement is expected to take %d ms to complete", this->conversion_time_);
this->set_timeout("measurement", this->conversion_time_, [this]() {
float temperature = 0.0;
float pressure = 0.0;
////////////////////////////////////////////////////////////////////////
// 3) Read data registers for temperature and pressure, if applicable //
////////////////////////////////////////////////////////////////////////
if (this->pressure_sensor_) {
if (!this->read_temperature_and_pressure_(temperature, pressure)) {
ESP_LOGW(TAG, "Failed to read temperature and pressure measurements, skipping update");
this->status_set_warning();
return;
}
} else {
if (!this->read_temperature_(temperature)) {
ESP_LOGW(TAG, "Failed to read temperature measurement, skipping update");
this->status_set_warning();
return;
}
}
/////////////////////////////////////////////////////////
// 4) Publish measurements to sensor(s), if applicable //
/////////////////////////////////////////////////////////
if (this->temperature_sensor_) {
this->temperature_sensor_->publish_state(temperature);
}
if (this->pressure_sensor_) {
this->pressure_sensor_->publish_state(pressure);
}
this->status_clear_warning();
});
}
bool BMP581Component::check_data_readiness_() {
// - verifies component is not internally in standby mode
// - reads interrupt status register
// - checks if data ready bit is asserted
// - If true, then internally sets component to standby mode if in forced mode
// - returns data readiness state
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
ESP_LOGD(TAG, "Data is not ready, sensor is in standby mode");
return false;
}
uint8_t status;
if (!this->read_byte(BMP581_INT_STATUS, &status)) {
ESP_LOGE(TAG, "Failed to read interrupt status register");
return false;
}
this->int_status_.reg = status;
if (this->int_status_.bit.drdy_data_reg) {
// If in forced mode, then set internal record of the power mode to STANDBY_MODE
// - sensor automatically returns to standby mode after completing a forced measurement
if (this->odr_config_.bit.pwr_mode == FORCED_MODE) {
this->odr_config_.bit.pwr_mode = STANDBY_MODE;
}
return true;
}
return false;
}
bool BMP581Component::prime_iir_filter_() {
// - temporarily disables oversampling for a fast initial measurement; avoids slowing down ESPHome's startup process
// - enables IIR filter flushing with forced measurements
// - forces a measurement; flushing the IIR filter and priming it with a current value
// - disables IIR filter flushing with forced measurements
// - reverts to internally configured oversampling rates
// - returns success of all register writes/priming
// store current internal oversampling settings to revert to after priming
Oversampling current_temperature_oversampling = (Oversampling) this->osr_config_.bit.osr_t;
Oversampling current_pressure_oversampling = (Oversampling) this->osr_config_.bit.osr_p;
// temporarily disables oversampling for temperature and pressure for a fast priming measurement
if (!this->write_oversampling_settings_(OVERSAMPLING_NONE, OVERSAMPLING_NONE)) {
ESP_LOGE(TAG, "Failed to write oversampling register");
return false;
}
// flush the IIR filter with forced measurements (we will only flush once)
this->dsp_config_.bit.iir_flush_forced_en = true;
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
ESP_LOGE(TAG, "Failed to write IIR source register");
return false;
}
// forces an intial measurement
// - this measurements flushes the IIR filter reflecting written DSP settings
// - flushing with this initial reading avoids having the internal previous data aquisition being 0, which
// (I)nfinitely affects future values
if (!this->start_measurement_()) {
ESP_LOGE(TAG, "Failed to request a forced measurement");
return false;
}
// wait for priming measurement to complete
// - with oversampling disabled, the conversion time for a single measurement for pressure and temperature is
// ceilf(1.05*(1.0+1.0)) = 3ms
// - see page 12 of datasheet for details
delay(3);
if (!this->check_data_readiness_()) {
ESP_LOGE(TAG, "IIR priming measurement was not ready");
return false;
}
// disable IIR filter flushings on future forced measurements
this->dsp_config_.bit.iir_flush_forced_en = false;
if (!this->write_byte(BMP581_DSP, this->dsp_config_.reg)) {
ESP_LOGE(TAG, "Failed to write IIR source register");
return false;
}
// revert oversampling rates to original settings
return this->write_oversampling_settings_(current_temperature_oversampling, current_pressure_oversampling);
}
bool BMP581Component::read_temperature_(float &temperature) {
// - verifies data is ready to be read
// - reads in 3 bytes of temperature data
// - returns whether successful, where the the variable parameter contains
// - the measured temperature (in degrees Celsius)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
}
uint8_t data[3];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 3)) {
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;
}
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
temperature = (float) (raw_temp / 65536.0); // convert measurement to degrees Celsius (page 22 of datasheet)
return true;
}
bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &pressure) {
// - verifies data is ready to be read
// - reads in 6 bytes of temperature data (3 for temeperature, 3 for pressure)
// - returns whether successful, where the variable parameters contain
// - the measured temperature (in degrees Celsius)
// - the measured pressure (in Pa)
if (!this->check_data_readiness_()) {
ESP_LOGW(TAG, "Data from sensor isn't ready, skipping this update");
this->status_set_warning();
return false;
}
uint8_t data[6];
if (!this->read_bytes(BMP581_MEASUREMENT_DATA, &data[0], 6)) {
ESP_LOGW(TAG, "Failed to read sensor's measurement data");
this->status_set_warning();
return false;
}
// temperature MSB is in data[2], LSB is in data[1], XLSB in data[0]
int32_t raw_temp = (int32_t) data[2] << 16 | (int32_t) data[1] << 8 | (int32_t) data[0];
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];
pressure = (float) (raw_press / 64.0); // Divide by 2^6=64 for Pa (page 22 of datasheet)
return true;
}
bool BMP581Component::reset_() {
// - writes reset command to the command register
// - waits for sensor to complete reset
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
// writes reset command to BMP's command register
if (!this->write_byte(BMP581_COMMAND, RESET_COMMAND)) {
ESP_LOGE(TAG, "Failed to write reset command");
return false;
}
// t_{soft_res} = 2ms (page 11 of datasheet); time it takes to enter standby mode
// - round up to 3 ms
delay(3);
// read interrupt status register
if (!this->read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
ESP_LOGE(TAG, "Failed to read interrupt status register");
return false;
}
// Power-On-Reboot bit is asserted if sensor successfully reset
return this->int_status_.bit.por;
}
bool BMP581Component::start_measurement_() {
// - only pushes the sensor into FORCED_MODE for a reading if already in STANDBY_MODE
// - returns whether a measurement is in progress or has been initiated
if (this->odr_config_.bit.pwr_mode == STANDBY_MODE) {
return this->write_power_mode_(FORCED_MODE);
} else {
return true;
}
}
bool BMP581Component::write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir) {
// - ensures data registers store filtered values
// - sets IIR filter levels on sensor
// - matches other default settings on sensor
// - writes configuration to the two relevant registers
// - returns success or failure of write to the registers
// If the temperature/pressure IIR filter is configured, then ensure data registers store the filtered measurement
this->dsp_config_.bit.shdw_sel_iir_t = (temperature_iir != IIR_FILTER_OFF);
this->dsp_config_.bit.shdw_sel_iir_p = (pressure_iir != IIR_FILTER_OFF);
// set temperature and pressure IIR filter level to configured values
this->iir_config_.bit.set_iir_t = temperature_iir;
this->iir_config_.bit.set_iir_p = pressure_iir;
// enable pressure and temperature compensation (page 61 of datasheet)
// - ?only relevant if IIR filter is applied?; the datasheet is ambiguous
// - matches BMP's default setting
this->dsp_config_.bit.comp_pt_en = 0x3;
// BMP581_DSP register and BMP581_DSP_IIR registers are successive
// - allows us to write the IIR configuration with one command to both registers
uint8_t register_data[2] = {this->dsp_config_.reg, this->iir_config_.reg};
return this->write_bytes(BMP581_DSP, register_data, sizeof(register_data));
}
bool BMP581Component::write_interrupt_source_settings_(bool data_ready_enable) {
// - updates component's internal setting
// - returns success or failure of write to interrupt source register
this->int_source_.bit.drdy_data_reg_en = data_ready_enable;
// write interrupt source register
return this->write_byte(BMP581_INT_SOURCE, this->int_source_.reg);
}
bool BMP581Component::write_oversampling_settings_(Oversampling temperature_oversampling,
Oversampling pressure_oversampling) {
// - updates component's internal setting
// - returns success or failure of write to Over-Sampling Rate register
this->osr_config_.bit.osr_t = temperature_oversampling;
this->osr_config_.bit.osr_p = pressure_oversampling;
return this->write_byte(BMP581_OSR, this->osr_config_.reg);
}
bool BMP581Component::write_power_mode_(OperationMode mode) {
// - updates the component's internal power mode
// - returns success or failure of write to Output Data Rate register
this->odr_config_.bit.pwr_mode = mode;
// write odr register
return this->write_byte(BMP581_ODR, this->odr_config_.reg);
}
} // namespace bmp581
} // namespace esphome

View file

@ -0,0 +1,222 @@
// All datasheet page references refer to Bosch Document Number BST-BMP581-DS004-04 (revision number 1.4)
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace bmp581 {
static const uint8_t BMP581_ASIC_ID = 0x50; // BMP581's ASIC chip ID (page 51 of datasheet)
static const uint8_t RESET_COMMAND = 0xB6; // Soft reset command
// BMP581 Register Addresses
enum {
BMP581_CHIP_ID = 0x01, // read chip ID
BMP581_INT_SOURCE = 0x15, // write interrupt sources
BMP581_MEASUREMENT_DATA =
0x1D, // read measurement registers, 0x1D-0x1F are temperature XLSB to MSB and 0x20-0x22 are pressure XLSB to MSB
BMP581_INT_STATUS = 0x27, // read interrupt statuses
BMP581_STATUS = 0x28, // read sensor status
BMP581_DSP = 0x30, // write sensor configuration
BMP581_DSP_IIR = 0x31, // write IIR filter configuration
BMP581_OSR = 0x36, // write oversampling configuration
BMP581_ODR = 0x37, // write data rate and power mode configuration
BMP581_COMMAND = 0x7E // write sensor command
};
// BMP581 Power mode operations
enum OperationMode {
STANDBY_MODE = 0x0, // no active readings
NORMAL_MODE = 0x1, // read continuously at ODR configured rate and standby between
FORCED_MODE = 0x2, // read sensor once (only reading mode used by this component)
NONSTOP_MODE = 0x3 // read continuously with no standby
};
// Temperature and pressure sensors can be oversampled to reduce noise
enum Oversampling {
OVERSAMPLING_NONE = 0x0,
OVERSAMPLING_X2 = 0x1,
OVERSAMPLING_X4 = 0x2,
OVERSAMPLING_X8 = 0x3,
OVERSAMPLING_X16 = 0x4,
OVERSAMPLING_X32 = 0x5,
OVERSAMPLING_X64 = 0x6,
OVERSAMPLING_X128 = 0x7
};
// Infinite Impulse Response filter reduces noise caused by ambient disturbances
enum IIRFilter {
IIR_FILTER_OFF = 0x0,
IIR_FILTER_2 = 0x1,
IIR_FILTER_4 = 0x2,
IIR_FILTER_8 = 0x3,
IIR_FILTER_16 = 0x4,
IIR_FILTER_32 = 0x5,
IIR_FILTER_64 = 0x6,
IIR_FILTER_128 = 0x7
};
class BMP581Component : public PollingComponent, public i2c::I2CDevice {
public:
float get_setup_priority() const override { return setup_priority::DATA; }
void dump_config() override;
void setup() override;
void update() override;
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { this->pressure_sensor_ = pressure_sensor; }
void set_temperature_oversampling_config(Oversampling temperature_oversampling) {
this->temperature_oversampling_ = temperature_oversampling;
}
void set_pressure_oversampling_config(Oversampling pressure_oversampling) {
this->pressure_oversampling_ = pressure_oversampling;
}
void set_temperature_iir_filter_config(IIRFilter iir_temperature_level) {
this->iir_temperature_level_ = iir_temperature_level;
}
void set_pressure_iir_filter_config(IIRFilter iir_pressure_level) { this->iir_pressure_level_ = iir_pressure_level; }
void set_conversion_time(uint8_t conversion_time) { this->conversion_time_ = conversion_time; }
protected:
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
Oversampling temperature_oversampling_;
Oversampling pressure_oversampling_;
IIRFilter iir_temperature_level_;
IIRFilter iir_pressure_level_;
// Stores the sensors conversion time needed for a measurement based on oversampling settings and datasheet (page 12)
// Computed in Python during codegen
uint8_t conversion_time_;
// Checks if the BMP581 has measurement data ready by checking the sensor's interrupts
bool check_data_readiness_();
// Flushes the IIR filter and primes an initial reading
bool prime_iir_filter_();
// Reads temperature data from sensor and converts data to measurement in degrees Celsius
bool read_temperature_(float &temperature);
// Reads temperature and pressure data from sensor and converts data to measurements in degrees Celsius and Pa
bool read_temperature_and_pressure_(float &temperature, float &pressure);
// Soft resets the BMP581
bool reset_();
// Initiates a measurement on sensor by switching to FORCED_MODE
bool start_measurement_();
// Writes the IIR filter configuration to the DSP and DSP_IIR registers
bool write_iir_settings_(IIRFilter temperature_iir, IIRFilter pressure_iir);
// Writes whether to enable the data ready interrupt to the interrupt source register
bool write_interrupt_source_settings_(bool data_ready_enable);
// Writes the oversampling settings to the OSR register
bool write_oversampling_settings_(Oversampling temperature_oversampling, Oversampling pressure_oversampling);
// Sets the power mode on the BMP581 by writing to the ODR register
bool write_power_mode_(OperationMode mode);
enum ErrorCode {
NONE = 0,
ERROR_COMMUNICATION_FAILED,
ERROR_WRONG_CHIP_ID,
ERROR_SENSOR_STATUS,
ERROR_SENSOR_RESET,
ERROR_PRIME_IIR_FAILED
} error_code_{NONE};
// BMP581's interrupt source register (address 0x15) to configure which interrupts are enabled (page 54 of datasheet)
union {
struct {
uint8_t drdy_data_reg_en : 1; // Data ready interrupt enable
uint8_t fifo_full_en : 1; // FIFO full interrupt enable
uint8_t fifo_ths_en : 1; // FIFO threshold/watermark interrupt enable
uint8_t oor_p_en : 1; // Pressure data out-of-range interrupt enable
} bit;
uint8_t reg;
} int_source_ = {.reg = 0};
// BMP581's interrupt status register (address 0x27) to determine ensor's current state (page 58 of datasheet)
union {
struct {
uint8_t drdy_data_reg : 1; // Data ready
uint8_t fifo_full : 1; // FIFO full
uint8_t fifo_ths : 1; // FIFO fhreshold/watermark
uint8_t oor_p : 1; // Pressure data out-of-range
uint8_t por : 1; // Power-On-Reset complete
} bit;
uint8_t reg;
} int_status_ = {.reg = 0};
// BMP581's status register (address 0x28) to determine if sensor has setup correctly (page 58 of datasheet)
union {
struct {
uint8_t status_core_rdy : 1;
uint8_t status_nvm_rdy : 1; // asserted if NVM is ready of operations
uint8_t status_nvm_err : 1; // asserted if NVM error
uint8_t status_nvm_cmd_err : 1; // asserted if boot command error
uint8_t status_boot_err_corrected : 1; // asserted if a boot error has been corrected
uint8_t : 2;
uint8_t st_crack_pass : 1; // asserted if crack check has executed without detecting a crack
} bit;
uint8_t reg;
} status_ = {.reg = 0};
// BMP581's dsp register (address 0x30) to configure data registers iir selection (page 61 of datasheet)
union {
struct {
uint8_t comp_pt_en : 2; // enable temperature and pressure compensation
uint8_t iir_flush_forced_en : 1; // IIR filter is flushed in forced mode
uint8_t shdw_sel_iir_t : 1; // temperature data register value selected before or after iir
uint8_t fifo_sel_iir_t : 1; // FIFO temperature data register value secected before or after iir
uint8_t shdw_sel_iir_p : 1; // pressure data register value selected before or after iir
uint8_t fifo_sel_iir_p : 1; // FIFO pressure data register value selected before or after iir
uint8_t oor_sel_iir_p : 1; // pressure out-of-range value selected before or after iir
} bit;
uint8_t reg;
} dsp_config_ = {.reg = 0};
// BMP581's iir register (address 0x31) to configure iir filtering(page 62 of datasheet)
union {
struct {
uint8_t set_iir_t : 3; // Temperature IIR filter coefficient
uint8_t set_iir_p : 3; // Pressure IIR filter coefficient
} bit;
uint8_t reg;
} iir_config_ = {.reg = 0};
// BMP581's OSR register (address 0x36) to configure Over-Sampling Rates (page 64 of datasheet)
union {
struct {
uint8_t osr_t : 3; // Temperature oversampling
uint8_t osr_p : 3; // Pressure oversampling
uint8_t press_en : 1; // Enables pressure measurement
} bit;
uint8_t reg;
} osr_config_ = {.reg = 0};
// BMP581's odr register (address 0x37) to configure output data rate and power mode (page 64 of datasheet)
union {
struct {
uint8_t pwr_mode : 2; // power mode of sensor
uint8_t odr : 5; // output data rate
uint8_t deep_dis : 1; // deep standby disabled if asserted
} bit;
uint8_t reg;
} odr_config_ = {.reg = 0};
};
} // namespace bmp581
} // namespace esphome

View file

@ -0,0 +1,163 @@
import math
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
CONF_IIR_FILTER,
CONF_OVERSAMPLING,
CONF_PRESSURE,
CONF_TEMPERATURE,
DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PASCAL,
)
CODEOWNERS = ["@kahrendt"]
DEPENDENCIES = ["i2c"]
bmp581_ns = cg.esphome_ns.namespace("bmp581")
Oversampling = bmp581_ns.enum("Oversampling")
OVERSAMPLING_OPTIONS = {
"NONE": Oversampling.OVERSAMPLING_NONE,
"2X": Oversampling.OVERSAMPLING_X2,
"4X": Oversampling.OVERSAMPLING_X4,
"8X": Oversampling.OVERSAMPLING_X8,
"16X": Oversampling.OVERSAMPLING_X16,
"32X": Oversampling.OVERSAMPLING_X32,
"64X": Oversampling.OVERSAMPLING_X64,
"128X": Oversampling.OVERSAMPLING_X128,
}
IIRFilter = bmp581_ns.enum("IIRFilter")
IIR_FILTER_OPTIONS = {
"OFF": IIRFilter.IIR_FILTER_OFF,
"2X": IIRFilter.IIR_FILTER_2,
"4X": IIRFilter.IIR_FILTER_4,
"8X": IIRFilter.IIR_FILTER_8,
"16X": IIRFilter.IIR_FILTER_16,
"32X": IIRFilter.IIR_FILTER_32,
"64X": IIRFilter.IIR_FILTER_64,
"128X": IIRFilter.IIR_FILTER_128,
}
BMP581Component = bmp581_ns.class_(
"BMP581Component", cg.PollingComponent, i2c.I2CDevice
)
def compute_measurement_conversion_time(config):
# - adds up sensor conversion time based on temperature and pressure oversampling rates given in datasheet
# - returns a rounded up time in ms
# Page 12 of datasheet
PRESSURE_OVERSAMPLING_CONVERSION_TIMES = {
"NONE": 1.0,
"2X": 1.7,
"4X": 2.9,
"8X": 5.4,
"16X": 10.4,
"32X": 20.4,
"64X": 40.4,
"128X": 80.4,
}
# Page 12 of datasheet
TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES = {
"NONE": 1.0,
"2X": 1.1,
"4X": 1.5,
"8X": 2.1,
"16X": 3.3,
"32X": 5.8,
"64X": 10.8,
"128X": 20.8,
}
pressure_conversion_time = (
0.0 # No conversion time necessary without a pressure sensor
)
if pressure_config := config.get(CONF_PRESSURE):
pressure_conversion_time = PRESSURE_OVERSAMPLING_CONVERSION_TIMES[
pressure_config.get(CONF_OVERSAMPLING)
]
temperature_conversion_time = (
1.0 # BMP581 always samples the temperature even if only reading pressure
)
if temperature_config := config.get(CONF_TEMPERATURE):
temperature_conversion_time = TEMPERATURE_OVERSAMPLING_CONVERSION_TIMES[
temperature_config.get(CONF_OVERSAMPLING)
]
# Datasheet indicates a 5% possible error in each conversion time listed
return math.ceil(1.05 * (pressure_conversion_time + temperature_conversion_time))
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BMP581Component),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="NONE"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
),
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PASCAL,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
).extend(
{
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
OVERSAMPLING_OPTIONS, upper=True
),
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
IIR_FILTER_OPTIONS, upper=True
),
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x46))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
cg.add(
var.set_temperature_oversampling_config(
temperature_config[CONF_OVERSAMPLING]
)
)
cg.add(
var.set_temperature_iir_filter_config(temperature_config[CONF_IIR_FILTER])
)
if pressure_config := config.get(CONF_PRESSURE):
sens = await sensor.new_sensor(pressure_config)
cg.add(var.set_pressure_sensor(sens))
cg.add(var.set_pressure_oversampling_config(pressure_config[CONF_OVERSAMPLING]))
cg.add(var.set_pressure_iir_filter_config(pressure_config[CONF_IIR_FILTER]))
cg.add(var.set_conversion_time(compute_measurement_conversion_time(config)))

View file

@ -85,11 +85,11 @@ async def setup_button_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
if CONF_DEVICE_CLASS in config: if device_class := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) cg.add(var.set_device_class(device_class))
if CONF_MQTT_ID in config: if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config) await mqtt.register_mqtt_component(mqtt_, config)

View file

@ -17,12 +17,11 @@ CONF_ON_FRAME = "on_frame"
def validate_id(config): def validate_id(config):
if CONF_CAN_ID in config: can_id = config[CONF_CAN_ID]
id_value = config[CONF_CAN_ID] id_ext = config[CONF_USE_EXTENDED_ID]
id_ext = config[CONF_USE_EXTENDED_ID] if not id_ext:
if not id_ext: if can_id > 0x7FF:
if id_value > 0x7FF: raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
return config return config
@ -145,8 +144,8 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_CANBUS_ID]) await cg.register_parented(var, config[CONF_CANBUS_ID])
if CONF_CAN_ID in config: if can_id := config.get(CONF_CAN_ID):
can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32) can_id = await cg.templatable(can_id, args, cg.uint32)
cg.add(var.set_can_id(can_id)) cg.add(var.set_can_id(can_id))
use_extended_id = await cg.templatable( use_extended_id = await cg.templatable(
config[CONF_USE_EXTENDED_ID], args, cg.uint32 config[CONF_USE_EXTENDED_ID], args, cg.uint32

View file

@ -37,8 +37,8 @@ async def to_code(config):
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD])) cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES])) cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
if CONF_RESET_PIN in config: if reset_pin_config := config.get(CONF_RESET_PIN):
pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) pin = await cg.gpio_pin_expression(reset_pin_config)
cg.add(var.set_reset_pin(pin)) cg.add(var.set_reset_pin(pin))
await cg.register_component(var, config) await cg.register_component(var, config)

View file

@ -69,16 +69,16 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_TVOC]) sens = await sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens)) cg.add(var.set_tvoc(sens))
if CONF_VERSION in config: if version_config := config.get(CONF_VERSION):
sens = await text_sensor.new_text_sensor(config[CONF_VERSION]) sens = await text_sensor.new_text_sensor(version_config)
cg.add(var.set_version(sens)) cg.add(var.set_version(sens))
if CONF_BASELINE in config: if (baseline := config.get(CONF_BASELINE)) is not None:
cg.add(var.set_baseline(config[CONF_BASELINE])) cg.add(var.set_baseline(baseline))
if CONF_TEMPERATURE in config: if temperature_id := config.get(CONF_TEMPERATURE):
sens = await cg.get_variable(config[CONF_TEMPERATURE]) sens = await cg.get_variable(temperature_id)
cg.add(var.set_temperature(sens)) cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config: if humidity_id := config.get(CONF_HUMIDITY):
sens = await cg.get_variable(config[CONF_HUMIDITY]) sens = await cg.get_variable(humidity_id)
cg.add(var.set_humidity(sens)) cg.add(var.set_humidity(sens))

View file

@ -127,8 +127,12 @@ def single_visual_temperature(value):
# Actions # Actions
ControlAction = climate_ns.class_("ControlAction", automation.Action) ControlAction = climate_ns.class_("ControlAction", automation.Action)
StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) StateTrigger = climate_ns.class_(
ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template()) "StateTrigger", automation.Trigger.template(Climate.operator("ref"))
)
ControlTrigger = climate_ns.class_(
"ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
)
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any( VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
single_visual_temperature, single_visual_temperature,
@ -322,11 +326,15 @@ async def setup_climate_core_(var, config):
for conf in config.get(CONF_ON_STATE, []): for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(
trigger, [(Climate.operator("ref"), "x")], conf
)
for conf in config.get(CONF_ON_CONTROL, []): for conf in config.get(CONF_ON_CONTROL, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf) await automation.build_automation(
trigger, [(ClimateCall.operator("ref"), "x")], conf
)
async def register_climate(var, config): async def register_climate(var, config):

View file

@ -42,17 +42,17 @@ template<typename... Ts> class ControlAction : public Action<Ts...> {
Climate *climate_; Climate *climate_;
}; };
class ControlTrigger : public Trigger<> { class ControlTrigger : public Trigger<ClimateCall &> {
public: public:
ControlTrigger(Climate *climate) { ControlTrigger(Climate *climate) {
climate->add_on_control_callback([this]() { this->trigger(); }); climate->add_on_control_callback([this](ClimateCall &x) { this->trigger(x); });
} }
}; };
class StateTrigger : public Trigger<> { class StateTrigger : public Trigger<Climate &> {
public: public:
StateTrigger(Climate *climate) { StateTrigger(Climate *climate) {
climate->add_on_state_callback([this]() { this->trigger(); }); climate->add_on_state_callback([this](Climate &x) { this->trigger(x); });
} }
}; };

View file

@ -7,6 +7,7 @@ namespace climate {
static const char *const TAG = "climate"; static const char *const TAG = "climate";
void ClimateCall::perform() { void ClimateCall::perform() {
this->parent_->control_callback_.call(*this);
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
this->validate_(); this->validate_();
if (this->mode_.has_value()) { if (this->mode_.has_value()) {
@ -44,7 +45,6 @@ void ClimateCall::perform() {
if (this->target_temperature_high_.has_value()) { if (this->target_temperature_high_.has_value()) {
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
} }
this->parent_->control_callback_.call();
this->parent_->control(*this); this->parent_->control(*this);
} }
void ClimateCall::validate_() { void ClimateCall::validate_() {
@ -300,11 +300,11 @@ ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode)
return *this; return *this;
} }
void Climate::add_on_state_callback(std::function<void()> &&callback) { void Climate::add_on_state_callback(std::function<void(Climate &)> &&callback) {
this->state_callback_.add(std::move(callback)); this->state_callback_.add(std::move(callback));
} }
void Climate::add_on_control_callback(std::function<void()> &&callback) { void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callback) {
this->control_callback_.add(std::move(callback)); this->control_callback_.add(std::move(callback));
} }
@ -408,7 +408,7 @@ void Climate::publish_state() {
} }
// Send state to frontend // Send state to frontend
this->state_callback_.call(); this->state_callback_.call(*this);
// Save state // Save state
this->save_state_(); this->save_state_();
} }

View file

@ -198,7 +198,7 @@ class Climate : public EntityBase {
* *
* @param callback The callback to call. * @param callback The callback to call.
*/ */
void add_on_state_callback(std::function<void()> &&callback); void add_on_state_callback(std::function<void(Climate &)> &&callback);
/** /**
* Add a callback for the climate device configuration; each time the configuration parameters of a climate device * Add a callback for the climate device configuration; each time the configuration parameters of a climate device
@ -206,7 +206,7 @@ class Climate : public EntityBase {
* *
* @param callback The callback to call. * @param callback The callback to call.
*/ */
void add_on_control_callback(std::function<void()> &&callback); void add_on_control_callback(std::function<void(ClimateCall &)> &&callback);
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
* for more info. * for more info.
@ -273,8 +273,8 @@ class Climate : public EntityBase {
void dump_traits_(const char *tag); void dump_traits_(const char *tag);
CallbackManager<void()> state_callback_{}; CallbackManager<void(Climate &)> state_callback_{};
CallbackManager<void()> control_callback_{}; CallbackManager<void(ClimateCall &)> control_callback_{};
ESPPreferenceObject rtc_; ESPPreferenceObject rtc_;
optional<float> visual_min_temperature_override_{}; optional<float> visual_min_temperature_override_{};
optional<float> visual_max_temperature_override_{}; optional<float> visual_max_temperature_override_{};

View file

@ -44,11 +44,11 @@ async def register_climate_ir(var, config):
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
if CONF_SENSOR in config: if sensor_id := config.get(CONF_SENSOR):
sens = await cg.get_variable(config[CONF_SENSOR]) sens = await cg.get_variable(sensor_id)
cg.add(var.set_sensor(sens)) cg.add(var.set_sensor(sens))
if CONF_RECEIVER_ID in config: if receiver_id := config.get(CONF_RECEIVER_ID):
receiver = await cg.get_variable(config[CONF_RECEIVER_ID]) receiver = await cg.get_variable(receiver_id)
cg.add(receiver.register_listener(var)) cg.add(receiver.register_listener(var))
transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID]) transmitter = await cg.get_variable(config[CONF_TRANSMITTER_ID])

View file

@ -82,5 +82,5 @@ async def to_code(config):
cg.new_variable( cg.new_variable(
config[CONF_ID], config[CONF_ID],
cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), cg.ArrayInitializer(r, g, b, w),
) )

View file

@ -114,7 +114,7 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei
if (!decoded.has_value()) if (!decoded.has_value())
return false; return false;
// Decoded remote state y 3 bytes long code. // Decoded remote state y 3 bytes long code.
uint32_t remote_state = *decoded; uint32_t remote_state = (*decoded).second;
ESP_LOGV(TAG, "Decoded 0x%06X", remote_state); ESP_LOGV(TAG, "Decoded 0x%06X", remote_state);
if ((remote_state & 0xFF0000) != 0xB20000) if ((remote_state & 0xFF0000) != 0xB20000)
return false; return false;

View file

@ -113,17 +113,14 @@ async def to_code(config):
cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF])) cg.add(var.set_hpf_enable(config[CONF_CURRENT_HPF], config[CONF_VOLTAGE_HPF]))
cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY])) cg.add(var.set_pulse_energy_wh(config[CONF_PULSE_ENERGY]))
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))

View file

@ -69,19 +69,15 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await uart.register_uart_device(var, config) await uart.register_uart_device(var, config)
if CONF_VOLTAGE in config: if voltage_config := config.get(CONF_VOLTAGE):
conf = config[CONF_VOLTAGE] sens = await sensor.new_sensor(voltage_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_voltage_sensor(sens)) cg.add(var.set_voltage_sensor(sens))
if CONF_CURRENT in config: if current_config := config.get(CONF_CURRENT):
conf = config[CONF_CURRENT] sens = await sensor.new_sensor(current_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_current_sensor(sens)) cg.add(var.set_current_sensor(sens))
if CONF_POWER in config: if power_config := config.get(CONF_POWER):
conf = config[CONF_POWER] sens = await sensor.new_sensor(power_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_power_sensor(sens)) cg.add(var.set_power_sensor(sens))
if CONF_ENERGY in config: if energy_config := config.get(CONF_ENERGY):
conf = config[CONF_ENERGY] sens = await sensor.new_sensor(energy_config)
sens = await sensor.new_sensor(conf)
cg.add(var.set_energy_sensor(sens)) cg.add(var.set_energy_sensor(sens))

View file

@ -66,59 +66,63 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config) await cg.register_component(var, config)
yield cover.register_cover(var, config) await cover.register_cover(var, config)
yield automation.build_automation( await automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION] var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
) )
# OPEN # OPEN
bin = yield cg.get_variable(config[CONF_OPEN_SENSOR]) bin = await cg.get_variable(config[CONF_OPEN_SENSOR])
cg.add(var.set_open_sensor(bin)) cg.add(var.set_open_sensor(bin))
cg.add( cg.add(
var.set_open_moving_current_threshold( var.set_open_moving_current_threshold(
config[CONF_OPEN_MOVING_CURRENT_THRESHOLD] config[CONF_OPEN_MOVING_CURRENT_THRESHOLD]
) )
) )
if CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD in config: if (
cg.add( open_obsticle_current_threshold := config.get(
var.set_open_obstacle_current_threshold( CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD
config[CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD]
)
) )
) is not None:
cg.add(var.set_open_obstacle_current_threshold(open_obsticle_current_threshold))
cg.add(var.set_open_duration(config[CONF_OPEN_DURATION])) cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
yield automation.build_automation( await automation.build_automation(
var.get_open_trigger(), [], config[CONF_OPEN_ACTION] var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
) )
# CLOSE # CLOSE
bin = yield cg.get_variable(config[CONF_CLOSE_SENSOR]) bin = await cg.get_variable(config[CONF_CLOSE_SENSOR])
cg.add(var.set_close_sensor(bin)) cg.add(var.set_close_sensor(bin))
cg.add( cg.add(
var.set_close_moving_current_threshold( var.set_close_moving_current_threshold(
config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD] config[CONF_CLOSE_MOVING_CURRENT_THRESHOLD]
) )
) )
if CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD in config: if (
cg.add( close_obsticle_current_threshold := config.get(
var.set_close_obstacle_current_threshold( CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD
config[CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD]
)
) )
) is not None:
cg.add(
var.set_close_obstacle_current_threshold(close_obsticle_current_threshold)
)
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION])) cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
yield automation.build_automation( await automation.build_automation(
var.get_close_trigger(), [], config[CONF_CLOSE_ACTION] var.get_close_trigger(), [], config[CONF_CLOSE_ACTION]
) )
cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK])) cg.add(var.set_obstacle_rollback(config[CONF_OBSTACLE_ROLLBACK]))
if CONF_MAX_DURATION in config: if (max_duration := config.get(CONF_MAX_DURATION)) is not None:
cg.add(var.set_max_duration(config[CONF_MAX_DURATION])) cg.add(var.set_max_duration(max_duration))
cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION])) cg.add(var.set_malfunction_detection(config[CONF_MALFUNCTION_DETECTION]))
if CONF_MALFUNCTION_ACTION in config: if malfunction_action := config.get(CONF_MALFUNCTION_ACTION):
yield automation.build_automation( await automation.build_automation(
var.get_malfunction_trigger(), [], config[CONF_MALFUNCTION_ACTION] var.get_malfunction_trigger(), [], malfunction_action
) )
cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY])) cg.add(var.set_start_sensing_delay(config[CONF_START_SENSING_DELAY]))

View file

@ -37,16 +37,12 @@ async def to_code(config):
cwhite = await cg.get_variable(config[CONF_COLD_WHITE]) cwhite = await cg.get_variable(config[CONF_COLD_WHITE])
cg.add(var.set_cold_white(cwhite)) cg.add(var.set_cold_white(cwhite))
if CONF_COLD_WHITE_COLOR_TEMPERATURE in config: if cold_white_color_temperature := config.get(CONF_COLD_WHITE_COLOR_TEMPERATURE):
cg.add( cg.add(var.set_cold_white_temperature(cold_white_color_temperature))
var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])
)
wwhite = await cg.get_variable(config[CONF_WARM_WHITE]) wwhite = await cg.get_variable(config[CONF_WARM_WHITE])
cg.add(var.set_warm_white(wwhite)) cg.add(var.set_warm_white(wwhite))
if CONF_WARM_WHITE_COLOR_TEMPERATURE in config: if warm_white_color_temperature := config.get(CONF_WARM_WHITE_COLOR_TEMPERATURE):
cg.add( cg.add(var.set_warm_white_temperature(warm_white_color_temperature))
var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])
)
cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS])) cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS]))

View file

@ -5,7 +5,6 @@ from esphome.const import CONF_ID, CONF_ADDRESS
CODEOWNERS = ["@s1lvi0"] CODEOWNERS = ["@s1lvi0"]
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
AUTO_LOAD = ["sensor", "text_sensor", "binary_sensor"]
CONF_BMS_DALY_ID = "bms_daly_id" CONF_BMS_DALY_ID = "bms_daly_id"

View file

@ -27,9 +27,8 @@ CONFIG_SCHEMA = cv.All(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] var = await binary_sensor.new_binary_sensor(sensor_config)
var = await binary_sensor.new_binary_sensor(conf)
cg.add(getattr(hub, f"set_{key}_binary_sensor")(var)) cg.add(getattr(hub, f"set_{key}_binary_sensor")(var))

View file

@ -1,6 +1,6 @@
#include "daly_bms.h" #include "daly_bms.h"
#include "esphome/core/log.h"
#include <vector> #include <vector>
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace daly_bms { namespace daly_bms {
@ -19,7 +19,7 @@ static const uint8_t DALY_REQUEST_STATUS = 0x94;
static const uint8_t DALY_REQUEST_CELL_VOLTAGE = 0x95; static const uint8_t DALY_REQUEST_CELL_VOLTAGE = 0x95;
static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96; static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96;
void DalyBmsComponent::setup() {} void DalyBmsComponent::setup() { this->next_request_ = 1; }
void DalyBmsComponent::dump_config() { void DalyBmsComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Daly BMS:"); ESP_LOGCONFIG(TAG, "Daly BMS:");
@ -27,20 +27,78 @@ void DalyBmsComponent::dump_config() {
} }
void DalyBmsComponent::update() { void DalyBmsComponent::update() {
this->request_data_(DALY_REQUEST_BATTERY_LEVEL); this->trigger_next_ = true;
this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE); this->next_request_ = 0;
this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE); }
this->request_data_(DALY_REQUEST_MOS);
this->request_data_(DALY_REQUEST_STATUS);
this->request_data_(DALY_REQUEST_CELL_VOLTAGE);
this->request_data_(DALY_REQUEST_TEMPERATURE);
std::vector<uint8_t> get_battery_level_data; void DalyBmsComponent::loop() {
int available_data = this->available(); const uint32_t now = millis();
if (available_data >= DALY_FRAME_SIZE) { if (this->receiving_ && (now - this->last_transmission_ >= 200)) {
get_battery_level_data.resize(available_data); // last transmission too long ago. Reset RX index.
this->read_array(get_battery_level_data.data(), available_data); ESP_LOGW(TAG, "Last transmission too long ago. Reset RX index.");
this->decode_data_(get_battery_level_data); this->data_.clear();
this->receiving_ = false;
}
if ((now - this->last_transmission_ >= 250) && !this->trigger_next_) {
// last transmittion longer than 0.25s ago -> trigger next request
this->last_transmission_ = now;
this->trigger_next_ = true;
}
if (available())
this->last_transmission_ = now;
while (available()) {
uint8_t c;
read_byte(&c);
if (!this->receiving_) {
if (c != 0xa5)
continue;
this->receiving_ = true;
}
this->data_.push_back(c);
if (this->data_.size() == 4)
this->data_count_ = c;
if ((this->data_.size() > 4) and (data_.size() == this->data_count_ + 5)) {
this->decode_data_(this->data_);
this->data_.clear();
this->receiving_ = false;
}
}
if (this->trigger_next_) {
this->trigger_next_ = false;
switch (this->next_request_) {
case 0:
this->request_data_(DALY_REQUEST_BATTERY_LEVEL);
this->next_request_ = 1;
break;
case 1:
this->request_data_(DALY_REQUEST_MIN_MAX_VOLTAGE);
this->next_request_ = 2;
break;
case 2:
this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE);
this->next_request_ = 3;
break;
case 3:
this->request_data_(DALY_REQUEST_MOS);
this->next_request_ = 4;
break;
case 4:
this->request_data_(DALY_REQUEST_STATUS);
this->next_request_ = 5;
break;
case 5:
this->request_data_(DALY_REQUEST_CELL_VOLTAGE);
this->next_request_ = 6;
break;
case 6:
this->request_data_(DALY_REQUEST_TEMPERATURE);
this->next_request_ = 7;
break;
case 7:
default:
break;
}
} }
} }
@ -49,21 +107,23 @@ float DalyBmsComponent::get_setup_priority() const { return setup_priority::DATA
void DalyBmsComponent::request_data_(uint8_t data_id) { void DalyBmsComponent::request_data_(uint8_t data_id) {
uint8_t request_message[DALY_FRAME_SIZE]; uint8_t request_message[DALY_FRAME_SIZE];
request_message[0] = 0xA5; // Start Flag request_message[0] = 0xA5; // Start Flag
request_message[1] = addr_; // Communication Module Address request_message[1] = this->addr_; // Communication Module Address
request_message[2] = data_id; // Data ID request_message[2] = data_id; // Data ID
request_message[3] = 0x08; // Data Length (Fixed) request_message[3] = 0x08; // Data Length (Fixed)
request_message[4] = 0x00; // Empty Data request_message[4] = 0x00; // Empty Data
request_message[5] = 0x00; // | request_message[5] = 0x00; // |
request_message[6] = 0x00; // | request_message[6] = 0x00; // |
request_message[7] = 0x00; // | request_message[7] = 0x00; // |
request_message[8] = 0x00; // | request_message[8] = 0x00; // |
request_message[9] = 0x00; // | request_message[9] = 0x00; // |
request_message[10] = 0x00; // | request_message[10] = 0x00; // |
request_message[11] = 0x00; // Empty Data request_message[11] = 0x00; // Empty Data
request_message[12] = (uint8_t) (request_message[0] + request_message[1] + request_message[2] + request_message[12] = (uint8_t) (request_message[0] + request_message[1] + request_message[2] +
request_message[3]); // Checksum (Lower byte of the other bytes sum) request_message[3]); // Checksum (Lower byte of the other bytes sum)
ESP_LOGV(TAG, "Request datapacket Nr %x", data_id);
this->write_array(request_message, sizeof(request_message)); this->write_array(request_message, sizeof(request_message));
this->flush(); this->flush();
} }
@ -82,6 +142,7 @@ void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
if (checksum == it[12]) { if (checksum == it[12]) {
switch (it[2]) { switch (it[2]) {
#ifdef USE_SENSOR
case DALY_REQUEST_BATTERY_LEVEL: case DALY_REQUEST_BATTERY_LEVEL:
if (this->voltage_sensor_) { if (this->voltage_sensor_) {
this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10); this->voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 10);
@ -95,36 +156,37 @@ void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
break; break;
case DALY_REQUEST_MIN_MAX_VOLTAGE: case DALY_REQUEST_MIN_MAX_VOLTAGE:
if (this->max_cell_voltage_) { if (this->max_cell_voltage_sensor_) {
this->max_cell_voltage_->publish_state((float) encode_uint16(it[4], it[5]) / 1000); this->max_cell_voltage_sensor_->publish_state((float) encode_uint16(it[4], it[5]) / 1000);
} }
if (this->max_cell_voltage_number_) { if (this->max_cell_voltage_number_sensor_) {
this->max_cell_voltage_number_->publish_state(it[6]); this->max_cell_voltage_number_sensor_->publish_state(it[6]);
} }
if (this->min_cell_voltage_) { if (this->min_cell_voltage_sensor_) {
this->min_cell_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->min_cell_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->min_cell_voltage_number_) { if (this->min_cell_voltage_number_sensor_) {
this->min_cell_voltage_number_->publish_state(it[9]); this->min_cell_voltage_number_sensor_->publish_state(it[9]);
} }
break; break;
case DALY_REQUEST_MIN_MAX_TEMPERATURE: case DALY_REQUEST_MIN_MAX_TEMPERATURE:
if (this->max_temperature_) { if (this->max_temperature_sensor_) {
this->max_temperature_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET); this->max_temperature_sensor_->publish_state(it[4] - DALY_TEMPERATURE_OFFSET);
} }
if (this->max_temperature_probe_number_) { if (this->max_temperature_probe_number_sensor_) {
this->max_temperature_probe_number_->publish_state(it[5]); this->max_temperature_probe_number_sensor_->publish_state(it[5]);
} }
if (this->min_temperature_) { if (this->min_temperature_sensor_) {
this->min_temperature_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET); this->min_temperature_sensor_->publish_state(it[6] - DALY_TEMPERATURE_OFFSET);
} }
if (this->min_temperature_probe_number_) { if (this->min_temperature_probe_number_sensor_) {
this->min_temperature_probe_number_->publish_state(it[7]); this->min_temperature_probe_number_sensor_->publish_state(it[7]);
} }
break; break;
#endif
case DALY_REQUEST_MOS: case DALY_REQUEST_MOS:
#ifdef USE_TEXT_SENSOR
if (this->status_text_sensor_ != nullptr) { if (this->status_text_sensor_ != nullptr) {
switch (it[4]) { switch (it[4]) {
case 0: case 0:
@ -140,20 +202,27 @@ void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
break; break;
} }
} }
if (this->charging_mos_enabled_) { #endif
this->charging_mos_enabled_->publish_state(it[5]); #ifdef USE_BINARY_SENSOR
if (this->charging_mos_enabled_binary_sensor_) {
this->charging_mos_enabled_binary_sensor_->publish_state(it[5]);
} }
if (this->discharging_mos_enabled_) { if (this->discharging_mos_enabled_binary_sensor_) {
this->discharging_mos_enabled_->publish_state(it[6]); this->discharging_mos_enabled_binary_sensor_->publish_state(it[6]);
} }
if (this->remaining_capacity_) { #endif
this->remaining_capacity_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) / 1000); #ifdef USE_SENSOR
if (this->remaining_capacity_sensor_) {
this->remaining_capacity_sensor_->publish_state((float) encode_uint32(it[8], it[9], it[10], it[11]) /
1000);
} }
#endif
break; break;
#ifdef USE_SENSOR
case DALY_REQUEST_STATUS: case DALY_REQUEST_STATUS:
if (this->cells_number_) { if (this->cells_number_sensor_) {
this->cells_number_->publish_state(it[4]); this->cells_number_sensor_->publish_state(it[4]);
} }
break; break;
@ -171,71 +240,73 @@ void DalyBmsComponent::decode_data_(std::vector<uint8_t> data) {
case DALY_REQUEST_CELL_VOLTAGE: case DALY_REQUEST_CELL_VOLTAGE:
switch (it[4]) { switch (it[4]) {
case 1: case 1:
if (this->cell_1_voltage_) { if (this->cell_1_voltage_sensor_) {
this->cell_1_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_1_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
if (this->cell_2_voltage_) { if (this->cell_2_voltage_sensor_) {
this->cell_2_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->cell_2_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->cell_3_voltage_) { if (this->cell_3_voltage_sensor_) {
this->cell_3_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); this->cell_3_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000);
} }
break; break;
case 2: case 2:
if (this->cell_4_voltage_) { if (this->cell_4_voltage_sensor_) {
this->cell_4_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_4_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
if (this->cell_5_voltage_) { if (this->cell_5_voltage_sensor_) {
this->cell_5_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->cell_5_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->cell_6_voltage_) { if (this->cell_6_voltage_sensor_) {
this->cell_6_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); this->cell_6_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000);
} }
break; break;
case 3: case 3:
if (this->cell_7_voltage_) { if (this->cell_7_voltage_sensor_) {
this->cell_7_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_7_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
if (this->cell_8_voltage_) { if (this->cell_8_voltage_sensor_) {
this->cell_8_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->cell_8_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->cell_9_voltage_) { if (this->cell_9_voltage_sensor_) {
this->cell_9_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); this->cell_9_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000);
} }
break; break;
case 4: case 4:
if (this->cell_10_voltage_) { if (this->cell_10_voltage_sensor_) {
this->cell_10_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_10_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
if (this->cell_11_voltage_) { if (this->cell_11_voltage_sensor_) {
this->cell_11_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->cell_11_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->cell_12_voltage_) { if (this->cell_12_voltage_sensor_) {
this->cell_12_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); this->cell_12_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000);
} }
break; break;
case 5: case 5:
if (this->cell_13_voltage_) { if (this->cell_13_voltage_sensor_) {
this->cell_13_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_13_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
if (this->cell_14_voltage_) { if (this->cell_14_voltage_sensor_) {
this->cell_14_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); this->cell_14_voltage_sensor_->publish_state((float) encode_uint16(it[7], it[8]) / 1000);
} }
if (this->cell_15_voltage_) { if (this->cell_15_voltage_sensor_) {
this->cell_15_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); this->cell_15_voltage_sensor_->publish_state((float) encode_uint16(it[9], it[10]) / 1000);
} }
break; break;
case 6: case 6:
if (this->cell_16_voltage_) { if (this->cell_16_voltage_sensor_) {
this->cell_16_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); this->cell_16_voltage_sensor_->publish_state((float) encode_uint16(it[5], it[6]) / 1000);
} }
break; break;
} }
break; break;
#endif
default: default:
break; break;
} }
} else {
ESP_LOGW(TAG, "Checksum-Error on Packet %x", it[4]);
} }
std::advance(it, DALY_FRAME_SIZE); std::advance(it, DALY_FRAME_SIZE);
} else { } else {

View file

@ -1,9 +1,16 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include <vector> #include <vector>
@ -15,60 +22,53 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
public: public:
DalyBmsComponent() = default; DalyBmsComponent() = default;
// SENSORS #ifdef USE_SENSOR
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } SUB_SENSOR(voltage)
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } SUB_SENSOR(current)
void set_battery_level_sensor(sensor::Sensor *battery_level_sensor) { battery_level_sensor_ = battery_level_sensor; } SUB_SENSOR(battery_level)
void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage) { max_cell_voltage_ = max_cell_voltage; } SUB_SENSOR(max_cell_voltage)
void set_max_cell_voltage_number_sensor(sensor::Sensor *max_cell_voltage_number) { SUB_SENSOR(max_cell_voltage_number)
max_cell_voltage_number_ = max_cell_voltage_number; SUB_SENSOR(min_cell_voltage)
} SUB_SENSOR(min_cell_voltage_number)
void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage) { min_cell_voltage_ = min_cell_voltage; } SUB_SENSOR(max_temperature)
void set_min_cell_voltage_number_sensor(sensor::Sensor *min_cell_voltage_number) { SUB_SENSOR(max_temperature_probe_number)
min_cell_voltage_number_ = min_cell_voltage_number; SUB_SENSOR(min_temperature)
} SUB_SENSOR(min_temperature_probe_number)
void set_max_temperature_sensor(sensor::Sensor *max_temperature) { max_temperature_ = max_temperature; } SUB_SENSOR(remaining_capacity)
void set_max_temperature_probe_number_sensor(sensor::Sensor *max_temperature_probe_number) { SUB_SENSOR(cells_number)
max_temperature_probe_number_ = max_temperature_probe_number; SUB_SENSOR(temperature_1)
} SUB_SENSOR(temperature_2)
void set_min_temperature_sensor(sensor::Sensor *min_temperature) { min_temperature_ = min_temperature; } SUB_SENSOR(cell_1_voltage)
void set_min_temperature_probe_number_sensor(sensor::Sensor *min_temperature_probe_number) { SUB_SENSOR(cell_2_voltage)
min_temperature_probe_number_ = min_temperature_probe_number; SUB_SENSOR(cell_3_voltage)
} SUB_SENSOR(cell_4_voltage)
void set_remaining_capacity_sensor(sensor::Sensor *remaining_capacity) { remaining_capacity_ = remaining_capacity; } SUB_SENSOR(cell_5_voltage)
void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; } SUB_SENSOR(cell_6_voltage)
void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; } SUB_SENSOR(cell_7_voltage)
void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; } SUB_SENSOR(cell_8_voltage)
void set_cell_1_voltage_sensor(sensor::Sensor *cell_1_voltage) { cell_1_voltage_ = cell_1_voltage; } SUB_SENSOR(cell_9_voltage)
void set_cell_2_voltage_sensor(sensor::Sensor *cell_2_voltage) { cell_2_voltage_ = cell_2_voltage; } SUB_SENSOR(cell_10_voltage)
void set_cell_3_voltage_sensor(sensor::Sensor *cell_3_voltage) { cell_3_voltage_ = cell_3_voltage; } SUB_SENSOR(cell_11_voltage)
void set_cell_4_voltage_sensor(sensor::Sensor *cell_4_voltage) { cell_4_voltage_ = cell_4_voltage; } SUB_SENSOR(cell_12_voltage)
void set_cell_5_voltage_sensor(sensor::Sensor *cell_5_voltage) { cell_5_voltage_ = cell_5_voltage; } SUB_SENSOR(cell_13_voltage)
void set_cell_6_voltage_sensor(sensor::Sensor *cell_6_voltage) { cell_6_voltage_ = cell_6_voltage; } SUB_SENSOR(cell_14_voltage)
void set_cell_7_voltage_sensor(sensor::Sensor *cell_7_voltage) { cell_7_voltage_ = cell_7_voltage; } SUB_SENSOR(cell_15_voltage)
void set_cell_8_voltage_sensor(sensor::Sensor *cell_8_voltage) { cell_8_voltage_ = cell_8_voltage; } SUB_SENSOR(cell_16_voltage)
void set_cell_9_voltage_sensor(sensor::Sensor *cell_9_voltage) { cell_9_voltage_ = cell_9_voltage; } #endif
void set_cell_10_voltage_sensor(sensor::Sensor *cell_10_voltage) { cell_10_voltage_ = cell_10_voltage; }
void set_cell_11_voltage_sensor(sensor::Sensor *cell_11_voltage) { cell_11_voltage_ = cell_11_voltage; }
void set_cell_12_voltage_sensor(sensor::Sensor *cell_12_voltage) { cell_12_voltage_ = cell_12_voltage; }
void set_cell_13_voltage_sensor(sensor::Sensor *cell_13_voltage) { cell_13_voltage_ = cell_13_voltage; }
void set_cell_14_voltage_sensor(sensor::Sensor *cell_14_voltage) { cell_14_voltage_ = cell_14_voltage; }
void set_cell_15_voltage_sensor(sensor::Sensor *cell_15_voltage) { cell_15_voltage_ = cell_15_voltage; }
void set_cell_16_voltage_sensor(sensor::Sensor *cell_16_voltage) { cell_16_voltage_ = cell_16_voltage; }
// TEXT_SENSORS #ifdef USE_TEXT_SENSOR
void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; } SUB_TEXT_SENSOR(status)
// BINARY_SENSORS #endif
void set_charging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *charging_mos_enabled) {
charging_mos_enabled_ = charging_mos_enabled; #ifdef USE_BINARY_SENSOR
} SUB_BINARY_SENSOR(charging_mos_enabled)
void set_discharging_mos_enabled_binary_sensor(binary_sensor::BinarySensor *discharging_mos_enabled) { SUB_BINARY_SENSOR(discharging_mos_enabled)
discharging_mos_enabled_ = discharging_mos_enabled; #endif
}
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void update() override; void update() override;
void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_address(uint8_t address) { this->addr_ = address; } void set_address(uint8_t address) { this->addr_ = address; }
@ -79,42 +79,12 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice {
uint8_t addr_; uint8_t addr_;
sensor::Sensor *voltage_sensor_{nullptr}; std::vector<uint8_t> data_;
sensor::Sensor *current_sensor_{nullptr}; bool receiving_{false};
sensor::Sensor *battery_level_sensor_{nullptr}; uint8_t data_count_;
sensor::Sensor *max_cell_voltage_{nullptr}; uint32_t last_transmission_{0};
sensor::Sensor *max_cell_voltage_number_{nullptr}; bool trigger_next_;
sensor::Sensor *min_cell_voltage_{nullptr}; uint8_t next_request_;
sensor::Sensor *min_cell_voltage_number_{nullptr};
sensor::Sensor *max_temperature_{nullptr};
sensor::Sensor *max_temperature_probe_number_{nullptr};
sensor::Sensor *min_temperature_{nullptr};
sensor::Sensor *min_temperature_probe_number_{nullptr};
sensor::Sensor *remaining_capacity_{nullptr};
sensor::Sensor *cells_number_{nullptr};
sensor::Sensor *temperature_1_sensor_{nullptr};
sensor::Sensor *temperature_2_sensor_{nullptr};
sensor::Sensor *cell_1_voltage_{nullptr};
sensor::Sensor *cell_2_voltage_{nullptr};
sensor::Sensor *cell_3_voltage_{nullptr};
sensor::Sensor *cell_4_voltage_{nullptr};
sensor::Sensor *cell_5_voltage_{nullptr};
sensor::Sensor *cell_6_voltage_{nullptr};
sensor::Sensor *cell_7_voltage_{nullptr};
sensor::Sensor *cell_8_voltage_{nullptr};
sensor::Sensor *cell_9_voltage_{nullptr};
sensor::Sensor *cell_10_voltage_{nullptr};
sensor::Sensor *cell_11_voltage_{nullptr};
sensor::Sensor *cell_12_voltage_{nullptr};
sensor::Sensor *cell_13_voltage_{nullptr};
sensor::Sensor *cell_14_voltage_{nullptr};
sensor::Sensor *cell_15_voltage_{nullptr};
sensor::Sensor *cell_16_voltage_{nullptr};
text_sensor::TextSensor *status_text_sensor_{nullptr};
binary_sensor::BinarySensor *charging_mos_enabled_{nullptr};
binary_sensor::BinarySensor *discharging_mos_enabled_{nullptr};
}; };
} // namespace daly_bms } // namespace daly_bms

View file

@ -218,9 +218,8 @@ CONFIG_SCHEMA = cv.All(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await sensor.new_sensor(sensor_config)
sens = await sensor.new_sensor(conf)
cg.add(getattr(hub, f"set_{key}_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_sensor")(sens))

View file

@ -23,9 +23,8 @@ CONFIG_SCHEMA = cv.All(
async def setup_conf(config, key, hub): async def setup_conf(config, key, hub):
if key in config: if sensor_config := config.get(key):
conf = config[key] sens = await text_sensor.new_text_sensor(sensor_config)
sens = await text_sensor.new_text_sensor(conf)
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))

View file

@ -269,10 +269,7 @@ void Display::do_update_() {
} else if (this->writer_.has_value()) { } else if (this->writer_.has_value()) {
(*this->writer_)(*this); (*this->writer_)(*this);
} }
// remove all not ended clipping regions this->clear_clipping_();
while (is_clipping()) {
end_clipping();
}
} }
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
@ -322,13 +319,51 @@ void Display::shrink_clipping(Rect add_rect) {
this->clipping_rectangle_.back().shrink(add_rect); this->clipping_rectangle_.back().shrink(add_rect);
} }
} }
Rect Display::get_clipping() { Rect Display::get_clipping() const {
if (this->clipping_rectangle_.empty()) { if (this->clipping_rectangle_.empty()) {
return Rect(); return Rect();
} else { } else {
return this->clipping_rectangle_.back(); return this->clipping_rectangle_.back();
} }
} }
void Display::clear_clipping_() { this->clipping_rectangle_.clear(); }
bool Display::clip(int x, int y) {
if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
return false;
if (!this->get_clipping().inside(x, y))
return false;
return true;
}
bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
min_x = std::max(x, 0);
max_x = std::min(x + w, this->get_width());
if (!this->clipping_rectangle_.empty()) {
const auto &rect = this->clipping_rectangle_.back();
if (!rect.is_set())
return false;
min_x = std::max(min_x, (int) rect.x);
max_x = std::min(max_x, (int) rect.x2());
}
return min_x < max_x;
}
bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
min_y = std::max(y, 0);
max_y = std::min(y + h, this->get_height());
if (!this->clipping_rectangle_.empty()) {
const auto &rect = this->clipping_rectangle_.back();
if (!rect.is_set())
return false;
min_y = std::max(min_y, (int) rect.y);
max_y = std::min(max_y, (int) rect.y2());
}
return min_y < max_y;
}
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
void DisplayPage::show() { this->parent_->show_page(this); } void DisplayPage::show() { this->parent_->show_page(this); }

View file

@ -472,14 +472,21 @@ class Display {
* *
* return rect for active clipping region * return rect for active clipping region
*/ */
Rect get_clipping(); Rect get_clipping() const;
bool is_clipping() const { return !this->clipping_rectangle_.empty(); } bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
/** Check if pixel is within region of display.
*/
bool clip(int x, int y);
protected: protected:
bool clamp_x_(int x, int w, int &min_x, int &max_x);
bool clamp_y_(int y, int h, int &min_y, int &max_y);
void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg); void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
void do_update_(); void do_update_();
void clear_clipping_();
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
optional<display_writer_t> writer_{}; optional<display_writer_t> writer_{};

View file

@ -60,11 +60,11 @@ void Rect::shrink(Rect rect) {
} }
} }
bool Rect::equal(Rect rect) { bool Rect::equal(Rect rect) const {
return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h); return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
} }
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const { // NOLINT
if (!this->is_set()) { if (!this->is_set()) {
return true; return true;
} }
@ -75,7 +75,7 @@ bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT
} }
} }
bool Rect::inside(Rect rect, bool absolute) { bool Rect::inside(Rect rect, bool absolute) const {
if (!this->is_set() || !rect.is_set()) { if (!this->is_set() || !rect.is_set()) {
return true; return true;
} }

View file

@ -16,19 +16,19 @@ class Rect {
Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT
inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {} inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner inline int16_t x2() const { return this->x + this->w; }; ///< X coordinate of corner
inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner inline int16_t y2() const { return this->y + this->h; }; ///< Y coordinate of corner
inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); } inline bool is_set() const ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
void expand(int16_t horizontal, int16_t vertical); void expand(int16_t horizontal, int16_t vertical);
void extend(Rect rect); void extend(Rect rect);
void shrink(Rect rect); void shrink(Rect rect);
bool inside(Rect rect, bool absolute = true); bool inside(Rect rect, bool absolute = true) const;
bool inside(int16_t test_x, int16_t test_y, bool absolute = true); bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
bool equal(Rect rect); bool equal(Rect rect) const;
void info(const std::string &prefix = "rect info:"); void info(const std::string &prefix = "rect info:");
}; };

View file

@ -6,9 +6,11 @@ namespace duty_time_sensor {
static const char *const TAG = "duty_time_sensor"; static const char *const TAG = "duty_time_sensor";
#ifdef USE_BINARY_SENSOR
void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) { void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) {
sensor->add_on_state_callback([this](bool state) { this->process_state_(state); }); sensor->add_on_state_callback([this](bool state) { this->process_state_(state); });
} }
#endif
void DutyTimeSensor::start() { void DutyTimeSensor::start() {
if (!this->last_state_) if (!this->last_state_)

View file

@ -3,8 +3,10 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
namespace esphome { namespace esphome {
namespace duty_time_sensor { namespace duty_time_sensor {
@ -22,8 +24,10 @@ class DutyTimeSensor : public sensor::Sensor, public PollingComponent {
bool is_running() const { return this->last_state_; } bool is_running() const { return this->last_state_; }
void reset() { this->set_value_(0); } void reset() { this->set_value_(0); }
void set_lambda(std::function<bool()> &&func) { this->func_ = func; } #ifdef USE_BINARY_SENSOR
void set_sensor(binary_sensor::BinarySensor *sensor); void set_sensor(binary_sensor::BinarySensor *sensor);
#endif
void set_lambda(std::function<bool()> &&func) { this->func_ = func; }
void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; } void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; }
void set_restore(bool restore) { this->restore_ = restore; } void set_restore(bool restore) { this->restore_ = restore; }
@ -43,44 +47,26 @@ class DutyTimeSensor : public sensor::Sensor, public PollingComponent {
bool restore_; bool restore_;
}; };
template<typename... Ts> class StartAction : public Action<Ts...> { template<typename... Ts> class BaseAction : public Action<Ts...>, public Parented<DutyTimeSensor> {};
public:
explicit StartAction(DutyTimeSensor *parent) : parent_(parent) {}
template<typename... Ts> class StartAction : public BaseAction<Ts...> {
void play(Ts... x) override { this->parent_->start(); } void play(Ts... x) override { this->parent_->start(); }
protected:
DutyTimeSensor *parent_;
}; };
template<typename... Ts> class StopAction : public Action<Ts...> { template<typename... Ts> class StopAction : public BaseAction<Ts...> {
public:
explicit StopAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->stop(); } void play(Ts... x) override { this->parent_->stop(); }
protected:
DutyTimeSensor *parent_;
}; };
template<typename... Ts> class ResetAction : public Action<Ts...> { template<typename... Ts> class ResetAction : public BaseAction<Ts...> {
public:
explicit ResetAction(DutyTimeSensor *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->reset(); } void play(Ts... x) override { this->parent_->reset(); }
protected:
DutyTimeSensor *parent_;
}; };
template<typename... Ts> class RunningCondition : public Condition<Ts...> { template<typename... Ts> class RunningCondition : public Condition<Ts...>, public Parented<DutyTimeSensor> {
public: public:
explicit RunningCondition(DutyTimeSensor *parent, bool state) : parent_(parent), state_(state) {} explicit RunningCondition(DutyTimeSensor *parent, bool state) : Parented(parent), state_(state) {}
bool check(Ts... x) override { return this->parent_->is_running() == this->state_; }
protected: protected:
DutyTimeSensor *parent_; bool check(Ts... x) override { return this->parent_->is_running() == this->state_; }
bool state_; bool state_;
}; };

View file

@ -26,11 +26,14 @@ duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor")
DutyTimeSensor = duty_time_sensor_ns.class_( DutyTimeSensor = duty_time_sensor_ns.class_(
"DutyTimeSensor", sensor.Sensor, cg.PollingComponent "DutyTimeSensor", sensor.Sensor, cg.PollingComponent
) )
StartAction = duty_time_sensor_ns.class_("StartAction", Action) BaseAction = duty_time_sensor_ns.class_("BaseAction", Action, cg.Parented)
StopAction = duty_time_sensor_ns.class_("StopAction", Action) StartAction = duty_time_sensor_ns.class_("StartAction", BaseAction)
ResetAction = duty_time_sensor_ns.class_("ResetAction", Action) StopAction = duty_time_sensor_ns.class_("StopAction", BaseAction)
SetAction = duty_time_sensor_ns.class_("SetAction", Action) ResetAction = duty_time_sensor_ns.class_("ResetAction", BaseAction)
RunningCondition = duty_time_sensor_ns.class_("RunningCondition", Condition) SetAction = duty_time_sensor_ns.class_("SetAction", BaseAction)
RunningCondition = duty_time_sensor_ns.class_(
"RunningCondition", Condition, cg.Parented
)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
@ -89,20 +92,23 @@ DUTY_TIME_ID_SCHEMA = maybe_simple_id(
@register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA) @register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_start_to_code(config, action_id, template_arg, args): async def sensor_runtime_start_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg)
return cg.new_Pvariable(action_id, template_arg, paren) await cg.register_parented(var, config[CONF_ID])
return var
@register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA) @register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_stop_to_code(config, action_id, template_arg, args): async def sensor_runtime_stop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg)
return cg.new_Pvariable(action_id, template_arg, paren) await cg.register_parented(var, config[CONF_ID])
return var
@register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA) @register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA)
async def sensor_runtime_reset_to_code(config, action_id, template_arg, args): async def sensor_runtime_reset_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID]) var = cg.new_Pvariable(action_id, template_arg)
return cg.new_Pvariable(action_id, template_arg, paren) await cg.register_parented(var, config[CONF_ID])
return var
@register_condition( @register_condition(

View file

@ -51,7 +51,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
if (universe < first_universe_ || universe > get_last_universe()) if (universe < first_universe_ || universe > get_last_universe())
return false; return false;
int output_offset = (universe - first_universe_) * get_lights_per_universe(); int32_t output_offset = (universe - first_universe_) * get_lights_per_universe();
// limit amount of lights per universe and received // limit amount of lights per universe and received
int output_end = int output_end =
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union from typing import Union, Optional
from pathlib import Path from pathlib import Path
import logging import logging
import os import os
@ -42,6 +42,7 @@ from .const import ( # noqa
KEY_REFRESH, KEY_REFRESH,
KEY_REPO, KEY_REPO,
KEY_SDKCONFIG_OPTIONS, KEY_SDKCONFIG_OPTIONS,
KEY_SUBMODULES,
KEY_VARIANT, KEY_VARIANT,
VARIANT_ESP32C3, VARIANT_ESP32C3,
VARIANT_FRIENDLY, VARIANT_FRIENDLY,
@ -80,6 +81,10 @@ def get_esp32_variant(core_obj=None):
return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT] return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT]
def get_board(core_obj=None):
return (core_obj or CORE).data[KEY_ESP32][KEY_BOARD]
def only_on_variant(*, supported=None, unsupported=None): def only_on_variant(*, supported=None, unsupported=None):
"""Config validator for features only available on some ESP32 variants.""" """Config validator for features only available on some ESP32 variants."""
if supported is not None and not isinstance(supported, list): if supported is not None and not isinstance(supported, list):
@ -120,17 +125,28 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component( def add_idf_component(
name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None *,
name: str,
repo: str,
ref: str = None,
path: str = None,
refresh: TimePeriod = None,
components: Optional[list[str]] = None,
submodules: Optional[list[str]] = None,
): ):
"""Add an esp-idf component to the project.""" """Add an esp-idf component to the project."""
if not CORE.using_esp_idf: if not CORE.using_esp_idf:
raise ValueError("Not an esp-idf project") raise ValueError("Not an esp-idf project")
if components is None:
components = []
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]: if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
KEY_REPO: repo, KEY_REPO: repo,
KEY_REF: ref, KEY_REF: ref,
KEY_PATH: path, KEY_PATH: path,
KEY_REFRESH: refresh, KEY_REFRESH: refresh,
KEY_COMPONENTS: components,
KEY_SUBMODULES: submodules,
} }
@ -163,23 +179,23 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5)
# The platformio/espressif32 version to use for arduino frameworks # The platformio/espressif32 version to use for arduino frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0) ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version # The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases # - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4) RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 5)
# The platformio/espressif32 version to use for esp-idf frameworks # The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases # - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0) ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0)
def _arduino_check_versions(value): def _arduino_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"), "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"),
"latest": (cv.Version(2, 0, 7), None), "latest": (cv.Version(2, 0, 9), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
} }
@ -214,7 +230,7 @@ def _esp_idf_check_versions(value):
value = value.copy() value = value.copy()
lookups = { lookups = {
"dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"), "dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 0, 1), None), "latest": (cv.Version(5, 1, 0), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
} }
@ -536,20 +552,41 @@ def copy_files():
ref=component[KEY_REF], ref=component[KEY_REF],
refresh=component[KEY_REFRESH], refresh=component[KEY_REFRESH],
domain="idf_components", domain="idf_components",
submodules=component[KEY_SUBMODULES],
) )
mkdir_p(CORE.relative_build_path("components")) mkdir_p(CORE.relative_build_path("components"))
component_dir = repo_dir component_dir = repo_dir
if component[KEY_PATH] is not None: if component[KEY_PATH] is not None:
component_dir = component_dir / component[KEY_PATH] component_dir = component_dir / component[KEY_PATH]
shutil.copytree( if component[KEY_COMPONENTS] == ["*"]:
component_dir, shutil.copytree(
CORE.relative_build_path(f"components/{name}"), component_dir,
dirs_exist_ok=True, CORE.relative_build_path("components"),
ignore=shutil.ignore_patterns(".git", ".github"), dirs_exist_ok=True,
symlinks=True, ignore=shutil.ignore_patterns(".git*"),
ignore_dangling_symlinks=True, symlinks=True,
) ignore_dangling_symlinks=True,
)
elif len(component[KEY_COMPONENTS]) > 0:
for comp in component[KEY_COMPONENTS]:
shutil.copytree(
component_dir / comp,
CORE.relative_build_path(f"components/{comp}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
else:
shutil.copytree(
component_dir,
CORE.relative_build_path(f"components/{name}"),
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git*"),
symlinks=True,
ignore_dangling_symlinks=True,
)
dir = os.path.dirname(__file__) dir = os.path.dirname(__file__)
post_build_file = os.path.join(dir, "post_build.py.script") post_build_file = os.path.join(dir, "post_build.py.script")

View file

@ -1201,6 +1201,10 @@ BOARDS = {
"name": "BPI-Bit", "name": "BPI-Bit",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"bpi_leaf_s3": {
"name": "BPI-Leaf-S3",
"variant": VARIANT_ESP32S3,
},
"briki_abc_esp32": { "briki_abc_esp32": {
"name": "Briki ABC (MBC-WB) - ESP32", "name": "Briki ABC (MBC-WB) - ESP32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1217,6 +1221,10 @@ BOARDS = {
"name": "Connaxio's Espoir", "name": "Connaxio's Espoir",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"cytron_maker_feather_aiot_s3": {
"name": "Cytron Maker Feather AIoT S3",
"variant": VARIANT_ESP32S3,
},
"d-duino-32": { "d-duino-32": {
"name": "D-duino-32", "name": "D-duino-32",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1225,6 +1233,10 @@ BOARDS = {
"name": "Deneyap Kart 1A", "name": "Deneyap Kart 1A",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"deneyapkart1Av2": {
"name": "Deneyap Kart 1A v2",
"variant": VARIANT_ESP32S3,
},
"deneyapkartg": { "deneyapkartg": {
"name": "Deneyap Kart G", "name": "Deneyap Kart G",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
@ -1237,6 +1249,10 @@ BOARDS = {
"name": "Deneyap Mini", "name": "Deneyap Mini",
"variant": VARIANT_ESP32S2, "variant": VARIANT_ESP32S2,
}, },
"deneyapminiv2": {
"name": "Deneyap Mini v2",
"variant": VARIANT_ESP32S2,
},
"denky32": { "denky32": {
"name": "Denky32 (WROOM32)", "name": "Denky32 (WROOM32)",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1265,6 +1281,10 @@ BOARDS = {
"name": "Espressif ESP32-C3-DevKitM-1", "name": "Espressif ESP32-C3-DevKitM-1",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,
}, },
"esp32-c3-m1i-kit": {
"name": "Ai-Thinker ESP-C3-M1-I-Kit",
"variant": VARIANT_ESP32C3,
},
"esp32cam": { "esp32cam": {
"name": "AI Thinker ESP32-CAM", "name": "AI Thinker ESP32-CAM",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1329,6 +1349,10 @@ BOARDS = {
"name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)", "name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
"variant": VARIANT_ESP32S3, "variant": VARIANT_ESP32S3,
}, },
"esp32-s3-korvo-2": {
"name": "Espressif ESP32-S3-Korvo-2",
"variant": VARIANT_ESP32S3,
},
"esp32thing": { "esp32thing": {
"name": "SparkFun ESP32 Thing", "name": "SparkFun ESP32 Thing",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
@ -1637,6 +1661,10 @@ BOARDS = {
"name": "Noduino Quantum", "name": "Noduino Quantum",
"variant": VARIANT_ESP32, "variant": VARIANT_ESP32,
}, },
"redpill_esp32s3": {
"name": "Munich Labs RedPill ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"seeed_xiao_esp32c3": { "seeed_xiao_esp32c3": {
"name": "Seeed Studio XIAO ESP32C3", "name": "Seeed Studio XIAO ESP32C3",
"variant": VARIANT_ESP32C3, "variant": VARIANT_ESP32C3,

View file

@ -9,6 +9,7 @@ KEY_REPO = "repo"
KEY_REF = "ref" KEY_REF = "ref"
KEY_REFRESH = "refresh" KEY_REFRESH = "refresh"
KEY_PATH = "path" KEY_PATH = "path"
KEY_SUBMODULES = "submodules"
VARIANT_ESP32 = "ESP32" VARIANT_ESP32 = "ESP32"
VARIANT_ESP32S2 = "ESP32S2" VARIANT_ESP32S2 = "ESP32S2"

View file

@ -4,6 +4,7 @@
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <cinttypes>
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -166,7 +167,7 @@ std::string ESPBTUUID::to_string() const {
case ESP_UUID_LEN_16: case ESP_UUID_LEN_16:
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff); return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
case ESP_UUID_LEN_32: case ESP_UUID_LEN_32:
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24, return str_snprintf("0x%02" PRIX32 "%02" PRIX32 "%02" PRIX32 "%02" PRIX32, 10, (this->uuid_.uuid.uuid32 >> 24),
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff), (this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
this->uuid_.uuid.uuid32 & 0xff); this->uuid_.uuid.uuid32 & 0xff);
default: default:

View file

@ -72,3 +72,4 @@ async def to_code(config):
if CORE.using_esp_idf: if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)

View file

@ -15,6 +15,7 @@
#include <freertos/FreeRTOSConfig.h> #include <freertos/FreeRTOSConfig.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <nvs_flash.h> #include <nvs_flash.h>
#include <cinttypes>
#ifdef USE_OTA #ifdef USE_OTA
#include "esphome/components/ota/ota_component.h" #include "esphome/components/ota/ota_component.h"
@ -614,7 +615,7 @@ uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uin
void ESP32BLETracker::dump_config() { void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Tracker:"); ESP_LOGCONFIG(TAG, "BLE Tracker:");
ESP_LOGCONFIG(TAG, " Scan Duration: %u s", this->scan_duration_); ESP_LOGCONFIG(TAG, " Scan Duration: %" PRIu32 " s", this->scan_duration_);
ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f); ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE"); ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");

View file

@ -12,25 +12,112 @@ from esphome.const import (
) )
from esphome.core import TimePeriod from esphome.core import TimePeriod
from esphome.components import esp32 from esphome.components import esp32
from esphome.components.esp32 import get_esp32_variant, gpio
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
AUTO_LOAD = ["binary_sensor"] AUTO_LOAD = ["binary_sensor"]
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
CONF_DEBOUNCE_COUNT = "debounce_count"
CONF_DENOISE_GRADE = "denoise_grade"
CONF_DENOISE_CAP_LEVEL = "denoise_cap_level"
CONF_FILTER_MODE = "filter_mode"
CONF_NOISE_THRESHOLD = "noise_threshold"
CONF_JITTER_STEP = "jitter_step"
CONF_SMOOTH_MODE = "smooth_mode"
CONF_WATERPROOF_GUARD_RING = "waterproof_guard_ring"
CONF_WATERPROOF_SHIELD_DRIVER = "waterproof_shield_driver"
esp32_touch_ns = cg.esphome_ns.namespace("esp32_touch") esp32_touch_ns = cg.esphome_ns.namespace("esp32_touch")
ESP32TouchComponent = esp32_touch_ns.class_("ESP32TouchComponent", cg.Component) ESP32TouchComponent = esp32_touch_ns.class_("ESP32TouchComponent", cg.Component)
TOUCH_PADS = {
VARIANT_ESP32: {
4: cg.global_ns.TOUCH_PAD_NUM0,
0: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
15: cg.global_ns.TOUCH_PAD_NUM3,
13: cg.global_ns.TOUCH_PAD_NUM4,
12: cg.global_ns.TOUCH_PAD_NUM5,
14: cg.global_ns.TOUCH_PAD_NUM6,
27: cg.global_ns.TOUCH_PAD_NUM7,
33: cg.global_ns.TOUCH_PAD_NUM8,
32: cg.global_ns.TOUCH_PAD_NUM9,
},
VARIANT_ESP32S2: {
1: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
3: cg.global_ns.TOUCH_PAD_NUM3,
4: cg.global_ns.TOUCH_PAD_NUM4,
5: cg.global_ns.TOUCH_PAD_NUM5,
6: cg.global_ns.TOUCH_PAD_NUM6,
7: cg.global_ns.TOUCH_PAD_NUM7,
8: cg.global_ns.TOUCH_PAD_NUM8,
9: cg.global_ns.TOUCH_PAD_NUM9,
10: cg.global_ns.TOUCH_PAD_NUM10,
11: cg.global_ns.TOUCH_PAD_NUM11,
12: cg.global_ns.TOUCH_PAD_NUM12,
13: cg.global_ns.TOUCH_PAD_NUM13,
14: cg.global_ns.TOUCH_PAD_NUM14,
},
VARIANT_ESP32S3: {
1: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
3: cg.global_ns.TOUCH_PAD_NUM3,
4: cg.global_ns.TOUCH_PAD_NUM4,
5: cg.global_ns.TOUCH_PAD_NUM5,
6: cg.global_ns.TOUCH_PAD_NUM6,
7: cg.global_ns.TOUCH_PAD_NUM7,
8: cg.global_ns.TOUCH_PAD_NUM8,
9: cg.global_ns.TOUCH_PAD_NUM9,
10: cg.global_ns.TOUCH_PAD_NUM10,
11: cg.global_ns.TOUCH_PAD_NUM11,
12: cg.global_ns.TOUCH_PAD_NUM12,
13: cg.global_ns.TOUCH_PAD_NUM13,
14: cg.global_ns.TOUCH_PAD_NUM14,
},
}
def validate_voltage(values):
def validator(value):
if isinstance(value, float) and value.is_integer():
value = int(value)
value = cv.string(value)
if not value.endswith("V"):
value += "V"
return cv.one_of(*values)(value)
return validator TOUCH_PAD_DENOISE_GRADE = {
"BIT12": cg.global_ns.TOUCH_PAD_DENOISE_BIT12,
"BIT10": cg.global_ns.TOUCH_PAD_DENOISE_BIT10,
"BIT8": cg.global_ns.TOUCH_PAD_DENOISE_BIT8,
"BIT4": cg.global_ns.TOUCH_PAD_DENOISE_BIT4,
}
TOUCH_PAD_DENOISE_CAP_LEVEL = {
"L0": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L0,
"L1": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L1,
"L2": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L2,
"L3": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L3,
"L4": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L4,
"L5": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L5,
"L6": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L6,
"L7": cg.global_ns.TOUCH_PAD_DENOISE_CAP_L7,
}
TOUCH_PAD_FILTER_MODE = {
"IIR_4": cg.global_ns.TOUCH_PAD_FILTER_IIR_4,
"IIR_8": cg.global_ns.TOUCH_PAD_FILTER_IIR_8,
"IIR_16": cg.global_ns.TOUCH_PAD_FILTER_IIR_16,
"IIR_32": cg.global_ns.TOUCH_PAD_FILTER_IIR_32,
"IIR_64": cg.global_ns.TOUCH_PAD_FILTER_IIR_64,
"IIR_128": cg.global_ns.TOUCH_PAD_FILTER_IIR_128,
"IIR_256": cg.global_ns.TOUCH_PAD_FILTER_IIR_256,
"JITTER": cg.global_ns.TOUCH_PAD_FILTER_JITTER,
}
TOUCH_PAD_SMOOTH_MODE = {
"OFF": cg.global_ns.TOUCH_PAD_SMOOTH_OFF,
"IIR_2": cg.global_ns.TOUCH_PAD_SMOOTH_IIR_2,
"IIR_4": cg.global_ns.TOUCH_PAD_SMOOTH_IIR_4,
"IIR_8": cg.global_ns.TOUCH_PAD_SMOOTH_IIR_8,
}
LOW_VOLTAGE_REFERENCE = { LOW_VOLTAGE_REFERENCE = {
"0.5V": cg.global_ns.TOUCH_LVOLT_0V5, "0.5V": cg.global_ns.TOUCH_LVOLT_0V5,
@ -50,15 +137,74 @@ VOLTAGE_ATTENUATION = {
"0.5V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V5, "0.5V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V5,
"0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V, "0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V,
} }
TOUCH_PAD_WATERPROOF_SHIELD_DRIVER = {
"L0": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L0,
"L1": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L1,
"L2": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L2,
"L3": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L3,
"L4": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L4,
"L5": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L5,
"L6": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L6,
"L7": cg.global_ns.TOUCH_PAD_SHIELD_DRV_L7,
}
def validate_touch_pad(value):
value = gpio.validate_gpio_pin(value)
variant = get_esp32_variant()
if variant not in TOUCH_PADS:
raise cv.Invalid(f"ESP32 variant {variant} does not support touch pads.")
pads = TOUCH_PADS[variant]
if value not in pads:
raise cv.Invalid(f"Pin {value} does not support touch pads.")
return cv.enum(pads)(value)
def validate_variant_vars(config):
if get_esp32_variant() == VARIANT_ESP32:
variant_vars = {
CONF_DEBOUNCE_COUNT,
CONF_DENOISE_GRADE,
CONF_DENOISE_CAP_LEVEL,
CONF_FILTER_MODE,
CONF_NOISE_THRESHOLD,
CONF_JITTER_STEP,
CONF_SMOOTH_MODE,
CONF_WATERPROOF_GUARD_RING,
CONF_WATERPROOF_SHIELD_DRIVER,
}
for vvar in variant_vars:
if vvar in config:
raise cv.Invalid(f"{vvar} is not valid on {VARIANT_ESP32}")
elif (
get_esp32_variant() == VARIANT_ESP32S2 or get_esp32_variant() == VARIANT_ESP32S3
) and CONF_IIR_FILTER in config:
raise cv.Invalid(
f"{CONF_IIR_FILTER} is not valid on {VARIANT_ESP32S2} or {VARIANT_ESP32S3}"
)
return config
def validate_voltage(values):
def validator(value):
if isinstance(value, float) and value.is_integer():
value = int(value)
value = cv.string(value)
if not value.endswith("V"):
value += "V"
return cv.one_of(*values)(value)
return validator
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(ESP32TouchComponent), cv.GenerateID(): cv.declare_id(ESP32TouchComponent),
cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean,
cv.Optional( # common options
CONF_IIR_FILTER, default="0ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_SLEEP_DURATION, default="27306us"): cv.All( cv.Optional(CONF_SLEEP_DURATION, default="27306us"): cv.All(
cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906)) cv.positive_time_period, cv.Range(max=TimePeriod(microseconds=436906))
), ),
@ -74,13 +220,47 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_VOLTAGE_ATTENUATION, default="0V"): validate_voltage( cv.Optional(CONF_VOLTAGE_ATTENUATION, default="0V"): validate_voltage(
VOLTAGE_ATTENUATION VOLTAGE_ATTENUATION
), ),
# ESP32 only
cv.Optional(CONF_IIR_FILTER): cv.positive_time_period_milliseconds,
# ESP32-S2/S3 only
cv.Optional(CONF_DEBOUNCE_COUNT): cv.int_range(min=0, max=7),
cv.Optional(CONF_FILTER_MODE): cv.enum(
TOUCH_PAD_FILTER_MODE, upper=True, space="_"
),
cv.Optional(CONF_NOISE_THRESHOLD): cv.int_range(min=0, max=3),
cv.Optional(CONF_JITTER_STEP): cv.int_range(min=0, max=15),
cv.Optional(CONF_SMOOTH_MODE): cv.enum(
TOUCH_PAD_SMOOTH_MODE, upper=True, space="_"
),
cv.Optional(CONF_DENOISE_GRADE): cv.enum(
TOUCH_PAD_DENOISE_GRADE, upper=True, space="_"
),
cv.Optional(CONF_DENOISE_CAP_LEVEL): cv.enum(
TOUCH_PAD_DENOISE_CAP_LEVEL, upper=True, space="_"
),
cv.Optional(CONF_WATERPROOF_GUARD_RING): validate_touch_pad,
cv.Optional(CONF_WATERPROOF_SHIELD_DRIVER): cv.enum(
TOUCH_PAD_WATERPROOF_SHIELD_DRIVER, upper=True, space="_"
),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.has_none_or_all_keys(CONF_DENOISE_GRADE, CONF_DENOISE_CAP_LEVEL),
cv.has_none_or_all_keys(
CONF_DEBOUNCE_COUNT,
CONF_FILTER_MODE,
CONF_NOISE_THRESHOLD,
CONF_JITTER_STEP,
CONF_SMOOTH_MODE,
),
cv.has_none_or_all_keys(CONF_WATERPROOF_GUARD_RING, CONF_WATERPROOF_SHIELD_DRIVER),
esp32.only_on_variant( esp32.only_on_variant(
supported=[ supported=[
esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32,
esp32.const.VARIANT_ESP32S2,
esp32.const.VARIANT_ESP32S3,
] ]
), ),
validate_variant_vars,
) )
@ -89,7 +269,6 @@ async def to_code(config):
await cg.register_component(touch, config) await cg.register_component(touch, config)
cg.add(touch.set_setup_mode(config[CONF_SETUP_MODE])) cg.add(touch.set_setup_mode(config[CONF_SETUP_MODE]))
cg.add(touch.set_iir_filter(config[CONF_IIR_FILTER]))
sleep_duration = int(round(config[CONF_SLEEP_DURATION].total_microseconds * 0.15)) sleep_duration = int(round(config[CONF_SLEEP_DURATION].total_microseconds * 0.15))
cg.add(touch.set_sleep_duration(sleep_duration)) cg.add(touch.set_sleep_duration(sleep_duration))
@ -114,3 +293,33 @@ async def to_code(config):
VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]] VOLTAGE_ATTENUATION[config[CONF_VOLTAGE_ATTENUATION]]
) )
) )
if get_esp32_variant() == VARIANT_ESP32:
if CONF_IIR_FILTER in config:
cg.add(touch.set_iir_filter(config[CONF_IIR_FILTER]))
if get_esp32_variant() == VARIANT_ESP32S2 or get_esp32_variant() == VARIANT_ESP32S3:
if CONF_FILTER_MODE in config:
cg.add(touch.set_filter_mode(config[CONF_FILTER_MODE]))
if CONF_DEBOUNCE_COUNT in config:
cg.add(touch.set_debounce_count(config[CONF_DEBOUNCE_COUNT]))
if CONF_NOISE_THRESHOLD in config:
cg.add(touch.set_noise_threshold(config[CONF_NOISE_THRESHOLD]))
if CONF_JITTER_STEP in config:
cg.add(touch.set_jitter_step(config[CONF_JITTER_STEP]))
if CONF_SMOOTH_MODE in config:
cg.add(touch.set_smooth_level(config[CONF_SMOOTH_MODE]))
if CONF_DENOISE_GRADE in config:
cg.add(touch.set_denoise_grade(config[CONF_DENOISE_GRADE]))
if CONF_DENOISE_CAP_LEVEL in config:
cg.add(touch.set_denoise_cap(config[CONF_DENOISE_CAP_LEVEL]))
if CONF_WATERPROOF_GUARD_RING in config:
cg.add(
touch.set_waterproof_guard_ring_pad(config[CONF_WATERPROOF_GUARD_RING])
)
if CONF_WATERPROOF_SHIELD_DRIVER in config:
cg.add(
touch.set_waterproof_shield_driver(
config[CONF_WATERPROOF_SHIELD_DRIVER]
)
)

View file

@ -1,87 +1,18 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.core import CORE
from esphome.components import binary_sensor from esphome.components import binary_sensor
from esphome.const import ( from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_THRESHOLD, CONF_THRESHOLD,
CONF_ID, CONF_ID,
) )
from esphome.components.esp32 import gpio from . import esp32_touch_ns, ESP32TouchComponent, validate_touch_pad
from esphome.components.esp32.const import (
KEY_ESP32,
KEY_VARIANT,
VARIANT_ESP32,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
from . import esp32_touch_ns, ESP32TouchComponent
DEPENDENCIES = ["esp32_touch", "esp32"] DEPENDENCIES = ["esp32_touch", "esp32"]
CONF_ESP32_TOUCH_ID = "esp32_touch_id" CONF_ESP32_TOUCH_ID = "esp32_touch_id"
CONF_WAKEUP_THRESHOLD = "wakeup_threshold" CONF_WAKEUP_THRESHOLD = "wakeup_threshold"
TOUCH_PADS = {
VARIANT_ESP32: {
4: cg.global_ns.TOUCH_PAD_NUM0,
0: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
15: cg.global_ns.TOUCH_PAD_NUM3,
13: cg.global_ns.TOUCH_PAD_NUM4,
12: cg.global_ns.TOUCH_PAD_NUM5,
14: cg.global_ns.TOUCH_PAD_NUM6,
27: cg.global_ns.TOUCH_PAD_NUM7,
33: cg.global_ns.TOUCH_PAD_NUM8,
32: cg.global_ns.TOUCH_PAD_NUM9,
},
VARIANT_ESP32S2: {
1: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
3: cg.global_ns.TOUCH_PAD_NUM3,
4: cg.global_ns.TOUCH_PAD_NUM4,
5: cg.global_ns.TOUCH_PAD_NUM5,
6: cg.global_ns.TOUCH_PAD_NUM6,
7: cg.global_ns.TOUCH_PAD_NUM7,
8: cg.global_ns.TOUCH_PAD_NUM8,
9: cg.global_ns.TOUCH_PAD_NUM9,
10: cg.global_ns.TOUCH_PAD_NUM10,
11: cg.global_ns.TOUCH_PAD_NUM11,
12: cg.global_ns.TOUCH_PAD_NUM12,
13: cg.global_ns.TOUCH_PAD_NUM13,
14: cg.global_ns.TOUCH_PAD_NUM14,
},
VARIANT_ESP32S3: {
1: cg.global_ns.TOUCH_PAD_NUM1,
2: cg.global_ns.TOUCH_PAD_NUM2,
3: cg.global_ns.TOUCH_PAD_NUM3,
4: cg.global_ns.TOUCH_PAD_NUM4,
5: cg.global_ns.TOUCH_PAD_NUM5,
6: cg.global_ns.TOUCH_PAD_NUM6,
7: cg.global_ns.TOUCH_PAD_NUM7,
8: cg.global_ns.TOUCH_PAD_NUM8,
9: cg.global_ns.TOUCH_PAD_NUM9,
10: cg.global_ns.TOUCH_PAD_NUM10,
11: cg.global_ns.TOUCH_PAD_NUM11,
12: cg.global_ns.TOUCH_PAD_NUM12,
13: cg.global_ns.TOUCH_PAD_NUM13,
14: cg.global_ns.TOUCH_PAD_NUM14,
},
}
def validate_touch_pad(value):
value = gpio.validate_gpio_pin(value)
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
if variant not in TOUCH_PADS:
raise cv.Invalid(f"ESP32 variant {variant} does not support touch pads.")
pads = TOUCH_PADS[variant]
if value not in pads:
raise cv.Invalid(f"Pin {value} does not support touch pads.")
return cv.enum(pads)(value)
ESP32TouchBinarySensor = esp32_touch_ns.class_( ESP32TouchBinarySensor = esp32_touch_ns.class_(
"ESP32TouchBinarySensor", binary_sensor.BinarySensor "ESP32TouchBinarySensor", binary_sensor.BinarySensor
) )
@ -90,8 +21,8 @@ CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(ESP32TouchBinarySensor).exten
{ {
cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent),
cv.Required(CONF_PIN): validate_touch_pad, cv.Required(CONF_PIN): validate_touch_pad,
cv.Required(CONF_THRESHOLD): cv.uint16_t, cv.Required(CONF_THRESHOLD): cv.uint32_t,
cv.Optional(CONF_WAKEUP_THRESHOLD, default=0): cv.uint16_t, cv.Optional(CONF_WAKEUP_THRESHOLD, default=0): cv.uint32_t,
} }
) )

View file

@ -5,6 +5,8 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace esp32_touch { namespace esp32_touch {
@ -13,18 +15,58 @@ static const char *const TAG = "esp32_touch";
void ESP32TouchComponent::setup() { void ESP32TouchComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 Touch Hub..."); ESP_LOGCONFIG(TAG, "Setting up ESP32 Touch Hub...");
touch_pad_init(); touch_pad_init();
// set up and enable/start filtering based on ESP32 variant
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
if (this->filter_configured_()) {
touch_filter_config_t filter_info = {
.mode = this->filter_mode_,
.debounce_cnt = this->debounce_count_,
.noise_thr = this->noise_threshold_,
.jitter_step = this->jitter_step_,
.smh_lvl = this->smooth_level_,
};
touch_pad_filter_set_config(&filter_info);
touch_pad_filter_enable();
}
if (this->denoise_configured_()) {
touch_pad_denoise_t denoise = {
.grade = this->grade_,
.cap_level = this->cap_level_,
};
touch_pad_denoise_set_config(&denoise);
touch_pad_denoise_enable();
}
if (this->waterproof_configured_()) {
touch_pad_waterproof_t waterproof = {
.guard_ring_pad = this->waterproof_guard_ring_pad_,
.shield_driver = this->waterproof_shield_driver_,
};
touch_pad_waterproof_set_config(&waterproof);
touch_pad_waterproof_enable();
}
#else
if (this->iir_filter_enabled_()) { if (this->iir_filter_enabled_()) {
touch_pad_filter_start(this->iir_filter_); touch_pad_filter_start(this->iir_filter_);
} }
#endif
touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_); touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_);
touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_); touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_);
for (auto *child : this->children_) { for (auto *child : this->children_) {
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
touch_pad_config(child->get_touch_pad());
#else
// Disable interrupt threshold // Disable interrupt threshold
touch_pad_config(child->get_touch_pad(), 0); touch_pad_config(child->get_touch_pad(), 0);
#endif
} }
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
touch_pad_fsm_start();
#endif
} }
void ESP32TouchComponent::dump_config() { void ESP32TouchComponent::dump_config() {
@ -92,38 +134,168 @@ void ESP32TouchComponent::dump_config() {
} }
ESP_LOGCONFIG(TAG, " Voltage Attenuation: %s", atten_s); ESP_LOGCONFIG(TAG, " Voltage Attenuation: %s", atten_s);
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
if (this->filter_configured_()) {
const char *filter_mode_s;
switch (this->filter_mode_) {
case TOUCH_PAD_FILTER_IIR_4:
filter_mode_s = "IIR_4";
break;
case TOUCH_PAD_FILTER_IIR_8:
filter_mode_s = "IIR_8";
break;
case TOUCH_PAD_FILTER_IIR_16:
filter_mode_s = "IIR_16";
break;
case TOUCH_PAD_FILTER_IIR_32:
filter_mode_s = "IIR_32";
break;
case TOUCH_PAD_FILTER_IIR_64:
filter_mode_s = "IIR_64";
break;
case TOUCH_PAD_FILTER_IIR_128:
filter_mode_s = "IIR_128";
break;
case TOUCH_PAD_FILTER_IIR_256:
filter_mode_s = "IIR_256";
break;
case TOUCH_PAD_FILTER_JITTER:
filter_mode_s = "JITTER";
break;
default:
filter_mode_s = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Filter mode: %s", filter_mode_s);
ESP_LOGCONFIG(TAG, " Debounce count: %" PRIu32, this->debounce_count_);
ESP_LOGCONFIG(TAG, " Noise threshold coefficient: %" PRIu32, this->noise_threshold_);
ESP_LOGCONFIG(TAG, " Jitter filter step size: %" PRIu32, this->jitter_step_);
const char *smooth_level_s;
switch (this->smooth_level_) {
case TOUCH_PAD_SMOOTH_OFF:
smooth_level_s = "OFF";
break;
case TOUCH_PAD_SMOOTH_IIR_2:
smooth_level_s = "IIR_2";
break;
case TOUCH_PAD_SMOOTH_IIR_4:
smooth_level_s = "IIR_4";
break;
case TOUCH_PAD_SMOOTH_IIR_8:
smooth_level_s = "IIR_8";
break;
default:
smooth_level_s = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s);
}
if (this->denoise_configured_()) {
const char *grade_s;
switch (this->grade_) {
case TOUCH_PAD_DENOISE_BIT12:
grade_s = "BIT12";
break;
case TOUCH_PAD_DENOISE_BIT10:
grade_s = "BIT10";
break;
case TOUCH_PAD_DENOISE_BIT8:
grade_s = "BIT8";
break;
case TOUCH_PAD_DENOISE_BIT4:
grade_s = "BIT4";
break;
default:
grade_s = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s);
const char *cap_level_s;
switch (this->cap_level_) {
case TOUCH_PAD_DENOISE_CAP_L0:
cap_level_s = "L0";
break;
case TOUCH_PAD_DENOISE_CAP_L1:
cap_level_s = "L1";
break;
case TOUCH_PAD_DENOISE_CAP_L2:
cap_level_s = "L2";
break;
case TOUCH_PAD_DENOISE_CAP_L3:
cap_level_s = "L3";
break;
case TOUCH_PAD_DENOISE_CAP_L4:
cap_level_s = "L4";
break;
case TOUCH_PAD_DENOISE_CAP_L5:
cap_level_s = "L5";
break;
case TOUCH_PAD_DENOISE_CAP_L6:
cap_level_s = "L6";
break;
case TOUCH_PAD_DENOISE_CAP_L7:
cap_level_s = "L7";
break;
default:
cap_level_s = "UNKNOWN";
break;
}
ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s);
}
#else
if (this->iir_filter_enabled_()) { if (this->iir_filter_enabled_()) {
ESP_LOGCONFIG(TAG, " IIR Filter: %ums", this->iir_filter_); ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_);
} else { } else {
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED"); ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
} }
#endif
if (this->setup_mode_) { if (this->setup_mode_) {
ESP_LOGCONFIG(TAG, " Setup Mode ENABLED!"); ESP_LOGCONFIG(TAG, " Setup Mode ENABLED");
} }
for (auto *child : this->children_) { for (auto *child : this->children_) {
LOG_BINARY_SENSOR(" ", "Touch Pad", child); LOG_BINARY_SENSOR(" ", "Touch Pad", child);
ESP_LOGCONFIG(TAG, " Pad: T%d", child->get_touch_pad()); ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad());
ESP_LOGCONFIG(TAG, " Threshold: %u", child->get_threshold()); ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold());
} }
} }
uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
uint32_t value = 0;
if (this->filter_configured_()) {
touch_pad_filter_read_smooth(tp, &value);
} else {
touch_pad_read_raw_data(tp, &value);
}
#else
uint16_t value = 0;
if (this->iir_filter_enabled_()) {
touch_pad_read_filtered(tp, &value);
} else {
touch_pad_read(tp, &value);
}
#endif
return value;
}
void ESP32TouchComponent::loop() { void ESP32TouchComponent::loop() {
const uint32_t now = millis(); const uint32_t now = millis();
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250; bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
for (auto *child : this->children_) { for (auto *child : this->children_) {
uint16_t value; child->value_ = this->component_touch_pad_read(child->get_touch_pad());
if (this->iir_filter_enabled_()) { #if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
touch_pad_read_filtered(child->get_touch_pad(), &value); child->publish_state(child->value_ < child->get_threshold());
} else { #else
touch_pad_read(child->get_touch_pad(), &value); child->publish_state(child->value_ > child->get_threshold());
} #endif
child->value_ = value;
child->publish_state(value < child->get_threshold());
if (should_print) { if (should_print) {
ESP_LOGD(TAG, "Touch Pad '%s' (T%u): %u", child->get_name().c_str(), child->get_touch_pad(), value); ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(),
(uint32_t) child->get_touch_pad(), child->value_);
} }
App.feed_wdt(); App.feed_wdt();
@ -138,10 +310,12 @@ void ESP32TouchComponent::loop() {
void ESP32TouchComponent::on_shutdown() { void ESP32TouchComponent::on_shutdown() {
bool is_wakeup_source = false; bool is_wakeup_source = false;
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
if (this->iir_filter_enabled_()) { if (this->iir_filter_enabled_()) {
touch_pad_filter_stop(); touch_pad_filter_stop();
touch_pad_filter_delete(); touch_pad_filter_delete();
} }
#endif
for (auto *child : this->children_) { for (auto *child : this->children_) {
if (child->get_wakeup_threshold() != 0) { if (child->get_wakeup_threshold() != 0) {
@ -151,8 +325,10 @@ void ESP32TouchComponent::on_shutdown() {
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
} }
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
// No filter available when using as wake-up source. // No filter available when using as wake-up source.
touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold()); touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold());
#endif
} }
} }
@ -161,7 +337,7 @@ void ESP32TouchComponent::on_shutdown() {
} }
} }
ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold) ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold)
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
} // namespace esp32_touch } // namespace esp32_touch

View file

@ -21,25 +21,37 @@ class ESP32TouchBinarySensor;
class ESP32TouchComponent : public Component { class ESP32TouchComponent : public Component {
public: public:
void register_touch_pad(ESP32TouchBinarySensor *pad) { children_.push_back(pad); } void register_touch_pad(ESP32TouchBinarySensor *pad) { this->children_.push_back(pad); }
void set_setup_mode(bool setup_mode) { setup_mode_ = setup_mode; }
void set_iir_filter(uint32_t iir_filter) { iir_filter_ = iir_filter; }
void set_sleep_duration(uint16_t sleep_duration) { sleep_cycle_ = sleep_duration; }
void set_measurement_duration(uint16_t meas_cycle) { meas_cycle_ = meas_cycle; }
void set_setup_mode(bool setup_mode) { this->setup_mode_ = setup_mode; }
void set_sleep_duration(uint16_t sleep_duration) { this->sleep_cycle_ = sleep_duration; }
void set_measurement_duration(uint16_t meas_cycle) { this->meas_cycle_ = meas_cycle; }
void set_low_voltage_reference(touch_low_volt_t low_voltage_reference) { void set_low_voltage_reference(touch_low_volt_t low_voltage_reference) {
low_voltage_reference_ = low_voltage_reference; this->low_voltage_reference_ = low_voltage_reference;
} }
void set_high_voltage_reference(touch_high_volt_t high_voltage_reference) { void set_high_voltage_reference(touch_high_volt_t high_voltage_reference) {
high_voltage_reference_ = high_voltage_reference; this->high_voltage_reference_ = high_voltage_reference;
} }
void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) {
this->voltage_attenuation_ = voltage_attenuation;
}
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
void set_filter_mode(touch_filter_mode_t filter_mode) { this->filter_mode_ = filter_mode; }
void set_debounce_count(uint32_t debounce_count) { this->debounce_count_ = debounce_count; }
void set_noise_threshold(uint32_t noise_threshold) { this->noise_threshold_ = noise_threshold; }
void set_jitter_step(uint32_t jitter_step) { this->jitter_step_ = jitter_step; }
void set_smooth_level(touch_smooth_mode_t smooth_level) { this->smooth_level_ = smooth_level; }
void set_denoise_grade(touch_pad_denoise_grade_t denoise_grade) { this->grade_ = denoise_grade; }
void set_denoise_cap(touch_pad_denoise_cap_t cap_level) { this->cap_level_ = cap_level; }
void set_waterproof_guard_ring_pad(touch_pad_t pad) { this->waterproof_guard_ring_pad_ = pad; }
void set_waterproof_shield_driver(touch_pad_shield_driver_t drive_capability) {
this->waterproof_shield_driver_ = drive_capability;
}
#else
void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; }
#endif
void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) { voltage_attenuation_ = voltage_attenuation; } uint32_t component_touch_pad_read(touch_pad_t tp);
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
@ -49,38 +61,63 @@ class ESP32TouchComponent : public Component {
void on_shutdown() override; void on_shutdown() override;
protected: protected:
/// Is the IIR filter enabled? #if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
bool iir_filter_enabled_() const { return iir_filter_ > 0; } bool filter_configured_() const {
return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX);
}
bool denoise_configured_() const {
return (this->grade_ != TOUCH_PAD_DENOISE_MAX) && (this->cap_level_ != TOUCH_PAD_DENOISE_CAP_MAX);
}
bool waterproof_configured_() const {
return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) &&
(this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX);
}
#else
bool iir_filter_enabled_() const { return this->iir_filter_ > 0; }
#endif
uint16_t sleep_cycle_{};
uint16_t meas_cycle_{65535};
touch_low_volt_t low_voltage_reference_{};
touch_high_volt_t high_voltage_reference_{};
touch_volt_atten_t voltage_attenuation_{};
std::vector<ESP32TouchBinarySensor *> children_; std::vector<ESP32TouchBinarySensor *> children_;
bool setup_mode_{false}; bool setup_mode_{false};
uint32_t setup_mode_last_log_print_{}; uint32_t setup_mode_last_log_print_{0};
// common parameters
uint16_t sleep_cycle_{4095};
uint16_t meas_cycle_{65535};
touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5};
touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7};
touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V};
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX};
uint32_t debounce_count_{0};
uint32_t noise_threshold_{0};
uint32_t jitter_step_{0};
touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX};
touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX};
touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX};
touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX};
touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX};
#else
uint32_t iir_filter_{0}; uint32_t iir_filter_{0};
#endif
}; };
/// Simple helper class to expose a touch pad value as a binary sensor. /// Simple helper class to expose a touch pad value as a binary sensor.
class ESP32TouchBinarySensor : public binary_sensor::BinarySensor { class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
public: public:
ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold); ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold);
touch_pad_t get_touch_pad() const { return touch_pad_; } touch_pad_t get_touch_pad() const { return this->touch_pad_; }
uint16_t get_threshold() const { return threshold_; } uint32_t get_threshold() const { return this->threshold_; }
void set_threshold(uint16_t threshold) { threshold_ = threshold; } void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
uint16_t get_value() const { return value_; } uint32_t get_value() const { return this->value_; }
uint16_t get_wakeup_threshold() const { return wakeup_threshold_; } uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
protected: protected:
friend ESP32TouchComponent; friend ESP32TouchComponent;
touch_pad_t touch_pad_; touch_pad_t touch_pad_{TOUCH_PAD_MAX};
uint16_t threshold_; uint32_t threshold_{0};
uint16_t value_; uint32_t value_{0};
const uint16_t wakeup_threshold_; const uint32_t wakeup_threshold_{0};
}; };
} // namespace esp32_touch } // namespace esp32_touch

View file

@ -118,6 +118,10 @@ void EthernetComponent::setup() {
ESPHL_ERROR_CHECK(err, "ETH event handler register error"); ESPHL_ERROR_CHECK(err, "ETH event handler register error");
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr);
ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); ESPHL_ERROR_CHECK(err, "GOT IP event handler register error");
#if LWIP_IPV6
err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr);
ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error");
#endif /* LWIP_IPV6 */
/* start Ethernet driver state machine */ /* start Ethernet driver state machine */
err = esp_eth_start(this->eth_handle_); err = esp_eth_start(this->eth_handle_);
@ -160,6 +164,20 @@ void EthernetComponent::loop() {
this->state_ = EthernetComponentState::CONNECTING; this->state_ = EthernetComponentState::CONNECTING;
this->start_connect_(); this->start_connect_();
} }
#if LWIP_IPV6
else if (this->got_ipv6_) {
esp_ip6_addr_t ip6_addr;
if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 &&
esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) {
ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr));
} else {
esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr);
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr));
}
this->got_ipv6_ = false;
}
#endif /* LWIP_IPV6 */
break; break;
} }
} }
@ -254,6 +272,15 @@ void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_b
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id);
} }
#if LWIP_IPV6
void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data) {
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%d)", event_id);
global_eth_component->got_ipv6_ = true;
global_eth_component->ipv6_count_ += 1;
}
#endif /* LWIP_IPV6 */
void EthernetComponent::start_connect_() { void EthernetComponent::start_connect_() {
this->connect_begin_ = millis(); this->connect_begin_ = millis();
this->status_set_warning(); this->status_set_warning();
@ -316,6 +343,12 @@ void EthernetComponent::start_connect_() {
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error"); ESPHL_ERROR_CHECK(err, "DHCPC start error");
} }
#if LWIP_IPV6
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
if (err != ESP_OK) {
ESPHL_ERROR_CHECK(err, "IPv6 local failed");
}
#endif /* LWIP_IPV6 */
} }
this->connect_begin_ = millis(); this->connect_begin_ = millis();
@ -343,6 +376,19 @@ void EthernetComponent::dump_connect_params_() {
ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str()); ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2->addr).str().c_str());
#endif #endif
#if LWIP_IPV6
if (this->ipv6_count_ > 0) {
esp_ip6_addr_t ip6_addr;
esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr);
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr));
if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 &&
esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) {
ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr));
}
}
#endif /* LWIP_IPV6 */
esp_err_t err; esp_err_t err;
uint8_t mac[6]; uint8_t mac[6];

View file

@ -65,6 +65,9 @@ class EthernetComponent : public Component {
protected: protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#if LWIP_IPV6
static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
#endif /* LWIP_IPV6 */
void start_connect_(); void start_connect_();
void dump_connect_params_(); void dump_connect_params_();
@ -83,6 +86,10 @@ class EthernetComponent : public Component {
bool started_{false}; bool started_{false};
bool connected_{false}; bool connected_{false};
#if LWIP_IPV6
bool got_ipv6_{false};
uint8_t ipv6_count_{0};
#endif /* LWIP_IPV6 */
EthernetComponentState state_{EthernetComponentState::STOPPED}; EthernetComponentState state_{EthernetComponentState::STOPPED};
uint32_t connect_begin_; uint32_t connect_begin_;
esp_netif_t *eth_netif_{nullptr}; esp_netif_t *eth_netif_{nullptr};

View file

@ -0,0 +1 @@
CODEOWNERS = ["@gcormier"]

View file

@ -0,0 +1,119 @@
/* From snooping with a logic analyzer, the I2C on this sensor is broken. I was only able
* to receive 1's as a response from the sensor. I was able to get the UART working.
*
* The datasheet says the values should be divided by 1000, but this must only be for the I2C
* implementation. Comparing UART values with another sensor, there is no need to divide by 1000.
*/
#include "gcja5.h"
#include "esphome/core/log.h"
#include <cstring>
namespace esphome {
namespace gcja5 {
static const char *const TAG = "gcja5";
void GCJA5Component::setup() { ESP_LOGCONFIG(TAG, "Setting up gcja5..."); }
void GCJA5Component::loop() {
const uint32_t now = millis();
if (now - this->last_transmission_ >= 500) {
// last transmission too long ago. Reset RX index.
this->rx_message_.clear();
}
if (this->available() == 0) {
return;
}
// There must now be data waiting
this->last_transmission_ = now;
uint8_t val;
while (this->available() != 0) {
this->read_byte(&val);
this->rx_message_.push_back(val);
// check if rx_message_ has 32 bytes of data
if (this->rx_message_.size() == 32) {
this->parse_data_();
if (this->have_good_data_) {
if (this->pm_1_0_sensor_ != nullptr)
this->pm_1_0_sensor_->publish_state(get_32_bit_uint_(1));
if (this->pm_2_5_sensor_ != nullptr)
this->pm_2_5_sensor_->publish_state(get_32_bit_uint_(5));
if (this->pm_10_0_sensor_ != nullptr)
this->pm_10_0_sensor_->publish_state(get_32_bit_uint_(9));
if (this->pmc_0_3_sensor_ != nullptr)
this->pmc_0_3_sensor_->publish_state(get_16_bit_uint_(13));
if (this->pmc_0_5_sensor_ != nullptr)
this->pmc_0_5_sensor_->publish_state(get_16_bit_uint_(15));
if (this->pmc_1_0_sensor_ != nullptr)
this->pmc_1_0_sensor_->publish_state(get_16_bit_uint_(17));
if (this->pmc_2_5_sensor_ != nullptr)
this->pmc_2_5_sensor_->publish_state(get_16_bit_uint_(21));
if (this->pmc_5_0_sensor_ != nullptr)
this->pmc_5_0_sensor_->publish_state(get_16_bit_uint_(23));
if (this->pmc_10_0_sensor_ != nullptr)
this->pmc_10_0_sensor_->publish_state(get_16_bit_uint_(25));
} else {
this->status_set_warning();
ESP_LOGV(TAG, "Have 32 bytes but not good data. Skipping.");
}
this->rx_message_.clear();
}
}
}
bool GCJA5Component::calculate_checksum_() {
uint8_t crc = 0;
for (uint8_t i = 1; i < 30; i++)
crc = crc ^ this->rx_message_[i];
ESP_LOGVV(TAG, "Checksum packet was (0x%02X), calculated checksum was (0x%02X)", this->rx_message_[30], crc);
return (crc == this->rx_message_[30]);
}
uint32_t GCJA5Component::get_32_bit_uint_(uint8_t start_index) {
return (((uint32_t) this->rx_message_[start_index + 3]) << 24) |
(((uint32_t) this->rx_message_[start_index + 2]) << 16) |
(((uint32_t) this->rx_message_[start_index + 1]) << 8) | ((uint32_t) this->rx_message_[start_index]);
}
uint16_t GCJA5Component::get_16_bit_uint_(uint8_t start_index) {
return (((uint32_t) this->rx_message_[start_index + 1]) << 8) | ((uint32_t) this->rx_message_[start_index]);
}
void GCJA5Component::parse_data_() {
ESP_LOGVV(TAG, "GCJA5 Data: ");
for (uint8_t i = 0; i < 32; i++) {
ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->rx_message_[i]),
this->rx_message_[i]);
}
if (this->rx_message_[0] != 0x02 || this->rx_message_[31] != 0x03 || !this->calculate_checksum_()) {
ESP_LOGVV(TAG, "Discarding bad packet - failed checks.");
return;
} else
ESP_LOGVV(TAG, "Good packet found.");
this->have_good_data_ = true;
uint8_t status = this->rx_message_[29];
if (!this->first_status_log_) {
this->first_status_log_ = true;
ESP_LOGI(TAG, "GCJA5 Status");
ESP_LOGI(TAG, "Overall Status : %i", (status >> 6) & 0x03);
ESP_LOGI(TAG, "PD Status : %i", (status >> 4) & 0x03);
ESP_LOGI(TAG, "LD Status : %i", (status >> 2) & 0x03);
ESP_LOGI(TAG, "Fan Status : %i", (status >> 0) & 0x03);
}
}
void GCJA5Component::dump_config() { ; }
} // namespace gcja5
} // namespace esphome

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