diff --git a/esphome/components/lsm6ds/__init__.py b/esphome/components/lsm6ds/__init__.py new file mode 100644 index 000000000..c970b5fc7 --- /dev/null +++ b/esphome/components/lsm6ds/__init__.py @@ -0,0 +1,93 @@ +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_4G, + LSM6DS_ACCEL_RANGE_8G, + LSM6DS_ACCEL_RANGE_16G, + LSM6DS_GYRO_RANGE_125DPS, + LSM6DS_GYRO_RANGE_250DPS, + LSM6DS_GYRO_RANGE_500DPS, + LSM6DS_GYRO_RANGE_1000DPS, + LSM6DS_GYRO_RANGE_2000DPS, + 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, +) + +DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["sensor"] + +LSM6DSI2CDevice = lsm6ds_ns.class_("LSM6DSI2CDevice", LSM6DSComponent, i2c.I2CDevice) + +ACCEL_RANGES = { + "2G": LSM6DS_ACCEL_RANGE_2G, + "4G": LSM6DS_ACCEL_RANGE_4G, + "8G": LSM6DS_ACCEL_RANGE_8G, + "16G": LSM6DS_ACCEL_RANGE_16G, +} + +GYRO_RANGES = { + "125DPS": LSM6DS_GYRO_RANGE_125DPS, + "250DPS": LSM6DS_GYRO_RANGE_250DPS, + "500DPS": LSM6DS_GYRO_RANGE_500DPS, + "1000DPS": LSM6DS_GYRO_RANGE_1000DPS, + "2000DPS": LSM6DS_GYRO_RANGE_2000DPS, +} + +ODRS = { + "0HZ": 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, +} + +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(ODRS, upper=True), + cv.Optional(CONF_GYRO_ODR, default="104HZ"): cv.enum(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 = ODRS[config[CONF_ACCEL_ODR]] + gyro_odr = 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 new file mode 100644 index 000000000..42ac5389d --- /dev/null +++ b/esphome/components/lsm6ds/const.py @@ -0,0 +1,55 @@ +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 + +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") +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 + +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/lsm6ds.cpp new file mode 100644 index 000000000..df21c1087 --- /dev/null +++ b/esphome/components/lsm6ds/lsm6ds.cpp @@ -0,0 +1,129 @@ +#include "lsm6ds.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace lsm6ds { + +static const char *const TAG = "lsm6ds"; + +void LSM6DSComponent::setup() { + uint8_t who_am_i; + if (!this->read_register(0x0F, &who_am_i, 1)) { + this->mark_failed(); + ESP_LOGE(TAG, "Communication failed"); + return; + } + + if (who_am_i == 0x6C) { + ESP_LOGI(TAG, "Detected LSM6DSOX"); + } else if (who_am_i == 0x6A) { + ESP_LOGI(TAG, "Detected LSM6DS3TR-C"); + } else { + this->mark_failed(); + ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", who_am_i); + return; + } + + // SW Reset + this->write_register(0x12, 0x01); + uint8_t ctrl3; + uint32_t start = millis(); + do { + delay(10); + this->read_register(0x12, &ctrl3, 1); + } while ((ctrl3 & 0x01) && (millis() - start < 100)); + + if (ctrl3 & 0x01) { + ESP_LOGW(TAG, "SW Reset timed out"); + } + + // Enable BDU (Block Data Update) + 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; + } + + // 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; + } 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; + } + } + this->write_register(0x11, ctrl2_g); +} + +void LSM6DSComponent::update() { + uint8_t status; + if (!this->read_register(0x1E, &status, 1)) return; + + 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 (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 (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)); + } + } +} + +void LSM6DSComponent::dump_config() { + ESP_LOGCONFIG(TAG, "LSM6DS (LSM6DSOX/LSM6DS3TR-C):"); + LOG_UPDATE_INTERVAL(this); + if (this->is_failed()) { + ESP_LOGCONFIG(TAG, " Connection failed!"); + return; + } + LOG_SENSOR(" ", "Accel X", this->accel_x_sensor_); + LOG_SENSOR(" ", "Accel Y", this->accel_y_sensor_); + LOG_SENSOR(" ", "Accel Z", this->accel_z_sensor_); + LOG_SENSOR(" ", "Gyro X", this->gyro_x_sensor_); + LOG_SENSOR(" ", "Gyro Y", this->gyro_y_sensor_); + LOG_SENSOR(" ", "Gyro Z", this->gyro_z_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +} // namespace lsm6ds +} // namespace esphome diff --git a/esphome/components/lsm6ds/lsm6ds.h b/esphome/components/lsm6ds/lsm6ds.h new file mode 100644 index 000000000..c21c5c147 --- /dev/null +++ b/esphome/components/lsm6ds/lsm6ds.h @@ -0,0 +1,115 @@ +#pragma once + +#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 { + +enum LSM6DSAccelRange { + LSM6DS_ACCEL_RANGE_2G = 0x00, + LSM6DS_ACCEL_RANGE_16G = 0x01, + LSM6DS_ACCEL_RANGE_4G = 0x02, + LSM6DS_ACCEL_RANGE_8G = 0x03, +}; + +enum LSM6DSGyroRange { + LSM6DS_GYRO_RANGE_250DPS = 0x00, + LSM6DS_GYRO_RANGE_500DPS = 0x01, + LSM6DS_GYRO_RANGE_1000DPS = 0x02, + LSM6DS_GYRO_RANGE_2000DPS = 0x03, + LSM6DS_GYRO_RANGE_125DPS = 0x04, +}; + +enum LSM6DSODR { + LSM6DS_ODR_OFF = 0x00, + LSM6DS_ODR_12_5HZ = 0x01, + LSM6DS_ODR_26HZ = 0x02, + LSM6DS_ODR_52HZ = 0x03, + LSM6DS_ODR_104HZ = 0x04, + LSM6DS_ODR_208HZ = 0x05, + LSM6DS_ODR_416HZ = 0x06, + LSM6DS_ODR_833HZ = 0x07, + LSM6DS_ODR_1_66KHZ = 0x08, + LSM6DS_ODR_3_33KHZ = 0x09, + LSM6DS_ODR_6_66KHZ = 0x0A, +}; + +class LSM6DSComponent : public PollingComponent { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_accel_x_sensor(sensor::Sensor *accel_x) { accel_x_sensor_ = accel_x; } + void set_accel_y_sensor(sensor::Sensor *accel_y) { accel_y_sensor_ = accel_y; } + void set_accel_z_sensor(sensor::Sensor *accel_z) { accel_z_sensor_ = accel_z; } + void set_gyro_x_sensor(sensor::Sensor *gyro_x) { gyro_x_sensor_ = gyro_x; } + void set_gyro_y_sensor(sensor::Sensor *gyro_y) { gyro_y_sensor_ = gyro_y; } + void set_gyro_z_sensor(sensor::Sensor *gyro_z) { gyro_z_sensor_ = gyro_z; } + void set_temperature_sensor(sensor::Sensor *temp) { temperature_sensor_ = temp; } + + void set_accel_range(LSM6DSAccelRange range) { accel_range_ = range; } + void set_gyro_range(LSM6DSGyroRange range) { gyro_range_ = range; } + void set_accel_odr(LSM6DSODR odr) { accel_odr_ = odr; } + void set_gyro_odr(LSM6DSODR odr) { gyro_odr_ = odr; } + + protected: + virtual bool read_register(uint8_t reg, uint8_t *data, size_t len) = 0; + virtual bool write_register(uint8_t reg, uint8_t data) = 0; + + sensor::Sensor *accel_x_sensor_{nullptr}; + sensor::Sensor *accel_y_sensor_{nullptr}; + sensor::Sensor *accel_z_sensor_{nullptr}; + sensor::Sensor *gyro_x_sensor_{nullptr}; + sensor::Sensor *gyro_y_sensor_{nullptr}; + sensor::Sensor *gyro_z_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + + LSM6DSAccelRange accel_range_; + LSM6DSGyroRange gyro_range_; + LSM6DSODR accel_odr_; + LSM6DSODR gyro_odr_; + + float accel_sensitivity_; + 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 esphome diff --git a/esphome/components/lsm6ds/sensor.py b/esphome/components/lsm6ds/sensor.py new file mode 100644 index 000000000..0205c8b9e --- /dev/null +++ b/esphome/components/lsm6ds/sensor.py @@ -0,0 +1,74 @@ +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))