mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 11:07:33 +00:00
[lsm6ds] Add motion platform for STMicro LSM6DS IMU (#16232)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -291,6 +291,7 @@ esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/logger/select/* @clydebarrow
|
||||
esphome/components/lps22/* @nagisa
|
||||
esphome/components/lsm6ds/* @clydebarrow
|
||||
esphome/components/ltr390/* @latonita @sjtrny
|
||||
esphome/components/ltr501/* @latonita
|
||||
esphome/components/ltr_als_ps/* @latonita
|
||||
|
||||
15
esphome/components/lsm6ds/__init__.py
Normal file
15
esphome/components/lsm6ds/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.motion import MotionComponent
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
CONF_LSM6DS_ID = "lsm6ds_id"
|
||||
# C++ namespace / class
|
||||
|
||||
lsm6ds_ns = cg.esphome_ns.namespace("lsm6ds")
|
||||
LSM6DSComponent = lsm6ds_ns.class_(
|
||||
"LSM6DSComponent",
|
||||
MotionComponent,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
203
esphome/components/lsm6ds/lsm6ds.cpp
Normal file
203
esphome/components/lsm6ds/lsm6ds.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "lsm6ds.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::lsm6ds {
|
||||
|
||||
static const char *const TAG = "lsm6ds";
|
||||
|
||||
static const struct {
|
||||
uint8_t who_am_i;
|
||||
const char *const name;
|
||||
} CHIP_IDS[] = {{0x69, "LSMDSO"}, {0x6A, "LSM6DS3"}};
|
||||
|
||||
void LSM6DSComponent::setup() {
|
||||
MotionComponent::setup();
|
||||
uint8_t who_am_i = 0;
|
||||
if (this->read_register(LSM6DS_REG_WHO_AM_I, &who_am_i, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read WHO_AM_I — check wiring and I2C address");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
const char *chip_name = nullptr;
|
||||
for (const auto &chip : CHIP_IDS) {
|
||||
if (chip.who_am_i == who_am_i) {
|
||||
chip_name = chip.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chip_name == nullptr) {
|
||||
ESP_LOGE(TAG, "Unknown WHO_AM_I: 0x%02X", who_am_i);
|
||||
this->mark_failed(LOG_STR("Unknown WHO_AM_I value"));
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Found %s (WHO_AM_I = 0x%02X)", chip_name, who_am_i);
|
||||
this->chip_name_ = chip_name;
|
||||
|
||||
// 2. Software reset — clears all registers to defaults
|
||||
if (this->write_register(LSM6DS_REG_CTRL3_C, &CTRL3_C_SW_RESET, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR("Software reset failed"));
|
||||
return;
|
||||
}
|
||||
// Datasheet: reset bit self-clears after boot (typ. 50 µs);
|
||||
delay(2);
|
||||
|
||||
// 3. Enable auto-increment and block data update (BDU).
|
||||
// BDU prevents reading a high-byte from one sample and a low-byte from the next.
|
||||
// IF_INC is set by default after reset but we set it explicitly for clarity.
|
||||
uint8_t ctrl3 = CTRL3_C_IF_INC | CTRL3_C_BDU;
|
||||
if (this->write_register(LSM6DS_REG_CTRL3_C, &ctrl3, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR("Config failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Configure accelerometer: ODR in bits[7:4], FS in bits[3:2]
|
||||
// Anti-aliasing filter bandwidth is left at power-on default (bits[1:0] = 00 = ODR/2).
|
||||
uint8_t ctrl1_xl = (uint8_t) (this->accel_odr_ << 4) | (uint8_t) (this->accel_range_ << 2);
|
||||
if (this->write_register(LSM6DS_REG_CTRL1_XL, &ctrl1_xl, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR("Failed to configure accelerometer"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Configure gyroscope: ODR in bits[7:4], FS_G + FS_125 in bits[3:0]
|
||||
// For ±125 dps: FS_G[2:1]=00 and FS_125(bit1)=1, so gyro_range_ encodes the full nibble.
|
||||
uint8_t ctrl2_g = (uint8_t) (this->gyro_odr_ << 4) | (uint8_t) (this->gyro_range_);
|
||||
if (this->write_register(LSM6DS_REG_CTRL2_G, &ctrl2_g, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed(LOG_STR("Failed to configure gyroscope"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Ensure accelerometer is in high-performance mode (CTRL6_C bit 4 = XL_HM_MODE = 0)
|
||||
// and gyroscope is in high-performance mode (CTRL7_G bit 7 = G_HM_MODE = 0).
|
||||
// Both default to 0 (high-performance) after reset, but write explicitly.
|
||||
uint8_t zero = 0x00;
|
||||
if (this->write_register(LSM6DS_REG_CTRL6_C, &zero, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->write_register(LSM6DS_REG_CTRL7_G, &zero, 1) != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void LSM6DSComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LSM6DS IMU:\n"
|
||||
" Chip type: %s\n",
|
||||
this->chip_name_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
// Accel range — index into the sensitivity table (datasheet Table 3)
|
||||
static const char *const ACCEL_RANGE_STR[] = {"±2g", "±16g", "±4g", "±8g"};
|
||||
|
||||
const char *gyro_str;
|
||||
switch (this->gyro_range_) {
|
||||
case LSM6DS_GYRO_RANGE_125:
|
||||
gyro_str = "±125dps";
|
||||
break;
|
||||
case LSM6DS_GYRO_RANGE_250:
|
||||
gyro_str = "±250dps";
|
||||
break;
|
||||
case LSM6DS_GYRO_RANGE_500:
|
||||
gyro_str = "±500dps";
|
||||
break;
|
||||
case LSM6DS_GYRO_RANGE_1000:
|
||||
gyro_str = "±1000dps";
|
||||
break;
|
||||
case LSM6DS_GYRO_RANGE_2000:
|
||||
gyro_str = "±2000dps";
|
||||
break;
|
||||
default:
|
||||
gyro_str = "unknown";
|
||||
break;
|
||||
}
|
||||
auto accel_odr = this->accel_odr_ == 0 ? 0 : 13 * (1 << (this->accel_odr_ - 1));
|
||||
auto gyro_odr = this->gyro_odr_ == 0 ? 0 : 13 * (1 << (this->gyro_odr_ - 1));
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Accel range : %s\n"
|
||||
" Accel data rate : %dHz\n"
|
||||
" Gyro range : %s\n"
|
||||
" Gyro data rate : %dHz",
|
||||
ACCEL_RANGE_STR[this->accel_range_], accel_odr, gyro_str, gyro_odr);
|
||||
}
|
||||
|
||||
// update_data()
|
||||
// Called by MotionComponent::update() on each polling interval.
|
||||
// Reads gyro XYZ and accel XYZ in a single 12-byte burst (registers 0x22–0x2D).
|
||||
// Values are in g (accel) and °/s (gyro) — MotionComponent handles axis mapping
|
||||
// and sensor publishing.
|
||||
|
||||
bool LSM6DSComponent::update_data(motion::MotionData &data) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
// Single burst: gyro X/Y/Z (0x22–0x27) then accel X/Y/Z (0x28–0x2D)
|
||||
uint8_t raw[LSM6DS_BURST_LEN];
|
||||
if (!this->read_bytes(LSM6DS_REG_OUTX_L_G, raw, LSM6DS_BURST_LEN)) {
|
||||
this->status_set_error(LOG_STR("Failed to read IMU data"));
|
||||
return false;
|
||||
}
|
||||
this->status_clear_error();
|
||||
|
||||
// Gyroscope
|
||||
// Sensitivity (mdps/LSB) from datasheet Table 3.
|
||||
// Multiply by 1e-3 to convert mdps → dps (°/s).
|
||||
static constexpr float GYRO_SCALE[] = {
|
||||
8.75e-3f, // 0x00 — ±250 dps
|
||||
8.75e-3f, // 0x01 — unused (maps to 250 as fallback)
|
||||
4.375e-3f, // 0x02 — ±125 dps (FS_125 bit set)
|
||||
8.75e-3f, // 0x03 — unused
|
||||
17.50e-3f, // 0x04 — ±500 dps
|
||||
17.50e-3f, // 0x05 — unused
|
||||
8.75e-3f, // 0x06 — unused
|
||||
8.75e-3f, // 0x07 — unused
|
||||
35.0e-3f, // 0x08 — ±1000 dps
|
||||
35.0e-3f, // 0x09 — unused
|
||||
17.50e-3f, // 0x0A — unused
|
||||
17.50e-3f, // 0x0B — unused
|
||||
70.0e-3f, // 0x0C — ±2000 dps
|
||||
};
|
||||
float gyro_scale = GYRO_SCALE[this->gyro_range_];
|
||||
|
||||
data.angular_rate[motion::X_AXIS] = (int16_t) ((raw[1] << 8) | raw[0]) * gyro_scale;
|
||||
data.angular_rate[motion::Y_AXIS] = (int16_t) ((raw[3] << 8) | raw[2]) * gyro_scale;
|
||||
data.angular_rate[motion::Z_AXIS] = (int16_t) ((raw[5] << 8) | raw[4]) * gyro_scale;
|
||||
|
||||
// Accelerometer
|
||||
// Sensitivity (mg/LSB) from datasheet Table 3.
|
||||
// Multiply by 1e-3 to convert mg → g.
|
||||
// Note: FS_XL register values are non-monotonic (0=2g, 1=16g, 2=4g, 3=8g).
|
||||
static constexpr float ACCEL_SCALE[] = {
|
||||
0.061e-3f, // 0x00 — ±2g
|
||||
0.488e-3f, // 0x01 — ±16g
|
||||
0.122e-3f, // 0x02 — ±4g
|
||||
0.244e-3f, // 0x03 — ±8g
|
||||
};
|
||||
float accel_scale = ACCEL_SCALE[this->accel_range_];
|
||||
|
||||
data.acceleration[motion::X_AXIS] =
|
||||
(int16_t) ((raw[LSM6DS_ACCEL_OFFSET + 1] << 8) | raw[LSM6DS_ACCEL_OFFSET + 0]) * accel_scale;
|
||||
data.acceleration[motion::Y_AXIS] =
|
||||
(int16_t) ((raw[LSM6DS_ACCEL_OFFSET + 3] << 8) | raw[LSM6DS_ACCEL_OFFSET + 2]) * accel_scale;
|
||||
data.acceleration[motion::Z_AXIS] =
|
||||
(int16_t) ((raw[LSM6DS_ACCEL_OFFSET + 5] << 8) | raw[LSM6DS_ACCEL_OFFSET + 4]) * accel_scale;
|
||||
|
||||
// Temperature (lazy — only read if a listener is registered)
|
||||
// Kept as a separate 2-byte read to avoid extending the burst to 14 bytes when
|
||||
// temperature is not needed.
|
||||
// Formula: T(°C) = (raw / 256.0) + 25.0 (datasheet Table 90, OUT_TEMP register)
|
||||
if (!this->temperature_callback_.empty()) {
|
||||
uint8_t raw_t[2];
|
||||
if (this->read_bytes(LSM6DS_REG_OUT_TEMP_L, raw_t, 2)) {
|
||||
int16_t temp_raw = (int16_t) ((raw_t[1] << 8) | raw_t[0]);
|
||||
float temperature = (temp_raw / 256.0f) + 25.0f;
|
||||
this->temperature_callback_.call(temperature);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::lsm6ds
|
||||
111
esphome/components/lsm6ds/lsm6ds.h
Normal file
111
esphome/components/lsm6ds/lsm6ds.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/motion/motion_component.h"
|
||||
|
||||
namespace esphome::lsm6ds {
|
||||
|
||||
// ── Register map (datasheet DocID030071 Rev 3, Table 19) ────────────────────
|
||||
static const uint8_t LSM6DS_REG_WHO_AM_I = 0x0F;
|
||||
static const uint8_t LSM6DS_REG_CTRL1_XL = 0x10; // Accel ODR + FS
|
||||
static const uint8_t LSM6DS_REG_CTRL2_G = 0x11; // Gyro ODR + FS
|
||||
static const uint8_t LSM6DS_REG_CTRL3_C = 0x12; // SW_RESET, BDU, IF_INC
|
||||
static const uint8_t LSM6DS_REG_CTRL6_C = 0x15; // Accel HP disable, Gyro LPF1
|
||||
static const uint8_t LSM6DS_REG_CTRL7_G = 0x16; // Gyro HP disable
|
||||
static const uint8_t LSM6DS_REG_STATUS = 0x1E; // XLDA, GDA, TDA
|
||||
static const uint8_t LSM6DS_REG_OUT_TEMP_L = 0x20; // Temperature LSB
|
||||
static const uint8_t LSM6DS_REG_OUTX_L_G = 0x22; // Gyro X LSB (burst start)
|
||||
static const uint8_t LSM6DS_REG_OUTX_L_XL = 0x28; // Accel X LSB
|
||||
|
||||
// Burst read from 0x22 to 0x2D inclusive: gyro XYZ (6 bytes) + accel XYZ (6 bytes)
|
||||
static const uint8_t LSM6DS_BURST_LEN = 12;
|
||||
static const uint8_t LSM6DS_ACCEL_OFFSET = 6; // 0x28 - 0x22
|
||||
|
||||
// ── CTRL3_C bit fields ───────────────────────────────────────────────────────
|
||||
static const uint8_t CTRL3_C_SW_RESET = (1 << 0);
|
||||
static const uint8_t CTRL3_C_IF_INC = (1 << 2); // auto-increment address on burst (default 1)
|
||||
static const uint8_t CTRL3_C_BDU = (1 << 6); // block data update
|
||||
|
||||
// ── Accelerometer full-scale range ──────────────────────────────────────────
|
||||
// CTRL1_XL bits [3:2] — FS_XL[1:0]
|
||||
// Note: 0x01 = ±16g is intentional per Table 52 — the mapping is non-monotonic
|
||||
enum LSM6DSAccelRange : uint8_t {
|
||||
LSM6DS_ACCEL_RANGE_2G = 0x00, // ±2 g, 0.061 mg/LSB
|
||||
LSM6DS_ACCEL_RANGE_16G = 0x01, // ±16 g, 0.488 mg/LSB
|
||||
LSM6DS_ACCEL_RANGE_4G = 0x02, // ±4 g, 0.122 mg/LSB
|
||||
LSM6DS_ACCEL_RANGE_8G = 0x03, // ±8 g, 0.244 mg/LSB
|
||||
};
|
||||
|
||||
// ── Accelerometer output data rate ──────────────────────────────────────────
|
||||
// CTRL1_XL bits [7:4] — ODR_XL[3:0]
|
||||
enum LSM6DSAccelODR : uint8_t {
|
||||
LSM6DS_ACCEL_ODR_OFF = 0x00,
|
||||
LSM6DS_ACCEL_ODR_12_5 = 0x01, // 12.5 Hz
|
||||
LSM6DS_ACCEL_ODR_26 = 0x02, // 26 Hz
|
||||
LSM6DS_ACCEL_ODR_52 = 0x03, // 52 Hz
|
||||
LSM6DS_ACCEL_ODR_104 = 0x04, // 104 Hz
|
||||
LSM6DS_ACCEL_ODR_208 = 0x05, // 208 Hz
|
||||
LSM6DS_ACCEL_ODR_416 = 0x06, // 416 Hz
|
||||
LSM6DS_ACCEL_ODR_833 = 0x07, // 833 Hz
|
||||
LSM6DS_ACCEL_ODR_1666 = 0x08, // 1666 Hz
|
||||
LSM6DS_ACCEL_ODR_3332 = 0x09, // 3332 Hz
|
||||
LSM6DS_ACCEL_ODR_6664 = 0x0A, // 6664 Hz
|
||||
};
|
||||
|
||||
// ── Gyroscope full-scale range ───────────────────────────────────────────────
|
||||
// CTRL2_G bits [3:0] — FS_G[2:1] and FS_125 (bit 1)
|
||||
// The FS_125 bit (bit 1) enables the ±125 dps range independently of FS_G.
|
||||
// For all other ranges, bits [3:2] select the range and bit 1 = 0.
|
||||
enum LSM6DSGyroRange : uint8_t {
|
||||
LSM6DS_GYRO_RANGE_125 = 0x02, // ±125 dps, 4.375 mdps/LSB (FS_125=1)
|
||||
LSM6DS_GYRO_RANGE_250 = 0x00, // ±250 dps, 8.75 mdps/LSB
|
||||
LSM6DS_GYRO_RANGE_500 = 0x04, // ±500 dps, 17.50 mdps/LSB
|
||||
LSM6DS_GYRO_RANGE_1000 = 0x08, // ±1000 dps, 35 mdps/LSB
|
||||
LSM6DS_GYRO_RANGE_2000 = 0x0C, // ±2000 dps, 70 mdps/LSB
|
||||
};
|
||||
|
||||
// ── Gyroscope output data rate ───────────────────────────────────────────────
|
||||
// CTRL2_G bits [7:4] — ODR_G[3:0]
|
||||
enum LSM6DSGyroODR : uint8_t {
|
||||
LSM6DS_GYRO_ODR_OFF = 0x00,
|
||||
LSM6DS_GYRO_ODR_12_5 = 0x01, // 12.5 Hz
|
||||
LSM6DS_GYRO_ODR_26 = 0x02, // 26 Hz
|
||||
LSM6DS_GYRO_ODR_52 = 0x03, // 52 Hz
|
||||
LSM6DS_GYRO_ODR_104 = 0x04, // 104 Hz
|
||||
LSM6DS_GYRO_ODR_208 = 0x05, // 208 Hz
|
||||
LSM6DS_GYRO_ODR_416 = 0x06, // 416 Hz
|
||||
LSM6DS_GYRO_ODR_833 = 0x07, // 833 Hz
|
||||
LSM6DS_GYRO_ODR_1666 = 0x08, // 1666 Hz
|
||||
LSM6DS_GYRO_ODR_3332 = 0x09, // 3332 Hz
|
||||
LSM6DS_GYRO_ODR_6664 = 0x0A, // 6664 Hz
|
||||
};
|
||||
|
||||
// ── Main component class ─────────────────────────────────────────────────────
|
||||
class LSM6DSComponent : public motion::MotionComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
// Configuration setters (called from Python codegen)
|
||||
void set_accel_range(LSM6DSAccelRange r) { this->accel_range_ = r; }
|
||||
void set_accel_odr(LSM6DSAccelODR o) { this->accel_odr_ = o; }
|
||||
void set_gyro_range(LSM6DSGyroRange r) { this->gyro_range_ = r; }
|
||||
void set_gyro_odr(LSM6DSGyroODR o) { this->gyro_odr_ = o; }
|
||||
|
||||
template<typename F> void add_temperature_listener(F &&cb) { this->temperature_callback_.add(std::forward<F>(cb)); }
|
||||
|
||||
protected:
|
||||
const char *chip_name_{"Unknown"};
|
||||
bool update_data(motion::MotionData &data) override;
|
||||
|
||||
LSM6DSAccelRange accel_range_{LSM6DS_ACCEL_RANGE_4G};
|
||||
LSM6DSAccelODR accel_odr_{LSM6DS_ACCEL_ODR_104};
|
||||
LSM6DSGyroRange gyro_range_{LSM6DS_GYRO_RANGE_2000};
|
||||
LSM6DSGyroODR gyro_odr_{LSM6DS_GYRO_ODR_208};
|
||||
|
||||
LazyCallbackManager<void(float)> temperature_callback_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::lsm6ds
|
||||
106
esphome/components/lsm6ds/motion.py
Normal file
106
esphome/components/lsm6ds/motion.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c
|
||||
from esphome.components.const import (
|
||||
CONF_ACCELEROMETER_ODR,
|
||||
CONF_ACCELEROMETER_RANGE,
|
||||
CONF_GYROSCOPE_ODR,
|
||||
CONF_GYROSCOPE_RANGE,
|
||||
)
|
||||
from esphome.components.motion import motion_schema, new_motion_component
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from . import LSM6DSComponent, lsm6ds_ns
|
||||
|
||||
# ── Dependency declarations ──────────────────────────────────────────────────
|
||||
DEPENDENCIES = ["i2c"]
|
||||
DOMAIN = "lsm6ds"
|
||||
|
||||
# ── C++ namespace / class ────────────────────────────────────────────────────
|
||||
# ── Enum proxies ─────────────────────────────────────────────────────────────
|
||||
LSM6DSAccelRange = lsm6ds_ns.enum("LSM6DSAccelRange")
|
||||
ACCEL_RANGE_OPTIONS = {
|
||||
"2G": LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_2G,
|
||||
"4G": LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_4G,
|
||||
"8G": LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_8G,
|
||||
"16G": LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_16G,
|
||||
}
|
||||
|
||||
LSM6DSAccelODR = lsm6ds_ns.enum("LSM6DSAccelODR")
|
||||
ACCEL_ODR_OPTIONS = {
|
||||
"OFF": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_OFF,
|
||||
"12_5HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_12_5,
|
||||
"26HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_26,
|
||||
"52HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_52,
|
||||
"104HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_104,
|
||||
"208HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_208,
|
||||
"416HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_416,
|
||||
"833HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_833,
|
||||
"1666HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_1666,
|
||||
"3332HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_3332,
|
||||
"6664HZ": LSM6DSAccelODR.LSM6DS_ACCEL_ODR_6664,
|
||||
}
|
||||
|
||||
LSM6DSGyroRange = lsm6ds_ns.enum("LSM6DSGyroRange")
|
||||
GYRO_RANGE_OPTIONS = {
|
||||
"125DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_125,
|
||||
"250DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_250,
|
||||
"500DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_500,
|
||||
"1000DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_1000,
|
||||
"2000DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_2000,
|
||||
}
|
||||
|
||||
LSM6DSGyroODR = lsm6ds_ns.enum("LSM6DSGyroODR")
|
||||
GYRO_ODR_OPTIONS = {
|
||||
"OFF": LSM6DSGyroODR.LSM6DS_GYRO_ODR_OFF,
|
||||
"12_5HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_12_5,
|
||||
"26HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_26,
|
||||
"52HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_52,
|
||||
"104HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_104,
|
||||
"208HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_208,
|
||||
"416HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_416,
|
||||
"833HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_833,
|
||||
"1666HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_1666,
|
||||
"3332HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_3332,
|
||||
"6664HZ": LSM6DSGyroODR.LSM6DS_GYRO_ODR_6664,
|
||||
}
|
||||
|
||||
# ── CONFIG_SCHEMA ─────────────────────────────────────────────────────────────
|
||||
# Extend the motion platform schema which provides:
|
||||
# - accel_x/y/z sensor schemas
|
||||
# - gyro_x/y/z sensor schemas
|
||||
# - axis_mapping schema + validation
|
||||
# - update_interval / polling
|
||||
CONFIG_SCHEMA = (
|
||||
motion_schema(LSM6DSComponent, has_accel=True, has_gyro=True)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_ACCELEROMETER_RANGE, default="4G"): cv.enum(
|
||||
ACCEL_RANGE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ACCELEROMETER_ODR, default="104HZ"): cv.enum(
|
||||
ACCEL_ODR_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_GYROSCOPE_RANGE, default="2000DPS"): cv.enum(
|
||||
GYRO_RANGE_OPTIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_GYROSCOPE_ODR, default="208HZ"): cv.enum(
|
||||
GYRO_ODR_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x6A))
|
||||
)
|
||||
|
||||
|
||||
# ── Code generation ──────────────────────────────────────────────────────────
|
||||
async def to_code(config):
|
||||
var = await new_motion_component(config)
|
||||
|
||||
# Let the motion platform handle sensor wiring, axis mapping, and polling
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
# Chip-specific hardware configuration
|
||||
cg.add(var.set_accel_range(config[CONF_ACCELEROMETER_RANGE]))
|
||||
cg.add(var.set_accel_odr(config[CONF_ACCELEROMETER_ODR]))
|
||||
cg.add(var.set_gyro_range(config[CONF_GYROSCOPE_RANGE]))
|
||||
cg.add(var.set_gyro_odr(config[CONF_GYROSCOPE_ODR]))
|
||||
39
esphome/components/lsm6ds/sensor.py
Normal file
39
esphome/components/lsm6ds/sensor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# YAML config keys
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_THERMOMETER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
from . import CONF_LSM6DS_ID, LSM6DSComponent
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_TYPE): CONF_TEMPERATURE,
|
||||
cv.GenerateID(CONF_LSM6DS_ID): cv.use_id(LSM6DSComponent),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
parent = await cg.get_variable(config[CONF_LSM6DS_ID])
|
||||
data = MockObj("data")
|
||||
value_lambda = await cg.process_lambda(
|
||||
var.publish_state(data),
|
||||
[(cg.float_, str(data))],
|
||||
)
|
||||
cg.add(parent.add_temperature_listener(value_lambda))
|
||||
63
tests/components/lsm6ds/common.yaml
Normal file
63
tests/components/lsm6ds/common.yaml
Normal file
@@ -0,0 +1,63 @@
|
||||
sensor:
|
||||
- platform: lsm6ds
|
||||
name: "lsm6ds Temperature"
|
||||
|
||||
- platform: motion
|
||||
type: acceleration_x
|
||||
name: "Accel X"
|
||||
accuracy_decimals: 4
|
||||
filters:
|
||||
- sliding_window_moving_average:
|
||||
window_size: 4
|
||||
send_every: 1
|
||||
- platform: motion
|
||||
type: acceleration_y
|
||||
name: "Accel Y"
|
||||
accuracy_decimals: 4
|
||||
- platform: motion
|
||||
type: acceleration_z
|
||||
name: "Accel Z"
|
||||
accuracy_decimals: 4
|
||||
|
||||
# Gyroscope axes (unit: °/s)
|
||||
- platform: motion
|
||||
type: gyroscope_x
|
||||
name: "Gyro X"
|
||||
- platform: motion
|
||||
type: gyroscope_y
|
||||
name: "Gyro Y"
|
||||
- platform: motion
|
||||
type: gyroscope_z
|
||||
name: "Gyro Z"
|
||||
|
||||
- platform: motion
|
||||
type: angular_rate_x
|
||||
name: "Angular Rate X"
|
||||
- platform: motion
|
||||
type: angular_rate_y
|
||||
name: "Angular Rate Y"
|
||||
- platform: motion
|
||||
type: angular_rate_z
|
||||
name: "Angular Rate Z"
|
||||
|
||||
- platform: motion
|
||||
type: pitch
|
||||
name: "Pitch"
|
||||
- platform: motion
|
||||
type: roll
|
||||
name: "Roll"
|
||||
|
||||
motion:
|
||||
- platform: lsm6ds
|
||||
# Accelerometer full-scale range: 2G | 4G | 8G | 16G
|
||||
accelerometer_range: 4G
|
||||
|
||||
accelerometer_odr: 104HZ
|
||||
|
||||
gyroscope_range: 2000DPS
|
||||
|
||||
gyroscope_odr: 208HZ
|
||||
axis_map:
|
||||
x: y
|
||||
y: x
|
||||
z: -z
|
||||
4
tests/components/lsm6ds/test.esp32-idf.yaml
Normal file
4
tests/components/lsm6ds/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user