diff --git a/CODEOWNERS b/CODEOWNERS index 52e80a5822..eb8fb873de 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -124,6 +124,7 @@ esphome/components/modbus_controller/select/* @martgras @stegm esphome/components/modbus_controller/sensor/* @martgras esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras +esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw diff --git a/esphome/components/mpu6886/__init__.py b/esphome/components/mpu6886/__init__.py new file mode 100644 index 0000000000..933f71ccd7 --- /dev/null +++ b/esphome/components/mpu6886/__init__.py @@ -0,0 +1 @@ +"""Support for MPC-6886.""" diff --git a/esphome/components/mpu6886/mpu6886.cpp b/esphome/components/mpu6886/mpu6886.cpp new file mode 100644 index 0000000000..c296653e6b --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.cpp @@ -0,0 +1,153 @@ +#include "mpu6886.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mpu6886 { + +static const char *const TAG = "mpu6886"; + +const uint8_t MPU6886_REGISTER_WHO_AM_I = 0x75; +const uint8_t MPU6886_REGISTER_POWER_MANAGEMENT_1 = 0x6B; +const uint8_t MPU6886_REGISTER_GYRO_CONFIG = 0x1B; +const uint8_t MPU6886_REGISTER_ACCEL_CONFIG = 0x1C; +const uint8_t MPU6886_REGISTER_ACCEL_XOUT_H = 0x3B; +const uint8_t MPU6886_CLOCK_SOURCE_X_GYRO = 0b001; +const uint8_t MPU6886_SCALE_2000_DPS = 0b11; +const uint8_t MPU6886_WHO_AM_I_IDENTIFIER = 0x19; +const float MPU6886_SCALE_DPS_PER_DIGIT_2000 = 0.060975f; +const uint8_t MPU6886_RANGE_2G = 0b00; +const float MPU6886_RANGE_PER_DIGIT_2G = 0.000061f; +const uint8_t MPU6886_BIT_SLEEP_ENABLED = 6; +const uint8_t MPU6886_BIT_TEMPERATURE_DISABLED = 3; +const float GRAVITY_EARTH = 9.80665f; +// See https://github.com/m5stack/M5-Schematic/blob/master/datasheet/MPU-6886-000193%2Bv1.1_GHIC.PDF.pdf +// p. 43 +const float TEMPERATURE_SENSITIVITY = 326.8; +const float TEMPERATURE_OFFSET = 25.0; + +void MPU6886Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MPU6886..."); + uint8_t who_am_i; + if (!this->read_byte(MPU6886_REGISTER_WHO_AM_I, &who_am_i) || who_am_i != MPU6886_WHO_AM_I_IDENTIFIER) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Power Management..."); + // Setup power management + uint8_t power_management; + if (!this->read_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, &power_management)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + // Set clock source - X-Gyro + power_management &= 0b11111000; + power_management |= MPU6886_CLOCK_SOURCE_X_GYRO; + // Disable sleep + power_management &= ~(1 << MPU6886_BIT_SLEEP_ENABLED); + // Enable temperature + power_management &= ~(1 << MPU6886_BIT_TEMPERATURE_DISABLED); + ESP_LOGV(TAG, " Output power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + if (!this->write_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, power_management)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Gyroscope Config..."); + // Set scale - 2000DPS + uint8_t gyro_config; + if (!this->read_byte(MPU6886_REGISTER_GYRO_CONFIG, &gyro_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input gyroscope_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + gyro_config &= 0b11100111; + gyro_config |= MPU6886_SCALE_2000_DPS << 3; + ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Accelerometer Config..."); + // Set range - 2G + uint8_t accel_config; + if (!this->read_byte(MPU6886_REGISTER_ACCEL_CONFIG, &accel_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input accelerometer_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + accel_config &= 0b11100111; + accel_config |= (MPU6886_RANGE_2G << 3); + ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } +} + +void MPU6886Component::dump_config() { + ESP_LOGCONFIG(TAG, "MPU6886:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MPU6886 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_); + LOG_SENSOR(" ", "Acceleration Y", this->accel_y_sensor_); + LOG_SENSOR(" ", "Acceleration 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_); +} + +void MPU6886Component::update() { + ESP_LOGV(TAG, " Updating MPU6886..."); + uint16_t raw_data[7]; + if (!this->read_bytes_16(MPU6886_REGISTER_ACCEL_XOUT_H, raw_data, 7)) { + this->status_set_warning(); + return; + } + auto *data = reinterpret_cast(raw_data); + + float accel_x = data[0] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_y = data[1] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_z = data[2] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + + float temperature = data[3] / TEMPERATURE_SENSITIVITY + TEMPERATURE_OFFSET; + + float gyro_x = data[4] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_y = data[5] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_z = data[6] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + + ESP_LOGD(TAG, + "Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, " + "gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C", + accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature); + + if (this->accel_x_sensor_ != nullptr) + this->accel_x_sensor_->publish_state(accel_x); + if (this->accel_y_sensor_ != nullptr) + this->accel_y_sensor_->publish_state(accel_y); + if (this->accel_z_sensor_ != nullptr) + this->accel_z_sensor_->publish_state(accel_z); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + + if (this->gyro_x_sensor_ != nullptr) + this->gyro_x_sensor_->publish_state(gyro_x); + if (this->gyro_y_sensor_ != nullptr) + this->gyro_y_sensor_->publish_state(gyro_y); + if (this->gyro_z_sensor_ != nullptr) + this->gyro_z_sensor_->publish_state(gyro_z); + + this->status_clear_warning(); +} + +float MPU6886Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/mpu6886.h b/esphome/components/mpu6886/mpu6886.h new file mode 100644 index 0000000000..04551ae56d --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mpu6886 { + +class MPU6886Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void update() override; + + float get_setup_priority() const override; + + void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; } + void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; } + void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; } + void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; } + void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; } + + protected: + sensor::Sensor *accel_x_sensor_{nullptr}; + sensor::Sensor *accel_y_sensor_{nullptr}; + sensor::Sensor *accel_z_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *gyro_x_sensor_{nullptr}; + sensor::Sensor *gyro_y_sensor_{nullptr}; + sensor::Sensor *gyro_z_sensor_{nullptr}; +}; +; + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/sensor.py b/esphome/components/mpu6886/sensor.py new file mode 100644 index 0000000000..535007d008 --- /dev/null +++ b/esphome/components/mpu6886/sensor.py @@ -0,0 +1,85 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + STATE_CLASS_MEASUREMENT, + UNIT_METER_PER_SECOND_SQUARED, + ICON_SCREEN_ROTATION, + UNIT_DEGREE_PER_SECOND, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@fabaff"] +DEPENDENCIES = ["i2c"] + +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" + +mpu6886_ns = cg.esphome_ns.namespace("mpu6886") +MPU6886Component = mpu6886_ns.class_( + "MPU6886Component", cg.PollingComponent, i2c.I2CDevice +) + +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, +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPU6886Component), + 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, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x68)) +) + + +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) + + 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)) + accel_key = f"gyro_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_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/tests/test1.yaml b/tests/test1.yaml index c4398498c1..0d8ba9dfe8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -724,6 +724,23 @@ sensor: temperature: name: "MPU6050 Temperature" i2c_id: i2c_bus + - platform: mpu6886 + address: 0x68 + accel_x: + name: "MPU6886 Accel X" + accel_y: + name: "MPU6886 Accel Y" + accel_z: + name: "MPU6886 Accel z" + gyro_x: + name: "MPU6886 Gyro X" + gyro_y: + name: "MPU6886 Gyro Y" + gyro_z: + name: "MPU6886 Gyro z" + temperature: + name: "MPU6886 Temperature" + i2c_id: i2c_bus - platform: ms5611 temperature: name: "Outside Temperature"