[lis2dw12] Add ST LIS2DW12 accelerometer component (#14780)

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Anton Viktorov
2026-10-13 09:44:51 +13:00
committed by GitHub
co-authored by J. Nick Koston J. Nick Koston
parent 5a3bee638f
commit 833c6634ad
21 changed files with 1138 additions and 0 deletions
+3
View File
@@ -311,6 +311,9 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/lis2dw12/* @latonita
esphome/components/lis2dw12_i2c/* @latonita
esphome/components/lis2dw12_spi/* @latonita
esphome/components/lm75b/* @beormund
esphome/components/ln882h_ble/* @Bl00d-B0b
esphome/components/ln882h_ble_tracker/* @Bl00d-B0b
+181
View File
@@ -0,0 +1,181 @@
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_CALIBRATION,
CONF_ID,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_OFFSET_X,
CONF_OFFSET_Y,
CONF_OFFSET_Z,
CONF_POWER_MODE,
CONF_RANGE,
CONF_SWAP_XY,
CONF_TRANSFORM,
)
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType
CODEOWNERS = ["@latonita"]
DOMAIN = "lis2dw12"
CONF_LIS2DW12_ID = "lis2dw12_id"
CONF_MIRROR_Z = "mirror_z"
CONF_ON_ACTIVE = "on_active"
CONF_ON_DOUBLE_TAP = "on_double_tap"
CONF_ON_FREEFALL = "on_freefall"
CONF_ON_ORIENTATION = "on_orientation"
CONF_ON_TAP = "on_tap"
CONF_LOW_NOISE = "low_noise"
CONF_FILTER_BANDWIDTH = "filter_bandwidth"
CONF_OUTPUT_DATA_RATE = "output_data_rate"
lis2dw12_ns = cg.esphome_ns.namespace("lis2dw12")
LIS2DW12Component = lis2dw12_ns.class_("LIS2DW12Component", cg.PollingComponent)
LIS2DW12Range = lis2dw12_ns.enum("Range", True)
RANGES = {
"2G": LIS2DW12Range.RANGE_2G,
"4G": LIS2DW12Range.RANGE_4G,
"8G": LIS2DW12Range.RANGE_8G,
"16G": LIS2DW12Range.RANGE_16G,
}
LIS2DW12PowerMode = lis2dw12_ns.enum("PowerMode", True)
POWER_MODES = {
"high_performance": LIS2DW12PowerMode.HIGH_PERF,
"low_power_1": LIS2DW12PowerMode.LOW_POWER_1,
"low_power_2": LIS2DW12PowerMode.LOW_POWER_2,
"low_power_3": LIS2DW12PowerMode.LOW_POWER_3,
"low_power_4": LIS2DW12PowerMode.LOW_POWER_4,
}
LIS2DW12DataRate = lis2dw12_ns.enum("DataRate", True)
DATA_RATES = {
"1.6Hz": LIS2DW12DataRate.RATE_1_6HZ,
"12.5Hz": LIS2DW12DataRate.RATE_12_5HZ,
"25Hz": LIS2DW12DataRate.RATE_25HZ,
"50Hz": LIS2DW12DataRate.RATE_50HZ,
"100Hz": LIS2DW12DataRate.RATE_100HZ,
"200Hz": LIS2DW12DataRate.RATE_200HZ,
"400Hz": LIS2DW12DataRate.RATE_400HZ,
"800Hz": LIS2DW12DataRate.RATE_800HZ,
"1600Hz": LIS2DW12DataRate.RATE_1600HZ,
}
LIS2DW12FilterBandwidth = lis2dw12_ns.enum("FilterBandwidth", True)
FILTER_BANDWIDTHS = {
"odr_div_2": LIS2DW12FilterBandwidth.BW_ODR_DIV_2,
"odr_div_4": LIS2DW12FilterBandwidth.BW_ODR_DIV_4,
"odr_div_10": LIS2DW12FilterBandwidth.BW_ODR_DIV_10,
"odr_div_20": LIS2DW12FilterBandwidth.BW_ODR_DIV_20,
}
_CALLBACK_AUTOMATIONS = (
automation.CallbackAutomation(CONF_ON_TAP, "add_on_tap_callback"),
automation.CallbackAutomation(CONF_ON_DOUBLE_TAP, "add_on_double_tap_callback"),
automation.CallbackAutomation(CONF_ON_FREEFALL, "add_on_freefall_callback"),
automation.CallbackAutomation(CONF_ON_ACTIVE, "add_on_active_callback"),
automation.CallbackAutomation(CONF_ON_ORIENTATION, "add_on_orientation_callback"),
)
_OFFSET = cv.float_range(min=-4.5, max=4.5)
# The ODR code means different rates per power mode: 0001 is 1.6 Hz only in low-power modes
# (12.5 Hz in high performance) and every code above 200 Hz runs at 200 Hz in low-power modes
_LOW_POWER_ONLY_RATES = ("1.6Hz",)
_HIGH_PERF_ONLY_RATES = ("400Hz", "800Hz", "1600Hz")
def validate_data_rate(config: ConfigType) -> ConfigType:
"""Reject rates the chip cannot run in the chosen power mode; apply after extending the base schema."""
rate = str(config[CONF_OUTPUT_DATA_RATE])
mode = str(config[CONF_POWER_MODE])
high_perf = mode == "high_performance"
if (high_perf and rate in _LOW_POWER_ONLY_RATES) or (
not high_perf and rate in _HIGH_PERF_ONLY_RATES
):
raise cv.Invalid(
f"{CONF_OUTPUT_DATA_RATE} {rate} is not available with {CONF_POWER_MODE} {mode}",
[CONF_OUTPUT_DATA_RATE],
)
return config
CONFIG_SCHEMA_BASE = cv.Schema(
{
cv.GenerateID(): cv.declare_id(LIS2DW12Component),
cv.Optional(CONF_RANGE, default="2G"): cv.enum(RANGES, upper=True),
cv.Optional(CONF_POWER_MODE, default="high_performance"): cv.enum(
POWER_MODES, lower=True
),
cv.Optional(CONF_OUTPUT_DATA_RATE, default="100Hz"): cv.enum(DATA_RATES),
cv.Optional(CONF_FILTER_BANDWIDTH, default="odr_div_2"): cv.enum(
FILTER_BANDWIDTHS, lower=True
),
cv.Optional(CONF_LOW_NOISE, default=False): cv.boolean,
cv.Optional(CONF_CALIBRATION): cv.Schema(
{
cv.Optional(CONF_OFFSET_X, default=0): _OFFSET,
cv.Optional(CONF_OFFSET_Y, default=0): _OFFSET,
cv.Optional(CONF_OFFSET_Z, default=0): _OFFSET,
}
),
cv.Optional(CONF_TRANSFORM): cv.Schema(
{
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Z, default=False): cv.boolean,
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
}
),
cv.Optional(CONF_ON_TAP): automation.validate_automation({}),
cv.Optional(CONF_ON_DOUBLE_TAP): automation.validate_automation({}),
cv.Optional(CONF_ON_FREEFALL): automation.validate_automation({}),
cv.Optional(CONF_ON_ACTIVE): automation.validate_automation({}),
cv.Optional(CONF_ON_ORIENTATION): automation.validate_automation({}),
}
).extend(cv.polling_component_schema("10s"))
LIS2DW12_SENSOR_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LIS2DW12_ID): cv.use_id(LIS2DW12Component),
}
)
async def to_code_base(config: ConfigType) -> MockObj:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_range(config[CONF_RANGE]))
cg.add(var.set_power_mode(config[CONF_POWER_MODE]))
cg.add(var.set_output_data_rate(config[CONF_OUTPUT_DATA_RATE]))
cg.add(var.set_filter_bandwidth(config[CONF_FILTER_BANDWIDTH]))
cg.add(var.set_low_noise(config[CONF_LOW_NOISE]))
if transform := config.get(CONF_TRANSFORM):
cg.add(
var.set_transform(
transform[CONF_MIRROR_X],
transform[CONF_MIRROR_Y],
transform[CONF_MIRROR_Z],
transform[CONF_SWAP_XY],
)
)
if calibration_config := config.get(CONF_CALIBRATION):
cg.add(
var.set_offset(
calibration_config[CONF_OFFSET_X],
calibration_config[CONF_OFFSET_Y],
calibration_config[CONF_OFFSET_Z],
)
)
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
return var
@@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ACTIVE, CONF_NAME, DEVICE_CLASS_VIBRATION, ICON_VIBRATE
from esphome.types import ConfigType
from . import CONF_LIS2DW12_ID, LIS2DW12_SENSOR_SCHEMA
CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["lis2dw12"]
CONF_DOUBLE_TAP = "double_tap"
CONF_FREEFALL = "freefall"
CONF_TAP = "tap"
ICON_DOUBLE_TAP = "mdi:gesture-double-tap"
ICON_FREEFALL = "mdi:parachute"
ICON_TAP = "mdi:gesture-tap"
EVENT_SENSORS = (CONF_TAP, CONF_DOUBLE_TAP, CONF_FREEFALL, CONF_ACTIVE)
ICONS = (ICON_TAP, ICON_DOUBLE_TAP, ICON_FREEFALL, ICON_VIBRATE)
CONFIG_SCHEMA = LIS2DW12_SENSOR_SCHEMA.extend(
{
cv.Optional(event): cv.maybe_simple_value(
binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_VIBRATION,
icon=icon,
),
key=CONF_NAME,
)
for event, icon in zip(EVENT_SENSORS, ICONS, strict=True)
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LIS2DW12_ID])
sensors = binary_sensor.sub_binary_sensors(config)
await sensors(CONF_TAP, hub.set_tap_binary_sensor)
await sensors(CONF_DOUBLE_TAP, hub.set_double_tap_binary_sensor)
await sensors(CONF_FREEFALL, hub.set_freefall_binary_sensor)
await sensors(CONF_ACTIVE, hub.set_active_binary_sensor)
+350
View File
@@ -0,0 +1,350 @@
#include "lis2dw12.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::lis2dw12 {
ESPHOME_LOG_TAG(TAG, "lis2dw12");
static constexpr uint8_t LIS2DW12_WHO_AM_I_VALUE = 0x44;
static constexpr float GRAVITY_EARTH = 9.80665f;
static constexpr uint32_t EVENT_COOLDOWN_MS = 500;
static constexpr uint32_t POLL_INTERVAL_MS = 50;
static constexpr uint8_t SIXD_POSITION_MASK = 0x3F; // XL..ZH, leaving out D6D_IA
static const LogString *power_mode_to_string(PowerMode mode) {
switch (mode) {
case PowerMode::HIGH_PERF:
return LOG_STR("High Performance");
case PowerMode::LOW_POWER_1:
return LOG_STR("Low Power 1 (12-bit)");
case PowerMode::LOW_POWER_2:
return LOG_STR("Low Power 2");
case PowerMode::LOW_POWER_3:
return LOG_STR("Low Power 3");
case PowerMode::LOW_POWER_4:
return LOG_STR("Low Power 4");
}
return LOG_STR("Unknown");
}
static const LogString *range_to_string(Range range) {
switch (range) {
case Range::RANGE_2G:
return LOG_STR("±2g");
case Range::RANGE_4G:
return LOG_STR("±4g");
case Range::RANGE_8G:
return LOG_STR("±8g");
case Range::RANGE_16G:
return LOG_STR("±16g");
}
return LOG_STR("Unknown");
}
static const LogString *data_rate_to_string(DataRate rate) {
switch (rate) {
case DataRate::RATE_1_6HZ:
return LOG_STR("1.6 Hz");
case DataRate::RATE_12_5HZ:
return LOG_STR("12.5 Hz");
case DataRate::RATE_25HZ:
return LOG_STR("25 Hz");
case DataRate::RATE_50HZ:
return LOG_STR("50 Hz");
case DataRate::RATE_100HZ:
return LOG_STR("100 Hz");
case DataRate::RATE_200HZ:
return LOG_STR("200 Hz");
case DataRate::RATE_400HZ:
return LOG_STR("400 Hz");
case DataRate::RATE_800HZ:
return LOG_STR("800 Hz");
case DataRate::RATE_1600HZ:
return LOG_STR("1600 Hz");
}
return LOG_STR("Unknown");
}
static const LogString *filter_bandwidth_to_string(FilterBandwidth bw) {
switch (bw) {
case FilterBandwidth::BW_ODR_DIV_2:
return LOG_STR("ODR/2");
case FilterBandwidth::BW_ODR_DIV_4:
return LOG_STR("ODR/4");
case FilterBandwidth::BW_ODR_DIV_10:
return LOG_STR("ODR/10");
case FilterBandwidth::BW_ODR_DIV_20:
return LOG_STR("ODR/20");
}
return LOG_STR("Unknown");
}
void LIS2DW12Component::setup() {
uint8_t who_am_i{0};
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::WHO_AM_I), &who_am_i) || who_am_i != LIS2DW12_WHO_AM_I_VALUE) {
ESP_LOGE(TAG, "WHO_AM_I check failed. Got 0x%02X, expected 0x%02X", who_am_i, LIS2DW12_WHO_AM_I_VALUE);
this->mark_failed();
return;
}
// Soft reset
if (!this->write_reg_(RegisterMap::CTRL2, 0x40)) {
this->mark_failed(LOG_STR("Soft reset failed"));
return;
}
delay(1); // NOLINT - wait for reset to complete
uint8_t ctrl2{0};
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::CTRL2), &ctrl2) || (ctrl2 & 0x40)) {
this->mark_failed(LOG_STR("Soft reset did not complete"));
return;
}
if (!this->configure_registers_()) {
this->mark_failed(LOG_STR("Register configuration failed"));
return;
}
// 0.244 mg/LSB at ±2g (14-bit), doubling per range step; 12-bit LP1 is 4x coarser
const bool lp1 = this->power_mode_ == PowerMode::LOW_POWER_1;
const uint8_t range_idx = static_cast<uint8_t>(this->range_);
const float mg = 0.244f * (1u << range_idx) * (lp1 ? 4 : 1);
this->scale_ = mg * 0.001f * GRAVITY_EARTH;
this->shift_ = lp1 ? 4 : 2;
bool events_used = !this->tap_callback_.empty() || !this->double_tap_callback_.empty() ||
!this->freefall_callback_.empty() || !this->active_callback_.empty() ||
!this->orientation_callback_.empty();
#ifdef USE_BINARY_SENSOR
for (auto *sensor : this->event_binary_sensors_) {
if (sensor != nullptr) {
sensor->publish_initial_state(false);
events_used = true;
}
}
#endif
#ifdef USE_TEXT_SENSOR
RegSixdSrc sixd;
if (this->orientation_text_sensor_ != nullptr &&
this->read_byte(static_cast<uint8_t>(RegisterMap::SIXD_SRC), &sixd.raw)) {
this->publish_orientation_(sixd);
events_used = true;
}
#endif
if (!events_used) {
this->disable_loop(); // nothing consumes the events, so skip polling the chip
}
}
bool LIS2DW12Component::configure_registers_() {
// CTRL1: ODR[7:4], MODE[3:2] (00=low power, 01=high performance), LP_MODE[1:0] (LP1..LP4)
const uint8_t odr_bits = static_cast<uint8_t>(this->data_rate_) << 4;
uint8_t ctrl1;
if (this->power_mode_ == PowerMode::HIGH_PERF) {
ctrl1 = odr_bits | 0x04;
} else {
ctrl1 = odr_bits | ((static_cast<uint8_t>(this->power_mode_) - 1) & 0x03);
}
// CTRL6: BW_FILT[7:6], FS[5:4], LOW_NOISE[2]
uint8_t ctrl6 = (static_cast<uint8_t>(this->filter_bandwidth_) << 6) | (static_cast<uint8_t>(this->range_) << 4);
if (this->low_noise_) {
ctrl6 |= 0x04;
}
return this->write_reg_(RegisterMap::CTRL2, 0x0C) && // BDU, IF_ADD_INC
this->write_reg_(RegisterMap::CTRL1, ctrl1) && // ODR and power mode
this->write_reg_(RegisterMap::CTRL6, ctrl6) && // bandwidth, range, low noise
this->write_reg_(RegisterMap::TAP_THS_X, 0x29) && // 6D_THS=70 degrees, TAP_THSX=9
this->write_reg_(RegisterMap::TAP_THS_Y, 0x09) && // TAP_THSY=9
this->write_reg_(RegisterMap::TAP_THS_Z, 0xE9) && // TAP_THSZ=9, tap on X/Y/Z
this->write_reg_(RegisterMap::INT_DUR, 0x76) && // LATENCY=7, QUIET=1, SHOCK=2
this->write_reg_(RegisterMap::WAKE_UP_THS, 0x82) && // double tap enabled, WU_THS=2
this->write_reg_(RegisterMap::WAKE_UP_DUR, 0x00) && // no wake/sleep durations
this->write_reg_(RegisterMap::FREE_FALL, 0x33) && // FF_DUR=6, FF_THS=312 mg
this->write_reg_(RegisterMap::CTRL4_INT1, 0xF8) && // 6D, single tap, wake-up, free-fall, double tap to INT1
this->write_reg_(RegisterMap::CTRL3, 0x10) && // LIR: latched interrupts
this->write_reg_(RegisterMap::CTRL7, 0x20); // INTERRUPTS_ENABLE
}
void LIS2DW12Component::dump_config() {
ESP_LOGCONFIG(TAG, "LIS2DW12:");
if (this->is_failed()) {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
}
ESP_LOGCONFIG(TAG,
" Power Mode: %s\n"
" Data Rate: %s\n"
" Range: %s\n"
" Filter Bandwidth: %s\n"
" Low Noise: %s\n"
" Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}\n"
" Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}",
LOG_STR_ARG(power_mode_to_string(this->power_mode_)),
LOG_STR_ARG(data_rate_to_string(this->data_rate_)), LOG_STR_ARG(range_to_string(this->range_)),
LOG_STR_ARG(filter_bandwidth_to_string(this->filter_bandwidth_)), YESNO(this->low_noise_),
this->offset_x_, this->offset_y_, this->offset_z_, YESNO(this->mirror_x_), YESNO(this->mirror_y_),
YESNO(this->mirror_z_), YESNO(this->swap_xy_));
LOG_UPDATE_INTERVAL(this);
#ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "Tap", this->event_binary_sensors_[EVENT_TAP]);
LOG_BINARY_SENSOR(" ", "Double Tap", this->event_binary_sensors_[EVENT_DOUBLE_TAP]);
LOG_BINARY_SENSOR(" ", "Freefall", this->event_binary_sensors_[EVENT_FREEFALL]);
LOG_BINARY_SENSOR(" ", "Active", this->event_binary_sensors_[EVENT_ACTIVE]);
#endif
#ifdef USE_SENSOR
LOG_SENSOR(" ", "Acceleration X", this->acceleration_x_sensor_);
LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
#endif
#ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "Orientation", this->orientation_text_sensor_);
#endif
}
bool LIS2DW12Component::read_acceleration_data_(float &x, float &y, float &z) {
uint8_t accel_data[6];
if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::OUT_X_L), accel_data, 6)) {
return false;
}
int16_t raw_x = static_cast<int16_t>(encode_uint16(accel_data[1], accel_data[0])) >> this->shift_;
int16_t raw_y = static_cast<int16_t>(encode_uint16(accel_data[3], accel_data[2])) >> this->shift_;
int16_t raw_z = static_cast<int16_t>(encode_uint16(accel_data[5], accel_data[4])) >> this->shift_;
if (this->swap_xy_) {
std::swap(raw_x, raw_y);
}
x = (this->mirror_x_ ? -raw_x : raw_x) * this->scale_ + this->offset_x_;
y = (this->mirror_y_ ? -raw_y : raw_y) * this->scale_ + this->offset_y_;
z = (this->mirror_z_ ? -raw_z : raw_z) * this->scale_ + this->offset_z_;
return true;
}
#ifdef USE_TEXT_SENSOR
static const char *orientation_to_string(RegSixdSrc sixd) {
if (sixd.xh)
return "X Up";
if (sixd.xl)
return "X Down";
if (sixd.yh)
return "Y Up";
if (sixd.yl)
return "Y Down";
if (sixd.zh)
return "Z Up";
if (sixd.zl)
return "Z Down";
return "Unknown";
}
void LIS2DW12Component::publish_orientation_(RegSixdSrc sixd) {
const uint8_t position = sixd.raw & SIXD_POSITION_MASK;
if (this->orientation_text_sensor_ == nullptr || position == this->sixd_last_) {
return;
}
this->sixd_last_ = position;
this->orientation_text_sensor_->publish_state(orientation_to_string(sixd));
}
#endif
void LIS2DW12Component::handle_event_(Event event, bool active, uint32_t now, LazyCallbackManager<void()> &callback) {
const uint8_t bit = 1 << event;
bool state;
if (active) {
if (now - this->last_event_ms_[event] <= EVENT_COOLDOWN_MS) {
return;
}
this->last_event_ms_[event] = now;
callback.call();
if (this->events_on_ & bit) {
return;
}
this->events_on_ |= bit;
state = true;
} else {
if (!(this->events_on_ & bit) || now - this->last_event_ms_[event] <= EVENT_COOLDOWN_MS) {
return;
}
this->events_on_ &= ~bit;
state = false;
}
#ifdef USE_BINARY_SENSOR
if (this->event_binary_sensors_[event] != nullptr) {
this->event_binary_sensors_[event]->publish_state(state);
}
#endif
}
void LIS2DW12Component::loop() {
const uint32_t now = App.get_loop_component_start_time();
if (now - this->last_poll_ms_ < POLL_INTERVAL_MS) {
return;
}
this->last_poll_ms_ = now;
// SIXD_SRC and ALL_INT_SRC are adjacent, so one read captures both before any latch clears
uint8_t regs[2];
if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::SIXD_SRC), regs, sizeof(regs))) {
this->status_set_warning();
return;
}
this->status_clear_warning();
RegSixdSrc sixd;
sixd.raw = regs[0];
RegAllIntSrc src;
src.raw = regs[1];
this->handle_event_(EVENT_TAP, src.single_tap, now, this->tap_callback_);
this->handle_event_(EVENT_DOUBLE_TAP, src.double_tap, now, this->double_tap_callback_);
this->handle_event_(EVENT_FREEFALL, src.ff_ia, now, this->freefall_callback_);
this->handle_event_(EVENT_ACTIVE, src.wu_ia, now, this->active_callback_);
if (sixd.d6d_ia) {
this->orientation_callback_.call();
#ifdef USE_TEXT_SENSOR
this->publish_orientation_(sixd);
#endif
}
}
void LIS2DW12Component::update() {
float x, y, z;
if (!this->read_acceleration_data_(x, y, z)) {
this->status_set_warning();
return;
}
ESP_LOGV(TAG, "Acceleration: {x = %+1.3f m/s², y = %+1.3f m/s², z = %+1.3f m/s²}", x, y, z);
#ifdef USE_SENSOR
if (this->acceleration_x_sensor_ != nullptr)
this->acceleration_x_sensor_->publish_state(x);
if (this->acceleration_y_sensor_ != nullptr)
this->acceleration_y_sensor_->publish_state(y);
if (this->acceleration_z_sensor_ != nullptr)
this->acceleration_z_sensor_->publish_state(z);
#endif
this->status_clear_warning();
}
void LIS2DW12Component::set_offset(float offset_x, float offset_y, float offset_z) {
this->offset_x_ = offset_x;
this->offset_y_ = offset_y;
this->offset_z_ = offset_z;
}
void LIS2DW12Component::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) {
this->mirror_x_ = mirror_x;
this->mirror_y_ = mirror_y;
this->mirror_z_ = mirror_z;
this->swap_xy_ = swap_xy;
}
} // namespace esphome::lis2dw12
+198
View File
@@ -0,0 +1,198 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome::lis2dw12 {
// LIS2DW12 Register Map
enum class RegisterMap : uint8_t {
WHO_AM_I = 0x0F,
CTRL1 = 0x20,
CTRL2 = 0x21,
CTRL3 = 0x22,
CTRL4_INT1 = 0x23,
CTRL6 = 0x25,
OUT_X_L = 0x28,
TAP_THS_X = 0x30,
TAP_THS_Y = 0x31,
TAP_THS_Z = 0x32,
INT_DUR = 0x33,
WAKE_UP_THS = 0x34,
WAKE_UP_DUR = 0x35,
FREE_FALL = 0x36,
SIXD_SRC = 0x3A,
ALL_INT_SRC = 0x3B,
CTRL7 = 0x3F,
};
enum class Range : uint8_t {
RANGE_2G = 0b00,
RANGE_4G = 0b01,
RANGE_8G = 0b10,
RANGE_16G = 0b11,
};
enum class PowerMode : uint8_t {
HIGH_PERF = 0,
LOW_POWER_1 = 1,
LOW_POWER_2 = 2,
LOW_POWER_3 = 3,
LOW_POWER_4 = 4,
};
enum class DataRate : uint8_t {
RATE_1_6HZ = 0b0001,
RATE_12_5HZ = 0b0010,
RATE_25HZ = 0b0011,
RATE_50HZ = 0b0100,
RATE_100HZ = 0b0101,
RATE_200HZ = 0b0110,
RATE_400HZ = 0b0111,
RATE_800HZ = 0b1000,
RATE_1600HZ = 0b1001,
};
enum class FilterBandwidth : uint8_t {
BW_ODR_DIV_2 = 0b00,
BW_ODR_DIV_4 = 0b01,
BW_ODR_DIV_10 = 0b10,
BW_ODR_DIV_20 = 0b11,
};
// ALL_INT_SRC register (0x3B)
union RegAllIntSrc {
struct {
bool ff_ia : 1;
bool wu_ia : 1;
bool single_tap : 1;
bool double_tap : 1;
bool d6d_ia : 1;
bool sleep_change_ia : 1;
uint8_t reserved : 2;
} __attribute__((packed));
uint8_t raw{0};
};
// SIXD_SRC register (0x3A)
union RegSixdSrc {
struct {
bool xl : 1;
bool xh : 1;
bool yl : 1;
bool yh : 1;
bool zl : 1;
bool zh : 1;
bool d6d_ia : 1;
uint8_t reserved : 1;
} __attribute__((packed));
uint8_t raw{0};
};
// Debounced events, also the bit index into events_on_
enum Event : uint8_t {
EVENT_TAP = 0,
EVENT_DOUBLE_TAP,
EVENT_FREEFALL,
EVENT_ACTIVE,
EVENT_COUNT,
};
class LIS2DW12Component : public PollingComponent {
public:
void setup() override;
void dump_config() override;
void loop() override;
void update() override;
void set_power_mode(PowerMode mode) { this->power_mode_ = mode; }
void set_low_noise(bool low_noise) { this->low_noise_ = low_noise; }
void set_output_data_rate(DataRate rate) { this->data_rate_ = rate; }
void set_range(Range range) { this->range_ = range; }
void set_filter_bandwidth(FilterBandwidth bw) { this->filter_bandwidth_ = bw; }
void set_offset(float offset_x, float offset_y, float offset_z);
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy);
bool read_byte(uint8_t a_register, uint8_t *data) { return this->read_bytes(a_register, data, 1); }
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
#ifdef USE_BINARY_SENSOR
void set_tap_binary_sensor(binary_sensor::BinarySensor *s) { this->event_binary_sensors_[EVENT_TAP] = s; }
void set_double_tap_binary_sensor(binary_sensor::BinarySensor *s) {
this->event_binary_sensors_[EVENT_DOUBLE_TAP] = s;
}
void set_freefall_binary_sensor(binary_sensor::BinarySensor *s) { this->event_binary_sensors_[EVENT_FREEFALL] = s; }
void set_active_binary_sensor(binary_sensor::BinarySensor *s) { this->event_binary_sensors_[EVENT_ACTIVE] = s; }
#endif
#ifdef USE_SENSOR
SUB_SENSOR(acceleration_x)
SUB_SENSOR(acceleration_y)
SUB_SENSOR(acceleration_z)
#endif
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(orientation)
#endif
template<typename F> void add_on_tap_callback(F &&callback) { this->tap_callback_.add(std::forward<F>(callback)); }
template<typename F> void add_on_double_tap_callback(F &&callback) {
this->double_tap_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_freefall_callback(F &&callback) {
this->freefall_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_active_callback(F &&callback) {
this->active_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_orientation_callback(F &&callback) {
this->orientation_callback_.add(std::forward<F>(callback));
}
protected:
bool write_reg_(RegisterMap reg, uint8_t value) { return this->write_byte(static_cast<uint8_t>(reg), value); }
bool configure_registers_();
bool read_acceleration_data_(float &x, float &y, float &z);
void handle_event_(Event event, bool active, uint32_t now, LazyCallbackManager<void()> &callback);
#ifdef USE_TEXT_SENSOR
void publish_orientation_(RegSixdSrc sixd);
#endif
float offset_x_{0}, offset_y_{0}, offset_z_{0};
float scale_{0}; // m/s² per LSB after shift_
uint32_t last_event_ms_[EVENT_COUNT]{};
uint32_t last_poll_ms_{0};
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *event_binary_sensors_[EVENT_COUNT]{};
#endif
LazyCallbackManager<void()> tap_callback_;
LazyCallbackManager<void()> double_tap_callback_;
LazyCallbackManager<void()> freefall_callback_;
LazyCallbackManager<void()> active_callback_;
LazyCallbackManager<void()> orientation_callback_;
PowerMode power_mode_{PowerMode::HIGH_PERF};
DataRate data_rate_{DataRate::RATE_100HZ};
Range range_{Range::RANGE_2G};
FilterBandwidth filter_bandwidth_{FilterBandwidth::BW_ODR_DIV_2};
bool low_noise_{false};
bool mirror_x_{false}, mirror_y_{false}, mirror_z_{false}, swap_xy_{false};
uint8_t shift_{2}; // data is left-justified: 14-bit >> 2, 12-bit (LP1) >> 4
uint8_t events_on_{0}; // bit per Event: binary sensor currently published on
#ifdef USE_TEXT_SENSOR
uint8_t sixd_last_{0xFF}; // position bits only; 0xFF cannot occur, so the first read publishes
#endif
};
} // namespace esphome::lis2dw12
+43
View File
@@ -0,0 +1,43 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ACCELERATION_X,
CONF_ACCELERATION_Y,
CONF_ACCELERATION_Z,
CONF_NAME,
ICON_ACCELERATION,
STATE_CLASS_MEASUREMENT,
UNIT_METER_PER_SECOND_SQUARED,
)
from esphome.types import ConfigType
from . import CONF_LIS2DW12_ID, LIS2DW12_SENSOR_SCHEMA
CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["lis2dw12"]
ACCELERATION_SENSORS = (CONF_ACCELERATION_X, CONF_ACCELERATION_Y, CONF_ACCELERATION_Z)
accel_schema = cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED,
icon=ICON_ACCELERATION,
accuracy_decimals=2,
state_class=STATE_CLASS_MEASUREMENT,
),
key=CONF_NAME,
)
CONFIG_SCHEMA = LIS2DW12_SENSOR_SCHEMA.extend(
{cv.Optional(key): accel_schema for key in ACCELERATION_SENSORS}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LIS2DW12_ID])
sensors = sensor.sub_sensors(config)
await sensors(CONF_ACCELERATION_X, hub.set_acceleration_x_sensor)
await sensors(CONF_ACCELERATION_Y, hub.set_acceleration_y_sensor)
await sensors(CONF_ACCELERATION_Z, hub.set_acceleration_z_sensor)
@@ -0,0 +1,27 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_NAME, ICON_SCREEN_ROTATION
from esphome.types import ConfigType
from . import CONF_LIS2DW12_ID, LIS2DW12_SENSOR_SCHEMA
CODEOWNERS = ["@latonita"]
DEPENDENCIES = ["lis2dw12"]
CONF_ORIENTATION = "orientation"
CONFIG_SCHEMA = LIS2DW12_SENSOR_SCHEMA.extend(
{
cv.Optional(CONF_ORIENTATION): cv.maybe_simple_value(
text_sensor.text_sensor_schema(icon=ICON_SCREEN_ROTATION),
key=CONF_NAME,
),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LIS2DW12_ID])
text_sensors = text_sensor.sub_text_sensors(config)
await text_sensors(CONF_ORIENTATION, hub.set_orientation_text_sensor)
@@ -0,0 +1,35 @@
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.lis2dw12 import (
CONFIG_SCHEMA_BASE,
LIS2DW12Component,
to_code_base,
validate_data_rate,
)
import esphome.config_validation as cv
from esphome.types import ConfigType
CODEOWNERS = ["@latonita"]
AUTO_LOAD = ["lis2dw12"]
DEPENDENCIES = ["i2c"]
DOMAIN = "lis2dw12_i2c"
MULTI_CONF = True
lis2dw12_i2c_ns = cg.esphome_ns.namespace("lis2dw12_i2c")
LIS2DW12I2CComponent = lis2dw12_i2c_ns.class_(
"LIS2DW12I2CComponent", LIS2DW12Component, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA_BASE.extend(
{
cv.GenerateID(): cv.declare_id(LIS2DW12I2CComponent),
}
).extend(i2c.i2c_device_schema(0x19)),
validate_data_rate,
)
async def to_code(config: ConfigType) -> None:
var = await to_code_base(config)
await i2c.register_i2c_device(var, config)
@@ -0,0 +1,13 @@
#include "lis2dw12_i2c.h"
#include "esphome/core/log.h"
namespace esphome::lis2dw12_i2c {
ESPHOME_LOG_TAG(TAG, "lis2dw12_i2c");
void LIS2DW12I2CComponent::dump_config() {
LOG_I2C_DEVICE(this);
lis2dw12::LIS2DW12Component::dump_config();
}
} // namespace esphome::lis2dw12_i2c
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/lis2dw12/lis2dw12.h"
namespace esphome::lis2dw12_i2c {
class LIS2DW12I2CComponent : public lis2dw12::LIS2DW12Component, public i2c::I2CDevice {
public:
bool write_byte(uint8_t a_register, uint8_t data) override { return i2c::I2CDevice::write_byte(a_register, data); }
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
return i2c::I2CDevice::read_bytes(a_register, data, len);
}
void dump_config() override;
};
} // namespace esphome::lis2dw12_i2c
@@ -0,0 +1,37 @@
import esphome.codegen as cg
from esphome.components import spi
from esphome.components.lis2dw12 import (
CONFIG_SCHEMA_BASE,
LIS2DW12Component,
to_code_base,
validate_data_rate,
)
import esphome.config_validation as cv
from esphome.types import ConfigType
CODEOWNERS = ["@latonita"]
AUTO_LOAD = ["lis2dw12"]
DEPENDENCIES = ["spi"]
DOMAIN = "lis2dw12_spi"
MULTI_CONF = True
lis2dw12_spi_ns = cg.esphome_ns.namespace("lis2dw12_spi")
LIS2DW12SPIComponent = lis2dw12_spi_ns.class_(
"LIS2DW12SPIComponent",
LIS2DW12Component,
spi.SPIDevice,
)
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA_BASE.extend(
{
cv.GenerateID(): cv.declare_id(LIS2DW12SPIComponent),
}
).extend(spi.spi_device_schema(cs_pin_required=True)),
validate_data_rate,
)
async def to_code(config: ConfigType) -> None:
var = await to_code_base(config)
await spi.register_spi_device(var, config)
@@ -0,0 +1,34 @@
#include "lis2dw12_spi.h"
#include "esphome/core/log.h"
namespace esphome::lis2dw12_spi {
ESPHOME_LOG_TAG(TAG, "lis2dw12_spi");
void LIS2DW12SPIComponent::setup() {
this->spi_setup();
lis2dw12::LIS2DW12Component::setup();
}
void LIS2DW12SPIComponent::dump_config() {
LOG_SPI_DEVICE(this);
lis2dw12::LIS2DW12Component::dump_config();
}
bool LIS2DW12SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
this->enable();
this->transfer_byte(a_register & 0x3F);
this->transfer_byte(data);
this->disable();
return true;
}
bool LIS2DW12SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(a_register | 0x80);
this->read_array(data, len);
this->disable();
return true;
}
} // namespace esphome::lis2dw12_spi
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/lis2dw12/lis2dw12.h"
#include "esphome/components/spi/spi.h"
namespace esphome::lis2dw12_spi {
class LIS2DW12SPIComponent : public lis2dw12::LIS2DW12Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_1MHZ> {
public:
void setup() override;
void dump_config() override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
};
} // namespace esphome::lis2dw12_spi
+60
View File
@@ -0,0 +1,60 @@
lis2dw12_i2c:
id: lis2dw12_i2c_accel
i2c_id: i2c_bus
range: 4G
power_mode: high_performance
output_data_rate: 100Hz
filter_bandwidth: odr_div_2
low_noise: true
update_interval: 10s
calibration:
offset_x: -0.250
offset_y: -0.400
offset_z: -0.800
transform:
mirror_x: false
mirror_y: true
mirror_z: true
swap_xy: false
on_tap:
- then:
- logger.log: "Tapped"
- then:
- logger.log: "Tapped again"
on_double_tap:
- then:
- logger.log: "Double tapped"
on_freefall:
- then:
- logger.log: "Freefall detected"
on_active:
- then:
- logger.log: "Activity detected"
on_orientation:
- then:
- logger.log: "Orientation changed"
sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_i2c_accel
acceleration_x: Accel X
acceleration_y: Accel Y
acceleration_z: Accel Z
text_sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_i2c_accel
orientation: Orientation
binary_sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_i2c_accel
tap: Single tap
double_tap:
name: Double tap
freefall:
name: Freefall
active:
name: Active
filters:
- delayed_off: 5000ms
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
lis2dw12_i2c: !include common.yaml
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
lis2dw12_i2c: !include common.yaml
@@ -0,0 +1,3 @@
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
lis2dw12_i2c: !include common.yaml
+61
View File
@@ -0,0 +1,61 @@
lis2dw12_spi:
id: lis2dw12_spi_accel
spi_id: spi_bus
cs_pin: GPIO5
range: 4G
power_mode: high_performance
output_data_rate: 100Hz
filter_bandwidth: odr_div_2
low_noise: true
update_interval: 10s
calibration:
offset_x: -0.250
offset_y: -0.400
offset_z: -0.800
transform:
mirror_x: false
mirror_y: true
mirror_z: true
swap_xy: false
on_tap:
- then:
- logger.log: "Tapped"
- then:
- logger.log: "Tapped again"
on_double_tap:
- then:
- logger.log: "Double tapped"
on_freefall:
- then:
- logger.log: "Freefall detected"
on_active:
- then:
- logger.log: "Activity detected"
on_orientation:
- then:
- logger.log: "Orientation changed"
sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_spi_accel
acceleration_x: Accel X
acceleration_y: Accel Y
acceleration_z: Accel Z
text_sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_spi_accel
orientation: Orientation
binary_sensor:
- platform: lis2dw12
lis2dw12_id: lis2dw12_spi_accel
tap: Single tap
double_tap:
name: Double tap
freefall:
name: Freefall
active:
name: Active
filters:
- delayed_off: 5000ms
@@ -0,0 +1,3 @@
packages:
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
lis2dw12_spi: !include common.yaml
@@ -0,0 +1,3 @@
packages:
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
lis2dw12_spi: !include common.yaml
@@ -0,0 +1,3 @@
packages:
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
lis2dw12_spi: !include common.yaml