diff --git a/esphome/components/COMPONENT_STRUCTURE.md b/esphome/components/COMPONENT_STRUCTURE.md new file mode 100644 index 000000000..f056aa466 --- /dev/null +++ b/esphome/components/COMPONENT_STRUCTURE.md @@ -0,0 +1,124 @@ +# LSM6DS Component Structure + +This document describes the organization of the LSM6DS family IMU sensor components for ESPHome. + +## Folder Structure + +``` +esphome/components/ +├── lsm6ds_base/ # Base component (shared code and logic) +│ ├── __init__.py # Base schema, enums, configuration, register_lsm6ds() +│ ├── const.py # Constants: register addresses, WHO_AM_I values, CONF_* keys +│ ├── lsm6ds.h # Base C++ class: LSM6DSComponent (abstract) +│ └── lsm6ds.cpp # Base C++ implementation: setup(), update(), dump_config() +│ +├── lsm6ds_i2c/ # I2C interface implementation +│ ├── __init__.py # I2C component registration (AUTO_LOAD: lsm6ds_base) +│ ├── sensor.py # Optional sensor platform (hub + sensor pattern) +│ ├── lsm6ds_i2c.h # LSM6DSI2CDevice class (implements read/write via I2C) +│ ├── lsm6ds_i2c.cpp # I2C implementation (minimal/empty) +│ └── README_EXAMPLE.md # I2C usage examples and configuration guide +│ +└── lsm6ds_spi/ # SPI interface implementation + ├── __init__.py # SPI component registration (AUTO_LOAD: lsm6ds_base) + ├── sensor.py # Optional sensor platform (hub + sensor pattern) + ├── lsm6ds_spi.h # LSM6DSSPIDevice class (implements read/write via SPI) + ├── lsm6ds_spi.cpp # SPI implementation (minimal/empty) + └── README_EXAMPLE.md # SPI usage examples and configuration guide +``` + +## Component Relationships + +### Auto-Loading Pattern +Both `lsm6ds_i2c` and `lsm6ds_spi` use `AUTO_LOAD = ["lsm6ds_base"]` to automatically include the base component when either interface variant is used. Users only need to configure `lsm6ds_i2c` or `lsm6ds_spi` directly. + +### Dependencies +- **lsm6ds_base**: No dependencies (standalone base) +- **lsm6ds_i2c**: Depends on `i2c`, auto-loads `lsm6ds_base` +- **lsm6ds_spi**: Depends on `spi`, auto-loads `lsm6ds_base` + +### Class Hierarchy + +``` +C++ Class Structure: + +LSM6DSComponent (abstract base class) + ↑ + ├── LSM6DSI2CDevice : public LSM6DSComponent, public i2c::I2CDevice + │ └── Implements: read_register() and write_register() via I2C + │ + └── LSM6DSSPIDevice : public LSM6DSComponent, public spi::SPIDevice + └── Implements: read_register() and write_register() via SPI +``` + +## Usage Patterns + +### Pattern 1: All-in-One (Recommended for most users) +Configure the sensor with all outputs directly in the component block: + +```yaml +lsm6ds_i2c: # or lsm6ds_spi + id: imu_sensor + # ... interface config (I2C address or SPI CS pin) + accel_range: 4G + gyro_range: 500DPS + accel_odr: 104HZ + gyro_odr: 104HZ + accel_x: + name: "Accel X" + gyro_x: + name: "Gyro X" + # ... other sensors +``` + +### Pattern 2: Hub + Sensor Platform +Configure the hub separately, then add sensors under the sensor platform: + +```yaml +lsm6ds_i2c: # or lsm6ds_spi + id: imu_hub + # ... interface config + accel_range: 4G + gyro_range: 500DPS + +sensor: + - platform: lsm6ds_i2c # or lsm6ds_spi + lsm6ds_id: imu_hub + accel_x: + name: "Accel X" + # ... other sensors +``` + +## Supported Sensors + +- **LSM6DSOX**: WHO_AM_I = 0x6C +- **LSM6DS3TR-C**: WHO_AM_I = 0x6A +- **LSM6DSO32**: WHO_AM_I = 0x69 + +All variants support: +- 3-axis accelerometer (±2G, ±4G, ±8G, ±16G) +- 3-axis gyroscope (±125, ±250, ±500, ±1000, ±2000 DPS) +- Temperature sensor +- Configurable output data rate (12.5Hz to 6.66kHz) +- Both I2C and SPI interfaces + +## Code Generation Flow + +1. User configures `lsm6ds_i2c` or `lsm6ds_spi` in YAML +2. ESPHome auto-loads `lsm6ds_base` component +3. Python `__init__.py` validates config and generates C++ code +4. `register_lsm6ds()` function: + - Registers component with ESPHome + - Sets range and ODR configuration + - Creates sensor entities for requested outputs +5. C++ `setup()` method: + - Detects sensor variant via WHO_AM_I + - Configures sensor registers + - Calculates sensitivity values +6. C++ `update()` method: + - Reads sensor data + - Publishes to sensor entities + +## CODEOWNERS + +All three components list `@NonaSuomy` as the code owner for maintenance and review. diff --git a/esphome/components/lsm6ds/__init__.py b/esphome/components/lsm6ds/__init__.py deleted file mode 100644 index 014d72b3b..000000000 --- a/esphome/components/lsm6ds/__init__.py +++ /dev/null @@ -1,119 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import i2c, spi -from esphome.const import CONF_ID - -from .const import ( - lsm6ds_ns, - LSM6DSComponent, - CONF_ACCEL_RANGE, - CONF_GYRO_RANGE, - CONF_ACCEL_ODR, - CONF_GYRO_ODR, - LSM6DS_ACCEL_RANGE_2G, - LSM6DS_ACCEL_RANGE_16G, - LSM6DS_ACCEL_RANGE_4G, - LSM6DS_ACCEL_RANGE_8G, - LSM6DS_GYRO_RANGE_250DPS, - LSM6DS_GYRO_RANGE_500DPS, - LSM6DS_GYRO_RANGE_1000DPS, - LSM6DS_GYRO_RANGE_2000DPS, - LSM6DS_GYRO_RANGE_125DPS, - LSM6DS_ACCEL_ODR_OFF, - LSM6DS_ACCEL_ODR_12_5HZ, - LSM6DS_ACCEL_ODR_26HZ, - LSM6DS_ACCEL_ODR_52HZ, - LSM6DS_ACCEL_ODR_104HZ, - LSM6DS_ACCEL_ODR_208HZ, - LSM6DS_ACCEL_ODR_416HZ, - LSM6DS_ACCEL_ODR_833HZ, - LSM6DS_ACCEL_ODR_1_66KHZ, - LSM6DS_ACCEL_ODR_3_33KHZ, - LSM6DS_ACCEL_ODR_6_66KHZ, - LSM6DS_GYRO_ODR_OFF, - LSM6DS_GYRO_ODR_12_5HZ, - LSM6DS_GYRO_ODR_26HZ, - LSM6DS_GYRO_ODR_52HZ, - LSM6DS_GYRO_ODR_104HZ, - LSM6DS_GYRO_ODR_208HZ, - LSM6DS_GYRO_ODR_416HZ, - LSM6DS_GYRO_ODR_833HZ, - LSM6DS_GYRO_ODR_1_66KHZ, - LSM6DS_GYRO_ODR_3_33KHZ, - LSM6DS_GYRO_ODR_6_66KHZ, -) - -CODEOWNERS = ["@NonaSuomy"] -DEPENDENCIES = ["i2c"] -AUTO_LOAD = ["sensor"] - -LSM6DSI2CDevice = lsm6ds_ns.class_("LSM6DSI2CDevice", LSM6DSComponent, i2c.I2CDevice) - -ACCEL_RANGES = { - "2G": LSM6DS_ACCEL_RANGE_2G, - "16G": LSM6DS_ACCEL_RANGE_16G, - "4G": LSM6DS_ACCEL_RANGE_4G, - "8G": LSM6DS_ACCEL_RANGE_8G, -} - -GYRO_RANGES = { - "250DPS": LSM6DS_GYRO_RANGE_250DPS, - "500DPS": LSM6DS_GYRO_RANGE_500DPS, - "1000DPS": LSM6DS_GYRO_RANGE_1000DPS, - "2000DPS": LSM6DS_GYRO_RANGE_2000DPS, - "125DPS": LSM6DS_GYRO_RANGE_125DPS, -} - -ACCEL_ODRS = { - "OFF": LSM6DS_ACCEL_ODR_OFF, - "12.5HZ": LSM6DS_ACCEL_ODR_12_5HZ, - "26HZ": LSM6DS_ACCEL_ODR_26HZ, - "52HZ": LSM6DS_ACCEL_ODR_52HZ, - "104HZ": LSM6DS_ACCEL_ODR_104HZ, - "208HZ": LSM6DS_ACCEL_ODR_208HZ, - "416HZ": LSM6DS_ACCEL_ODR_416HZ, - "833HZ": LSM6DS_ACCEL_ODR_833HZ, - "1.66KHZ": LSM6DS_ACCEL_ODR_1_66KHZ, - "3.33KHZ": LSM6DS_ACCEL_ODR_3_33KHZ, - "6.66KHZ": LSM6DS_ACCEL_ODR_6_66KHZ, -} - -GYRO_ODRS = { - "OFF": LSM6DS_GYRO_ODR_OFF, - "12.5HZ": LSM6DS_GYRO_ODR_12_5HZ, - "26HZ": LSM6DS_GYRO_ODR_26HZ, - "52HZ": LSM6DS_GYRO_ODR_52HZ, - "104HZ": LSM6DS_GYRO_ODR_104HZ, - "208HZ": LSM6DS_GYRO_ODR_208HZ, - "416HZ": LSM6DS_GYRO_ODR_416HZ, - "833HZ": LSM6DS_GYRO_ODR_833HZ, - "1.66KHZ": LSM6DS_GYRO_ODR_1_66KHZ, - "3.33KHZ": LSM6DS_GYRO_ODR_3_33KHZ, - "6.66KHZ": LSM6DS_GYRO_ODR_6_66KHZ, -} - -BASE_SCHEMA = cv.Schema({ - cv.Optional(CONF_ACCEL_RANGE, default="2G"): cv.enum(ACCEL_RANGES, upper=True), - cv.Optional(CONF_GYRO_RANGE, default="250DPS"): cv.enum(GYRO_RANGES, upper=True), - cv.Optional(CONF_ACCEL_ODR, default="104HZ"): cv.enum(ACCEL_ODRS, upper=True), - cv.Optional(CONF_GYRO_ODR, default="104HZ"): cv.enum(GYRO_ODRS, upper=True), -}).extend(cv.polling_component_schema("60s")) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(): cv.declare_id(LSM6DSI2CDevice), -}).extend(BASE_SCHEMA).extend(i2c.i2c_device_schema(None)) - -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) - - accel_range = ACCEL_RANGES[config[CONF_ACCEL_RANGE]] - gyro_range = GYRO_RANGES[config[CONF_GYRO_RANGE]] - accel_odr = ACCEL_ODRS[config[CONF_ACCEL_ODR]] - gyro_odr = GYRO_ODRS[config[CONF_GYRO_ODR]] - - cg.add(var.set_accel_range(accel_range)) - cg.add(var.set_gyro_range(gyro_range)) - cg.add(var.set_accel_odr(accel_odr)) - cg.add(var.set_gyro_odr(gyro_odr)) diff --git a/esphome/components/lsm6ds/const.py b/esphome/components/lsm6ds/const.py deleted file mode 100644 index 2ea4cba10..000000000 --- a/esphome/components/lsm6ds/const.py +++ /dev/null @@ -1,70 +0,0 @@ -import esphome.codegen as cg - -# Registers -LSM6DS_WHO_AM_I = 0x0F -LSM6DS_CTRL1_XL = 0x10 -LSM6DS_CTRL2_G = 0x11 -LSM6DS_CTRL3_C = 0x12 -LSM6DS_STATUS_REG = 0x1E -LSM6DS_OUT_TEMP_L = 0x20 -LSM6DS_OUTX_L_G = 0x22 -LSM6DS_OUTX_L_A = 0x28 - -# WHO_AM_I Values for different variants -LSM6DSOX_WHO_AM_I_VALUE = 0x6C -LSM6DS3TR_WHO_AM_I_VALUE = 0x6A -LSM6DSO32TR_WHO_AM_I_VALUE = 0x69 - -lsm6ds_ns = cg.esphome_ns.namespace("lsm6ds") -LSM6DSComponent = lsm6ds_ns.class_("LSM6DSComponent", cg.PollingComponent) - -LSM6DSAccelRange = lsm6ds_ns.enum("LSM6DSAccelRange") -LSM6DS_ACCEL_RANGE_2G = LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_2G -LSM6DS_ACCEL_RANGE_16G = LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_16G -LSM6DS_ACCEL_RANGE_4G = LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_4G -LSM6DS_ACCEL_RANGE_8G = LSM6DSAccelRange.LSM6DS_ACCEL_RANGE_8G - -LSM6DSGyroRange = lsm6ds_ns.enum("LSM6DSGyroRange") -LSM6DS_GYRO_RANGE_250DPS = LSM6DSGyroRange.LSM6DS_GYRO_RANGE_250DPS -LSM6DS_GYRO_RANGE_500DPS = LSM6DSGyroRange.LSM6DS_GYRO_RANGE_500DPS -LSM6DS_GYRO_RANGE_1000DPS = LSM6DSGyroRange.LSM6DS_GYRO_RANGE_1000DPS -LSM6DS_GYRO_RANGE_2000DPS = LSM6DSGyroRange.LSM6DS_GYRO_RANGE_2000DPS -LSM6DS_GYRO_RANGE_125DPS = LSM6DSGyroRange.LSM6DS_GYRO_RANGE_125DPS - -LSM6DSODR = lsm6ds_ns.enum("LSM6DSODR") -# Accelerometer ODR Values (matches ODR_XL[3:0] in CTRL1_XL) -LSM6DS_ACCEL_ODR_OFF = LSM6DSODR.LSM6DS_ODR_OFF -LSM6DS_ACCEL_ODR_12_5HZ = LSM6DSODR.LSM6DS_ODR_12_5HZ -LSM6DS_ACCEL_ODR_26HZ = LSM6DSODR.LSM6DS_ODR_26HZ -LSM6DS_ACCEL_ODR_52HZ = LSM6DSODR.LSM6DS_ODR_52HZ -LSM6DS_ACCEL_ODR_104HZ = LSM6DSODR.LSM6DS_ODR_104HZ -LSM6DS_ACCEL_ODR_208HZ = LSM6DSODR.LSM6DS_ODR_208HZ -LSM6DS_ACCEL_ODR_416HZ = LSM6DSODR.LSM6DS_ODR_416HZ -LSM6DS_ACCEL_ODR_833HZ = LSM6DSODR.LSM6DS_ODR_833HZ -LSM6DS_ACCEL_ODR_1_66KHZ = LSM6DSODR.LSM6DS_ODR_1_66KHZ -LSM6DS_ACCEL_ODR_3_33KHZ = LSM6DSODR.LSM6DS_ODR_3_33KHZ -LSM6DS_ACCEL_ODR_6_66KHZ = LSM6DSODR.LSM6DS_ODR_6_66KHZ - -# Gyroscope ODR Values (matches ODR_G[3:0] in CTRL2_G) -LSM6DS_GYRO_ODR_OFF = LSM6DSODR.LSM6DS_ODR_OFF -LSM6DS_GYRO_ODR_12_5HZ = LSM6DSODR.LSM6DS_ODR_12_5HZ -LSM6DS_GYRO_ODR_26HZ = LSM6DSODR.LSM6DS_ODR_26HZ -LSM6DS_GYRO_ODR_52HZ = LSM6DSODR.LSM6DS_ODR_52HZ -LSM6DS_GYRO_ODR_104HZ = LSM6DSODR.LSM6DS_ODR_104HZ -LSM6DS_GYRO_ODR_208HZ = LSM6DSODR.LSM6DS_ODR_208HZ -LSM6DS_GYRO_ODR_416HZ = LSM6DSODR.LSM6DS_ODR_416HZ -LSM6DS_GYRO_ODR_833HZ = LSM6DSODR.LSM6DS_ODR_833HZ -LSM6DS_GYRO_ODR_1_66KHZ = LSM6DSODR.LSM6DS_ODR_1_66KHZ -LSM6DS_GYRO_ODR_3_33KHZ = LSM6DSODR.LSM6DS_ODR_3_33KHZ -LSM6DS_GYRO_ODR_6_66KHZ = LSM6DSODR.LSM6DS_ODR_6_66KHZ - -CONF_ACCEL_X = "accel_x" -CONF_ACCEL_Y = "accel_y" -CONF_ACCEL_Z = "accel_z" -CONF_GYRO_X = "gyro_x" -CONF_GYRO_Y = "gyro_y" -CONF_GYRO_Z = "gyro_z" -CONF_ACCEL_RANGE = "accel_range" -CONF_GYRO_RANGE = "gyro_range" -CONF_ACCEL_ODR = "accel_odr" -CONF_GYRO_ODR = "gyro_odr" diff --git a/esphome/components/lsm6ds/sensor.py b/esphome/components/lsm6ds/sensor.py deleted file mode 100644 index 0205c8b9e..000000000 --- a/esphome/components/lsm6ds/sensor.py +++ /dev/null @@ -1,74 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import sensor -from esphome.const import ( - STATE_CLASS_MEASUREMENT, - UNIT_METER_PER_SECOND_SQUARED, - UNIT_DEGREE_PER_SECOND, - UNIT_CELSIUS, - ICON_ACCELERATION, - ICON_THERMOMETER, - ICON_SCREEN_ROTATION, - CONF_ID, -) -from .const import ( - CONF_ACCEL_X, CONF_ACCEL_Y, CONF_ACCEL_Z, - CONF_GYRO_X, CONF_GYRO_Y, CONF_GYRO_Z, - LSM6DSComponent, -) - -CONFIG_SCHEMA = cv.Schema({ - cv.GenerateID(CONF_ID): cv.use_id(LSM6DSComponent), - cv.Optional(CONF_ACCEL_X): sensor.sensor_schema( - unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, - icon=ICON_ACCELERATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ACCEL_Y): sensor.sensor_schema( - unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, - icon=ICON_ACCELERATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ACCEL_Z): sensor.sensor_schema( - unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, - icon=ICON_ACCELERATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_GYRO_X): sensor.sensor_schema( - unit_of_measurement=UNIT_DEGREE_PER_SECOND, - icon=ICON_SCREEN_ROTATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_GYRO_Y): sensor.sensor_schema( - unit_of_measurement=UNIT_DEGREE_PER_SECOND, - icon=ICON_SCREEN_ROTATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_GYRO_Z): sensor.sensor_schema( - unit_of_measurement=UNIT_DEGREE_PER_SECOND, - icon=ICON_SCREEN_ROTATION, - accuracy_decimals=2, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional("temperature"): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, - accuracy_decimals=1, - state_class=STATE_CLASS_MEASUREMENT, - ), -}) - -async def to_code(config): - hub = await cg.get_variable(config[CONF_ID]) - - for key in [CONF_ACCEL_X, CONF_ACCEL_Y, CONF_ACCEL_Z, - CONF_GYRO_X, CONF_GYRO_Y, CONF_GYRO_Z, - "temperature"]: - if key in config: - sens = await sensor.new_sensor(config[key]) - cg.add(getattr(hub, f"set_{key}_sensor")(sens)) diff --git a/esphome/components/lsm6ds_base/__init__.py b/esphome/components/lsm6ds_base/__init__.py new file mode 100644 index 000000000..411d61806 --- /dev/null +++ b/esphome/components/lsm6ds_base/__init__.py @@ -0,0 +1,128 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + ICON_SCREEN_ROTATION, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DEGREE_PER_SECOND, + UNIT_METER_PER_SECOND_SQUARED, +) + +from .const import ( + CONF_ACCEL_ODR, + CONF_ACCEL_RANGE, + CONF_ACCEL_X, + CONF_ACCEL_Y, + CONF_ACCEL_Z, + CONF_GYRO_ODR, + CONF_GYRO_RANGE, + CONF_GYRO_X, + CONF_GYRO_Y, + CONF_GYRO_Z, +) + +CODEOWNERS = ["@NonaSuomy"] + +# Define the C++ namespace +lsm6ds_ns = cg.esphome_ns.namespace("lsm6ds_base") +LSM6DSComponent = lsm6ds_ns.class_("LSM6DSComponent", cg.PollingComponent) + +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, +} + +LSM6DSGyroRange = lsm6ds_ns.enum("LSM6DSGyroRange") +GYRO_RANGE_OPTIONS = { + "125DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_125DPS, + "250DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_250DPS, + "500DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_500DPS, + "1000DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_1000DPS, + "2000DPS": LSM6DSGyroRange.LSM6DS_GYRO_RANGE_2000DPS, +} + +LSM6DSODR = lsm6ds_ns.enum("LSM6DSODR") +ODR_OPTIONS = { + "12.5HZ": LSM6DSODR.LSM6DS_ODR_12_5HZ, + "26HZ": LSM6DSODR.LSM6DS_ODR_26HZ, + "52HZ": LSM6DSODR.LSM6DS_ODR_52HZ, + "104HZ": LSM6DSODR.LSM6DS_ODR_104HZ, + "208HZ": LSM6DSODR.LSM6DS_ODR_208HZ, + "416HZ": LSM6DSODR.LSM6DS_ODR_416HZ, + "833HZ": LSM6DSODR.LSM6DS_ODR_833HZ, + "1660HZ": LSM6DSODR.LSM6DS_ODR_1660HZ, + "3330HZ": LSM6DSODR.LSM6DS_ODR_3330HZ, + "6660HZ": LSM6DSODR.LSM6DS_ODR_6660HZ, +} + +accel_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +gyro_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, +) + +LSM6DS_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(LSM6DSComponent), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + cv.Optional(CONF_ACCEL_RANGE, default="2G"): cv.enum( + ACCEL_RANGE_OPTIONS, upper=True + ), + cv.Optional(CONF_GYRO_RANGE, default="250DPS"): cv.enum( + GYRO_RANGE_OPTIONS, upper=True + ), + cv.Optional(CONF_ACCEL_ODR, default="104HZ"): cv.enum(ODR_OPTIONS, upper=True), + cv.Optional(CONF_GYRO_ODR, default="104HZ"): cv.enum(ODR_OPTIONS, upper=True), + } +).extend(cv.polling_component_schema("60s")) + + +async def register_lsm6ds(var, config): + await cg.register_component(var, config) + + # Set configuration options + cg.add(var.set_accel_range(config[CONF_ACCEL_RANGE])) + cg.add(var.set_gyro_range(config[CONF_GYRO_RANGE])) + cg.add(var.set_accel_odr(config[CONF_ACCEL_ODR])) + cg.add(var.set_gyro_odr(config[CONF_GYRO_ODR])) + + # Set up sensor outputs + for d in ["x", "y", "z"]: + accel_key = f"accel_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_accel_{d}_sensor")(sens)) + gyro_key = f"gyro_{d}" + if gyro_key in config: + sens = await sensor.new_sensor(config[gyro_key]) + cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/lsm6ds_base/const.py b/esphome/components/lsm6ds_base/const.py new file mode 100644 index 000000000..4f9dd595b --- /dev/null +++ b/esphome/components/lsm6ds_base/const.py @@ -0,0 +1,26 @@ +# Registers +LSM6DS_WHO_AM_I = 0x0F +LSM6DS_CTRL1_XL = 0x10 +LSM6DS_CTRL2_G = 0x11 +LSM6DS_CTRL3_C = 0x12 +LSM6DS_STATUS_REG = 0x1E +LSM6DS_OUT_TEMP_L = 0x20 +LSM6DS_OUTX_L_G = 0x22 +LSM6DS_OUTX_L_A = 0x28 + +# WHO_AM_I Values for different variants +LSM6DSOX_WHO_AM_I_VALUE = 0x6C +LSM6DS3TR_WHO_AM_I_VALUE = 0x6A +LSM6DSO32TR_WHO_AM_I_VALUE = 0x69 + +CONF_LSM6DS_ID = "lsm6ds_id" +CONF_ACCEL_X = "accel_x" +CONF_ACCEL_Y = "accel_y" +CONF_ACCEL_Z = "accel_z" +CONF_GYRO_X = "gyro_x" +CONF_GYRO_Y = "gyro_y" +CONF_GYRO_Z = "gyro_z" +CONF_ACCEL_RANGE = "accel_range" +CONF_GYRO_RANGE = "gyro_range" +CONF_ACCEL_ODR = "accel_odr" +CONF_GYRO_ODR = "gyro_odr" diff --git a/esphome/components/lsm6ds/lsm6ds.cpp b/esphome/components/lsm6ds_base/lsm6ds.cpp similarity index 56% rename from esphome/components/lsm6ds/lsm6ds.cpp rename to esphome/components/lsm6ds_base/lsm6ds.cpp index bf435d56c..8f8e63231 100644 --- a/esphome/components/lsm6ds/lsm6ds.cpp +++ b/esphome/components/lsm6ds_base/lsm6ds.cpp @@ -4,7 +4,7 @@ #include "esphome/core/hal.h" namespace esphome { -namespace lsm6ds { +namespace lsm6ds_base { static const char *const TAG = "lsm6ds"; @@ -28,14 +28,14 @@ void LSM6DSComponent::setup() { return; } -// SW Reset + // SW Reset this->write_register(0x12, 0x01); uint8_t ctrl3; - + // Use esphome:: namespace for framework compatibility - uint32_t start = esphome::millis(); + uint32_t start = esphome::millis(); do { - esphome::delay(10); + esphome::delay(10); this->read_register(0x12, &ctrl3, 1); } while ((ctrl3 & 0x01) && (esphome::millis() - start < 100)); @@ -44,33 +44,53 @@ void LSM6DSComponent::setup() { } // Enable BDU (Block Data Update) - this->write_register(0x12, 0x40 | 0x04); // BDU=1, IF_INC=1 + this->write_register(0x12, 0x40 | 0x04); // BDU=1, IF_INC=1 // Configure Accel uint8_t ctrl1_xl = (this->accel_odr_ << 4) | (this->accel_range_ << 2); this->write_register(0x10, ctrl1_xl); - + switch (this->accel_range_) { - case LSM6DS_ACCEL_RANGE_2G: accel_sensitivity_ = 0.061e-3f; break; - case LSM6DS_ACCEL_RANGE_4G: accel_sensitivity_ = 0.122e-3f; break; - case LSM6DS_ACCEL_RANGE_8G: accel_sensitivity_ = 0.244e-3f; break; - case LSM6DS_ACCEL_RANGE_16G: accel_sensitivity_ = 0.488e-3f; break; - default: accel_sensitivity_ = 0; break; + case LSM6DS_ACCEL_RANGE_2G: + this->accel_sensitivity_ = 0.061e-3f; + break; + case LSM6DS_ACCEL_RANGE_4G: + this->accel_sensitivity_ = 0.122e-3f; + break; + case LSM6DS_ACCEL_RANGE_8G: + this->accel_sensitivity_ = 0.244e-3f; + break; + case LSM6DS_ACCEL_RANGE_16G: + this->accel_sensitivity_ = 0.488e-3f; + break; + default: + this->accel_sensitivity_ = 0; + break; } // Configure Gyro uint8_t ctrl2_g = (this->gyro_odr_ << 4); if (this->gyro_range_ == LSM6DS_GYRO_RANGE_125DPS) { - ctrl2_g |= 0x02; // FS_125 = 1 - gyro_sensitivity_ = 4.375e-3f; + ctrl2_g |= 0x02; // FS_125 = 1 + this->gyro_sensitivity_ = 4.375e-3f; } else { ctrl2_g |= (this->gyro_range_ << 2); switch (this->gyro_range_) { - case LSM6DS_GYRO_RANGE_250DPS: gyro_sensitivity_ = 8.75e-3f; break; - case LSM6DS_GYRO_RANGE_500DPS: gyro_sensitivity_ = 17.50e-3f; break; - case LSM6DS_GYRO_RANGE_1000DPS: gyro_sensitivity_ = 35.0e-3f; break; - case LSM6DS_GYRO_RANGE_2000DPS: gyro_sensitivity_ = 70.0e-3f; break; - default: gyro_sensitivity_ = 0; break; + case LSM6DS_GYRO_RANGE_250DPS: + this->gyro_sensitivity_ = 8.75e-3f; + break; + case LSM6DS_GYRO_RANGE_500DPS: + this->gyro_sensitivity_ = 17.50e-3f; + break; + case LSM6DS_GYRO_RANGE_1000DPS: + this->gyro_sensitivity_ = 35.0e-3f; + break; + case LSM6DS_GYRO_RANGE_2000DPS: + this->gyro_sensitivity_ = 70.0e-3f; + break; + default: + this->gyro_sensitivity_ = 0; + break; } } this->write_register(0x11, ctrl2_g); @@ -78,38 +98,46 @@ void LSM6DSComponent::setup() { void LSM6DSComponent::update() { uint8_t status; - if (!this->read_register(0x1E, &status, 1)) return; + if (!this->read_register(0x1E, &status, 1)) + return; - if (status & 0x01) { // GDA - Gyroscope data available + if (status & 0x01) { // GDA - Gyroscope data available uint8_t data[6]; if (this->read_register(0x22, data, 6)) { int16_t gx = encode_uint16(data[1], data[0]); int16_t gy = encode_uint16(data[3], data[2]); int16_t gz = encode_uint16(data[5], data[4]); - if (this->gyro_x_sensor_) this->gyro_x_sensor_->publish_state(gx * gyro_sensitivity_); - if (this->gyro_y_sensor_) this->gyro_y_sensor_->publish_state(gy * gyro_sensitivity_); - if (this->gyro_z_sensor_) this->gyro_z_sensor_->publish_state(gz * gyro_sensitivity_); + if (this->gyro_x_sensor_) + this->gyro_x_sensor_->publish_state(gx * this->gyro_sensitivity_); + if (this->gyro_y_sensor_) + this->gyro_y_sensor_->publish_state(gy * this->gyro_sensitivity_); + if (this->gyro_z_sensor_) + this->gyro_z_sensor_->publish_state(gz * this->gyro_sensitivity_); } } - if (status & 0x02) { // XLDA - Accelerometer data available + if (status & 0x02) { // XLDA - Accelerometer data available uint8_t data[6]; if (this->read_register(0x28, data, 6)) { int16_t ax = encode_uint16(data[1], data[0]); int16_t ay = encode_uint16(data[3], data[2]); int16_t az = encode_uint16(data[5], data[4]); float grav = 9.80665f; - if (this->accel_x_sensor_) this->accel_x_sensor_->publish_state(ax * accel_sensitivity_ * grav); - if (this->accel_y_sensor_) this->accel_y_sensor_->publish_state(ay * accel_sensitivity_ * grav); - if (this->accel_z_sensor_) this->accel_z_sensor_->publish_state(az * accel_sensitivity_ * grav); + if (this->accel_x_sensor_) + this->accel_x_sensor_->publish_state(ax * this->accel_sensitivity_ * grav); + if (this->accel_y_sensor_) + this->accel_y_sensor_->publish_state(ay * this->accel_sensitivity_ * grav); + if (this->accel_z_sensor_) + this->accel_z_sensor_->publish_state(az * this->accel_sensitivity_ * grav); } } - if (status & 0x04) { // TDA - Temperature data available + if (status & 0x04) { // TDA - Temperature data available uint8_t data[2]; if (this->read_register(0x20, data, 2)) { int16_t temp = encode_uint16(data[1], data[0]); - if (this->temperature_sensor_) this->temperature_sensor_->publish_state(25.0f + (temp / 256.0f)); + if (this->temperature_sensor_) + this->temperature_sensor_->publish_state(25.0f + (temp / 256.0f)); } } } @@ -130,5 +158,5 @@ void LSM6DSComponent::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); } -} // namespace lsm6ds +} // namespace lsm6ds_base } // namespace esphome diff --git a/esphome/components/lsm6ds/lsm6ds.h b/esphome/components/lsm6ds_base/lsm6ds.h similarity index 72% rename from esphome/components/lsm6ds/lsm6ds.h rename to esphome/components/lsm6ds_base/lsm6ds.h index c21c5c147..941f9ea99 100644 --- a/esphome/components/lsm6ds/lsm6ds.h +++ b/esphome/components/lsm6ds_base/lsm6ds.h @@ -2,14 +2,9 @@ #include "esphome/core/component.h" #include "esphome/components/sensor/sensor.h" -#include "esphome/components/i2c/i2c.h" - -#ifdef USE_SPI -#include "esphome/components/spi/spi.h" -#endif namespace esphome { -namespace lsm6ds { +namespace lsm6ds_base { enum LSM6DSAccelRange { LSM6DS_ACCEL_RANGE_2G = 0x00, @@ -81,35 +76,5 @@ class LSM6DSComponent : public PollingComponent { float gyro_sensitivity_; }; -class LSM6DSI2CDevice : public LSM6DSComponent, public i2c::I2CDevice { - public: - bool read_register(uint8_t reg, uint8_t *data, size_t len) override { - return this->read_bytes(reg, data, len); - } - bool write_register(uint8_t reg, uint8_t data) override { - return this->write_byte(reg, data); - } -}; - -#ifdef USE_SPI -class LSM6DSSPIDevice : public LSM6DSComponent, public spi::SPIDevice { - public: - bool read_register(uint8_t reg, uint8_t *data, size_t len) override { - this->enable(); - this->transfer_byte(reg | 0x80); - this->transfer_bytes(nullptr, data, len); - this->disable(); - return true; - } - bool write_register(uint8_t reg, uint8_t data) override { - this->enable(); - this->transfer_byte(reg & 0x7F); - this->transfer_byte(data); - this->disable(); - return true; - } -}; -#endif - -} // namespace lsm6ds +} // namespace lsm6ds_base } // namespace esphome diff --git a/esphome/components/lsm6ds_i2c/README_EXAMPLE.md b/esphome/components/lsm6ds_i2c/README_EXAMPLE.md new file mode 100644 index 000000000..f876c0315 --- /dev/null +++ b/esphome/components/lsm6ds_i2c/README_EXAMPLE.md @@ -0,0 +1,124 @@ +# LSM6DS I2C Example Configuration + +This component supports LSM6DS family IMU sensors (accelerometer + gyroscope) via I2C interface. + +## Supported Sensors +- LSM6DSOX (WHO_AM_I: 0x6C, I2C address: 0x6A or 0x6B) +- LSM6DS3TR-C (WHO_AM_I: 0x6A, I2C address: 0x6A or 0x6B) +- LSM6DSO32 (WHO_AM_I: 0x69, I2C address: 0x6A or 0x6B) + +## Example 1: All-in-one Pattern + +Configure the sensor with all outputs in the component block: + +```yaml +i2c: + - id: i2c_bus + sda: GPIO21 + scl: GPIO22 + scan: true + +lsm6ds_i2c: + id: imu_sensor + i2c_id: i2c_bus + address: 0x6B + accel_range: 4G + gyro_range: 500DPS + accel_odr: 104HZ + gyro_odr: 104HZ + update_interval: 50ms + accel_x: + name: "Accel X" + accel_y: + name: "Accel Y" + accel_z: + name: "Accel Z" + gyro_x: + name: "Gyro X" + gyro_y: + name: "Gyro Y" + gyro_z: + name: "Gyro Z" + temperature: + name: "IMU Temperature" +``` + +## Example 2: Hub + Sensor Platform Pattern + +Configure the hub separately, then add sensor outputs under the sensor platform: + +```yaml +i2c: + - id: i2c_bus + sda: GPIO21 + scl: GPIO22 + scan: true + +lsm6ds_i2c: + id: imu_hub + i2c_id: i2c_bus + address: 0x6B + accel_range: 4G + gyro_range: 500DPS + accel_odr: 104HZ + gyro_odr: 104HZ + update_interval: 50ms + +sensor: + - platform: lsm6ds_i2c + lsm6ds_id: imu_hub + accel_x: + name: "Accel X" + accel_y: + name: "Accel Y" + accel_z: + name: "Accel Z" + gyro_x: + name: "Gyro X" + gyro_y: + name: "Gyro Y" + gyro_z: + name: "Gyro Z" + temperature: + name: "IMU Temperature" +``` + +## Configuration Options + +### I2C Address +- `0x6A` (default, SDO/SA0 pin LOW) +- `0x6B` (SDO/SA0 pin HIGH) + +### Accelerometer Range +- `2G` (default) +- `4G` +- `8G` +- `16G` + +### Gyroscope Range +- `125DPS` +- `250DPS` (default) +- `500DPS` +- `1000DPS` +- `2000DPS` + +### Output Data Rate (ODR) +- `12.5HZ` +- `26HZ` +- `52HZ` +- `104HZ` (default) +- `208HZ` +- `416HZ` +- `833HZ` +- `1660HZ` +- `3330HZ` +- `6660HZ` + +## I2C Configuration + +The LSM6DS I2C implementation uses standard I2C communication: +- **Speed**: Supports up to 400 kHz (Fast mode) +- **7-bit addressing**: 0x6A or 0x6B depending on SDO/SA0 pin state +- **Auto-increment**: Multi-byte reads automatically increment register address + +The component handles the I2C protocol automatically. diff --git a/esphome/components/lsm6ds_i2c/__init__.py b/esphome/components/lsm6ds_i2c/__init__.py new file mode 100644 index 000000000..d0db9314b --- /dev/null +++ b/esphome/components/lsm6ds_i2c/__init__.py @@ -0,0 +1,27 @@ +import esphome.codegen as cg +from esphome.components import i2c +import esphome.config_validation as cv +from esphome.const import CONF_ID + +from ..lsm6ds_base import LSM6DS_SCHEMA, LSM6DSComponent, register_lsm6ds + +AUTO_LOAD = ["lsm6ds_base"] +CODEOWNERS = ["@NonaSuomy"] +DEPENDENCIES = ["i2c"] + +lsm6ds_i2c_ns = cg.esphome_ns.namespace("lsm6ds_i2c") +LSM6DSI2CDevice = lsm6ds_i2c_ns.class_( + "LSM6DSI2CDevice", LSM6DSComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = LSM6DS_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LSM6DSI2CDevice), + } +).extend(i2c.i2c_device_schema(0x6A)) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_lsm6ds(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/lsm6ds_i2c/lsm6ds_i2c.cpp b/esphome/components/lsm6ds_i2c/lsm6ds_i2c.cpp new file mode 100644 index 000000000..b2332d399 --- /dev/null +++ b/esphome/components/lsm6ds_i2c/lsm6ds_i2c.cpp @@ -0,0 +1,5 @@ +#include "lsm6ds_i2c.h" + +namespace esphome { +namespace lsm6ds_i2c {} // namespace lsm6ds_i2c +} // namespace esphome diff --git a/esphome/components/lsm6ds_i2c/lsm6ds_i2c.h b/esphome/components/lsm6ds_i2c/lsm6ds_i2c.h new file mode 100644 index 000000000..61b29c37d --- /dev/null +++ b/esphome/components/lsm6ds_i2c/lsm6ds_i2c.h @@ -0,0 +1,16 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/lsm6ds_base/lsm6ds.h" + +namespace esphome { +namespace lsm6ds_i2c { + +class LSM6DSI2CDevice : public lsm6ds_base::LSM6DSComponent, public i2c::I2CDevice { + public: + bool read_register(uint8_t reg, uint8_t *data, size_t len) override { return this->read_bytes(reg, data, len); } + bool write_register(uint8_t reg, uint8_t data) override { return this->write_byte(reg, data); } +}; + +} // namespace lsm6ds_i2c +} // namespace esphome diff --git a/esphome/components/lsm6ds_i2c/sensor.py b/esphome/components/lsm6ds_i2c/sensor.py new file mode 100644 index 000000000..ec37001cb --- /dev/null +++ b/esphome/components/lsm6ds_i2c/sensor.py @@ -0,0 +1,77 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + ICON_SCREEN_ROTATION, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DEGREE_PER_SECOND, + UNIT_METER_PER_SECOND_SQUARED, +) + +from ..lsm6ds_base import LSM6DSComponent +from ..lsm6ds_base.const import ( + CONF_ACCEL_X, + CONF_ACCEL_Y, + CONF_ACCEL_Z, + CONF_GYRO_X, + CONF_GYRO_Y, + CONF_GYRO_Z, + CONF_LSM6DS_ID, +) + +DEPENDENCIES = ["lsm6ds_i2c"] +CODEOWNERS = ["@NonaSuomy"] + +accel_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +gyro_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LSM6DS_ID): cv.use_id(LSM6DSComponent), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_LSM6DS_ID]) + + for d in ["x", "y", "z"]: + accel_key = f"accel_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(hub, f"set_accel_{d}_sensor")(sens)) + gyro_key = f"gyro_{d}" + if gyro_key in config: + sens = await sensor.new_sensor(config[gyro_key]) + cg.add(getattr(hub, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(hub.set_temperature_sensor(sens)) diff --git a/esphome/components/lsm6ds_spi/README_EXAMPLE.md b/esphome/components/lsm6ds_spi/README_EXAMPLE.md new file mode 100644 index 000000000..377c7d1d4 --- /dev/null +++ b/esphome/components/lsm6ds_spi/README_EXAMPLE.md @@ -0,0 +1,122 @@ +# LSM6DS SPI Example Configuration + +This component supports LSM6DS family IMU sensors (accelerometer + gyroscope) via SPI interface. + +## Supported Sensors +- LSM6DSOX (WHO_AM_I: 0x6C) +- LSM6DS3TR-C (WHO_AM_I: 0x6A) +- LSM6DSO32 (WHO_AM_I: 0x69) + +## Example 1: All-in-one Pattern + +Configure the sensor with all outputs in the component block: + +```yaml +spi: + - id: spi_bus + clk_pin: GPIO12 + mosi_pin: GPIO11 + miso_pin: GPIO13 + +lsm6ds_spi: + id: imu_sensor + spi_id: spi_bus + cs_pin: GPIO10 + accel_range: 4G + gyro_range: 500DPS + accel_odr: 104HZ + gyro_odr: 104HZ + update_interval: 50ms + accel_x: + name: "Accel X" + accel_y: + name: "Accel Y" + accel_z: + name: "Accel Z" + gyro_x: + name: "Gyro X" + gyro_y: + name: "Gyro Y" + gyro_z: + name: "Gyro Z" + temperature: + name: "IMU Temperature" +``` + +## Example 2: Hub + Sensor Platform Pattern + +Configure the hub separately, then add sensor outputs under the sensor platform: + +```yaml +spi: + - id: spi_bus + clk_pin: GPIO12 + mosi_pin: GPIO11 + miso_pin: GPIO13 + +lsm6ds_spi: + id: imu_hub + spi_id: spi_bus + cs_pin: GPIO10 + accel_range: 4G + gyro_range: 500DPS + accel_odr: 104HZ + gyro_odr: 104HZ + update_interval: 50ms + +sensor: + - platform: lsm6ds_spi + lsm6ds_id: imu_hub + accel_x: + name: "Accel X" + accel_y: + name: "Accel Y" + accel_z: + name: "Accel Z" + gyro_x: + name: "Gyro X" + gyro_y: + name: "Gyro Y" + gyro_z: + name: "Gyro Z" + temperature: + name: "IMU Temperature" +``` + +## Configuration Options + +### Accelerometer Range +- `2G` (default) +- `4G` +- `8G` +- `16G` + +### Gyroscope Range +- `125DPS` +- `250DPS` (default) +- `500DPS` +- `1000DPS` +- `2000DPS` + +### Output Data Rate (ODR) +- `12.5HZ` +- `26HZ` +- `52HZ` +- `104HZ` (default) +- `208HZ` +- `416HZ` +- `833HZ` +- `1660HZ` +- `3330HZ` +- `6660HZ` + +## SPI Configuration + +The LSM6DS SPI implementation uses: +- **Mode 0** or **Mode 3** (CPOL=0, CPHA=0 or CPOL=1, CPHA=1) +- **MSB first** +- **Read bit**: MSB set to 1 (reg | 0x80) +- **Write bit**: MSB set to 0 (reg & 0x7F) +- **Max speed**: 10 MHz + +The component handles the SPI protocol automatically. diff --git a/esphome/components/lsm6ds_spi/__init__.py b/esphome/components/lsm6ds_spi/__init__.py new file mode 100644 index 000000000..2f95ebabc --- /dev/null +++ b/esphome/components/lsm6ds_spi/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +from esphome.components import spi +import esphome.config_validation as cv +from esphome.const import CONF_ID + +from ..lsm6ds_base import LSM6DS_SCHEMA, LSM6DSComponent, register_lsm6ds + +AUTO_LOAD = ["lsm6ds_base"] +CODEOWNERS = ["@NonaSuomy"] +DEPENDENCIES = ["spi"] + +lsm6ds_spi_ns = cg.esphome_ns.namespace("lsm6ds_spi") +LSM6DSSPIDevice = lsm6ds_spi_ns.class_( + "LSM6DSSPIDevice", LSM6DSComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LSM6DSSPIDevice), + } + ) + .extend(LSM6DS_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_lsm6ds(var, config) + await spi.register_spi_device(var, config) diff --git a/esphome/components/lsm6ds_spi/lsm6ds_spi.cpp b/esphome/components/lsm6ds_spi/lsm6ds_spi.cpp new file mode 100644 index 000000000..97763ecdb --- /dev/null +++ b/esphome/components/lsm6ds_spi/lsm6ds_spi.cpp @@ -0,0 +1,5 @@ +#include "lsm6ds_spi.h" + +namespace esphome { +namespace lsm6ds_spi {} // namespace lsm6ds_spi +} // namespace esphome diff --git a/esphome/components/lsm6ds_spi/lsm6ds_spi.h b/esphome/components/lsm6ds_spi/lsm6ds_spi.h new file mode 100644 index 000000000..183e27d9e --- /dev/null +++ b/esphome/components/lsm6ds_spi/lsm6ds_spi.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/components/lsm6ds_base/lsm6ds.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace lsm6ds_spi { + +class LSM6DSSPIDevice : public lsm6ds_base::LSM6DSComponent, + public spi::SPIDevice { + public: + bool read_register(uint8_t reg, uint8_t *data, size_t len) override { + this->enable(); + this->transfer_byte(reg | 0x80); + this->transfer_bytes(nullptr, data, len); + this->disable(); + return true; + } + bool write_register(uint8_t reg, uint8_t data) override { + this->enable(); + this->transfer_byte(reg & 0x7F); + this->transfer_byte(data); + this->disable(); + return true; + } +}; + +} // namespace lsm6ds_spi +} // namespace esphome diff --git a/esphome/components/lsm6ds_spi/sensor.py b/esphome/components/lsm6ds_spi/sensor.py new file mode 100644 index 000000000..5d14841f0 --- /dev/null +++ b/esphome/components/lsm6ds_spi/sensor.py @@ -0,0 +1,77 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + ICON_SCREEN_ROTATION, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_DEGREE_PER_SECOND, + UNIT_METER_PER_SECOND_SQUARED, +) + +from ..lsm6ds_base import LSM6DSComponent +from ..lsm6ds_base.const import ( + CONF_ACCEL_X, + CONF_ACCEL_Y, + CONF_ACCEL_Z, + CONF_GYRO_X, + CONF_GYRO_Y, + CONF_GYRO_Z, + CONF_LSM6DS_ID, +) + +DEPENDENCIES = ["lsm6ds_spi"] +CODEOWNERS = ["@NonaSuomy"] + +accel_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +gyro_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LSM6DS_ID): cv.use_id(LSM6DSComponent), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_LSM6DS_ID]) + + for d in ["x", "y", "z"]: + accel_key = f"accel_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(hub, f"set_accel_{d}_sensor")(sens)) + gyro_key = f"gyro_{d}" + if gyro_key in config: + sens = await sensor.new_sensor(config[gyro_key]) + cg.add(getattr(hub, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(hub.set_temperature_sensor(sens)) diff --git a/tests/components/lsm6ds/common.yaml b/tests/components/lsm6ds/common.yaml new file mode 100644 index 000000000..25cc6e97a --- /dev/null +++ b/tests/components/lsm6ds/common.yaml @@ -0,0 +1,31 @@ +lsm6ds_i2c: + id: lsm6ds_i2c_hub + +lsm6ds_spi: + id: lsm6ds_spi_hub + cs_pin: GPIO5 + +sensor: + - platform: lsm6ds + lsm6ds_id: lsm6ds_i2c_hub + accel_x: + name: "I2C Accel X" + accel_y: + name: "I2C Accel Y" + accel_z: + name: "I2C Accel Z" + gyro_x: + name: "I2C Gyro X" + gyro_y: + name: "I2C Gyro Y" + gyro_z: + name: "I2C Gyro Z" + temperature: + name: "I2C Temperature" + + - platform: lsm6ds + lsm6ds_id: lsm6ds_spi_hub + accel_x: + name: "SPI Accel X" + gyro_x: + name: "SPI Gyro X" diff --git a/tests/components/lsm6ds/test.esp32-ard.yaml b/tests/components/lsm6ds/test.esp32-ard.yaml new file mode 100644 index 000000000..9096dabed --- /dev/null +++ b/tests/components/lsm6ds/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-ard.yaml + spi: !include ../../test_build_components/common/spi/esp32-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/lsm6ds/test.esp32-idf.yaml b/tests/components/lsm6ds/test.esp32-idf.yaml new file mode 100644 index 000000000..cd925566c --- /dev/null +++ b/tests/components/lsm6ds/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/lsm6ds_test.yaml b/tests/lsm6ds_test.yaml new file mode 100644 index 000000000..983321de2 --- /dev/null +++ b/tests/lsm6ds_test.yaml @@ -0,0 +1,43 @@ +esphome: + name: lsm6ds_test + +esp32: + board: esp32dev + framework: + type: arduino + +i2c: + sda: 21 + scl: 22 + +spi: + clk_pin: 18 + mosi_pin: 23 + miso_pin: 19 + +lsm6ds_i2c: + id: my_lsm6ds_i2c + address: 0x6A + +lsm6ds_spi: + id: my_lsm6ds_spi + cs_pin: 5 + +sensor: + - platform: lsm6ds + lsm6ds_id: my_lsm6ds_i2c + accel_x: + name: "I2C Accel X" + gyro_x: + name: "I2C Gyro X" + temperature: + name: "I2C Temp" + + - platform: lsm6ds + lsm6ds_id: my_lsm6ds_spi + accel_x: + name: "SPI Accel X" + gyro_x: + name: "SPI Gyro X" + temperature: + name: "SPI Temp"