mirror of
https://github.com/esphome/esphome.git
synced 2025-02-27 15:28:29 +00:00
MSA311 and MSA301 accelerometer support (#6795)
Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
parent
bc96eb9d52
commit
c19621e238
@ -299,6 +299,7 @@ esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
|||||||
esphome/components/mpl3115a2/* @kbickar
|
esphome/components/mpl3115a2/* @kbickar
|
||||||
esphome/components/mpu6886/* @fabaff
|
esphome/components/mpu6886/* @fabaff
|
||||||
esphome/components/ms8607/* @e28eta
|
esphome/components/ms8607/* @e28eta
|
||||||
|
esphome/components/msa3xx/* @latonita
|
||||||
esphome/components/nau7802/* @cujomalainey
|
esphome/components/nau7802/* @cujomalainey
|
||||||
esphome/components/network/* @esphome/core
|
esphome/components/network/* @esphome/core
|
||||||
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
esphome/components/nextion/* @edwardtfn @senexcrenshaw
|
||||||
|
@ -462,8 +462,6 @@ CONF_LVGL_ID = "lvgl_id"
|
|||||||
CONF_LONG_MODE = "long_mode"
|
CONF_LONG_MODE = "long_mode"
|
||||||
CONF_MSGBOXES = "msgboxes"
|
CONF_MSGBOXES = "msgboxes"
|
||||||
CONF_OBJ = "obj"
|
CONF_OBJ = "obj"
|
||||||
CONF_OFFSET_X = "offset_x"
|
|
||||||
CONF_OFFSET_Y = "offset_y"
|
|
||||||
CONF_ONE_CHECKED = "one_checked"
|
CONF_ONE_CHECKED = "one_checked"
|
||||||
CONF_ONE_LINE = "one_line"
|
CONF_ONE_LINE = "one_line"
|
||||||
CONF_ON_PAUSE = "on_pause"
|
CONF_ON_PAUSE = "on_pause"
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ANGLE, CONF_MODE
|
from esphome.const import CONF_ANGLE, CONF_MODE, CONF_OFFSET_X, CONF_OFFSET_Y
|
||||||
|
|
||||||
from ..defines import (
|
from ..defines import (
|
||||||
CONF_ANTIALIAS,
|
CONF_ANTIALIAS,
|
||||||
CONF_MAIN,
|
CONF_MAIN,
|
||||||
CONF_OFFSET_X,
|
|
||||||
CONF_OFFSET_Y,
|
|
||||||
CONF_PIVOT_X,
|
CONF_PIVOT_X,
|
||||||
CONF_PIVOT_Y,
|
CONF_PIVOT_Y,
|
||||||
CONF_SRC,
|
CONF_SRC,
|
||||||
|
189
esphome/components/msa3xx/__init__.py
Normal file
189
esphome/components/msa3xx/__init__.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
from esphome import automation
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CALIBRATION,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_MIRROR_X,
|
||||||
|
CONF_MIRROR_Y,
|
||||||
|
CONF_OFFSET_X,
|
||||||
|
CONF_OFFSET_Y,
|
||||||
|
CONF_OFFSET_Z,
|
||||||
|
CONF_RANGE,
|
||||||
|
CONF_RESOLUTION,
|
||||||
|
CONF_SWAP_XY,
|
||||||
|
CONF_TRANSFORM,
|
||||||
|
CONF_TYPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
CONF_MSA3XX_ID = "msa3xx_id"
|
||||||
|
|
||||||
|
CONF_MIRROR_Z = "mirror_z"
|
||||||
|
CONF_ON_ACTIVE = "on_active"
|
||||||
|
CONF_ON_DOUBLE_TAP = "on_double_tap"
|
||||||
|
CONF_ON_FREEFALL = "on_freefall"
|
||||||
|
CONF_ON_ORIENTATION = "on_orientation"
|
||||||
|
CONF_ON_TAP = "on_tap"
|
||||||
|
|
||||||
|
MODEL_MSA301 = "MSA301"
|
||||||
|
MODEL_MSA311 = "MSA311"
|
||||||
|
|
||||||
|
msa3xx_ns = cg.esphome_ns.namespace("msa3xx")
|
||||||
|
MSA3xxComponent = msa3xx_ns.class_(
|
||||||
|
"MSA3xxComponent", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
MSAModels = msa3xx_ns.enum("Model", True)
|
||||||
|
MSA_MODELS = {
|
||||||
|
MODEL_MSA301: MSAModels.MSA301,
|
||||||
|
MODEL_MSA311: MSAModels.MSA311,
|
||||||
|
}
|
||||||
|
|
||||||
|
MSARange = msa3xx_ns.enum("Range", True)
|
||||||
|
MSA_RANGES = {
|
||||||
|
"2G": MSARange.RANGE_2G,
|
||||||
|
"4G": MSARange.RANGE_4G,
|
||||||
|
"8G": MSARange.RANGE_8G,
|
||||||
|
"16G": MSARange.RANGE_16G,
|
||||||
|
}
|
||||||
|
|
||||||
|
MSAResolution = msa3xx_ns.enum("Resolution", True)
|
||||||
|
RESOLUTIONS_MSA301 = {
|
||||||
|
14: MSAResolution.RES_14BIT,
|
||||||
|
12: MSAResolution.RES_12BIT,
|
||||||
|
10: MSAResolution.RES_10BIT,
|
||||||
|
8: MSAResolution.RES_8BIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
RESOLUTIONS_MSA311 = {
|
||||||
|
12: MSAResolution.RES_12BIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
_COMMON_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(MSA3xxComponent),
|
||||||
|
cv.Optional(CONF_RANGE, default="2G"): cv.enum(MSA_RANGES, upper=True),
|
||||||
|
cv.Optional(CONF_CALIBRATION): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OFFSET_X, default=0): cv.float_range(
|
||||||
|
min=-4.5, max=4.5
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_OFFSET_Y, default=0): cv.float_range(
|
||||||
|
min=-4.5, max=4.5
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_OFFSET_Z, default=0): cv.float_range(
|
||||||
|
min=-4.5, max=4.5
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_MIRROR_Z, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ON_ACTIVE): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_TAP): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_DOUBLE_TAP): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_FREEFALL): automation.validate_automation(single=True),
|
||||||
|
cv.Optional(CONF_ON_ORIENTATION): automation.validate_automation(single=True),
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("10s"))
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.typed_schema(
|
||||||
|
{
|
||||||
|
MODEL_MSA301: _COMMON_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_RESOLUTION, default=14): cv.enum(RESOLUTIONS_MSA301),
|
||||||
|
}
|
||||||
|
).extend(i2c.i2c_device_schema(0x26)),
|
||||||
|
MODEL_MSA311: _COMMON_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_RESOLUTION, default=12): cv.enum(RESOLUTIONS_MSA311),
|
||||||
|
}
|
||||||
|
).extend(i2c.i2c_device_schema(0x62)),
|
||||||
|
},
|
||||||
|
upper=True,
|
||||||
|
enum=MSA_MODELS,
|
||||||
|
)
|
||||||
|
|
||||||
|
MSA_SENSOR_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_MSA3XX_ID): cv.use_id(MSA3xxComponent),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
cg.add(var.set_model(config[CONF_TYPE]))
|
||||||
|
cg.add(var.set_range(MSA_RANGES[config[CONF_RANGE]]))
|
||||||
|
cg.add(var.set_resolution(RESOLUTIONS_MSA301[config[CONF_RESOLUTION]]))
|
||||||
|
|
||||||
|
if transform := config.get(CONF_TRANSFORM):
|
||||||
|
cg.add(
|
||||||
|
var.set_transform(
|
||||||
|
transform[CONF_MIRROR_X],
|
||||||
|
transform[CONF_MIRROR_Y],
|
||||||
|
transform[CONF_MIRROR_Z],
|
||||||
|
transform[CONF_SWAP_XY],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if calibration_config := config.get(CONF_CALIBRATION):
|
||||||
|
cg.add(
|
||||||
|
var.set_offset(
|
||||||
|
calibration_config[CONF_OFFSET_X],
|
||||||
|
calibration_config[CONF_OFFSET_Y],
|
||||||
|
calibration_config[CONF_OFFSET_Z],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Triggers secton
|
||||||
|
|
||||||
|
if CONF_ON_ORIENTATION in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_orientation_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_ORIENTATION],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_TAP in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_tap_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_TAP],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_DOUBLE_TAP in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_double_tap_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_DOUBLE_TAP],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_ACTIVE in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_active_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_ACTIVE],
|
||||||
|
)
|
||||||
|
|
||||||
|
if CONF_ON_FREEFALL in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_freefall_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_FREEFALL],
|
||||||
|
)
|
40
esphome/components/msa3xx/binary_sensor.py
Normal file
40
esphome/components/msa3xx/binary_sensor.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import binary_sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ACTIVE, CONF_NAME, DEVICE_CLASS_VIBRATION, ICON_VIBRATE
|
||||||
|
|
||||||
|
from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
|
||||||
|
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["msa3xx"]
|
||||||
|
|
||||||
|
CONF_TAP = "tap"
|
||||||
|
CONF_DOUBLE_TAP = "double_tap"
|
||||||
|
|
||||||
|
ICON_TAP = "mdi:gesture-tap"
|
||||||
|
ICON_DOUBLE_TAP = "mdi:gesture-double-tap"
|
||||||
|
|
||||||
|
EVENT_SENSORS = (CONF_TAP, CONF_DOUBLE_TAP, CONF_ACTIVE)
|
||||||
|
ICONS = (ICON_TAP, ICON_DOUBLE_TAP, ICON_VIBRATE)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(event): cv.maybe_simple_value(
|
||||||
|
binary_sensor.binary_sensor_schema(
|
||||||
|
device_class=DEVICE_CLASS_VIBRATION,
|
||||||
|
icon=icon,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
)
|
||||||
|
for event, icon in zip(EVENT_SENSORS, ICONS)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
hub = await cg.get_variable(config[CONF_MSA3XX_ID])
|
||||||
|
|
||||||
|
for sensor in EVENT_SENSORS:
|
||||||
|
if sensor in config:
|
||||||
|
sens = await binary_sensor.new_binary_sensor(config[sensor])
|
||||||
|
cg.add(getattr(hub, f"set_{sensor}_binary_sensor")(sens))
|
417
esphome/components/msa3xx/msa3xx.cpp
Normal file
417
esphome/components/msa3xx/msa3xx.cpp
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
#include "msa3xx.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace msa3xx {
|
||||||
|
|
||||||
|
static const char *const TAG = "msa3xx";
|
||||||
|
|
||||||
|
const uint8_t MSA_3XX_PART_ID = 0x13;
|
||||||
|
|
||||||
|
const float GRAVITY_EARTH = 9.80665f;
|
||||||
|
const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9); // LSB to 1 LSB = 3.9mg = 0.0039g
|
||||||
|
const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||||
|
const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
|
||||||
|
|
||||||
|
const uint8_t RESOLUTION[] = {14, 12, 10, 8};
|
||||||
|
|
||||||
|
const uint32_t TAP_COOLDOWN_MS = 500;
|
||||||
|
const uint32_t DOUBLE_TAP_COOLDOWN_MS = 500;
|
||||||
|
const uint32_t ACTIVITY_COOLDOWN_MS = 500;
|
||||||
|
|
||||||
|
const char *model_to_string(Model model) {
|
||||||
|
switch (model) {
|
||||||
|
case Model::MSA301:
|
||||||
|
return "MSA301";
|
||||||
|
case Model::MSA311:
|
||||||
|
return "MSA311";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *power_mode_to_string(PowerMode power_mode) {
|
||||||
|
switch (power_mode) {
|
||||||
|
case PowerMode::NORMAL:
|
||||||
|
return "Normal";
|
||||||
|
case PowerMode::LOW_POWER:
|
||||||
|
return "Low Power";
|
||||||
|
case PowerMode::SUSPEND:
|
||||||
|
return "Suspend";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *res_to_string(Resolution resolution) {
|
||||||
|
switch (resolution) {
|
||||||
|
case Resolution::RES_14BIT:
|
||||||
|
return "14-bit";
|
||||||
|
case Resolution::RES_12BIT:
|
||||||
|
return "12-bit";
|
||||||
|
case Resolution::RES_10BIT:
|
||||||
|
return "10-bit";
|
||||||
|
case Resolution::RES_8BIT:
|
||||||
|
return "8-bit";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *range_to_string(Range range) {
|
||||||
|
switch (range) {
|
||||||
|
case Range::RANGE_2G:
|
||||||
|
return "±2g";
|
||||||
|
case Range::RANGE_4G:
|
||||||
|
return "±4g";
|
||||||
|
case Range::RANGE_8G:
|
||||||
|
return "±8g";
|
||||||
|
case Range::RANGE_16G:
|
||||||
|
return "±16g";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *bandwidth_to_string(Bandwidth bandwidth) {
|
||||||
|
switch (bandwidth) {
|
||||||
|
case Bandwidth::BW_1_95HZ:
|
||||||
|
return "1.95 Hz";
|
||||||
|
case Bandwidth::BW_3_9HZ:
|
||||||
|
return "3.9 Hz";
|
||||||
|
case Bandwidth::BW_7_81HZ:
|
||||||
|
return "7.81 Hz";
|
||||||
|
case Bandwidth::BW_15_63HZ:
|
||||||
|
return "15.63 Hz";
|
||||||
|
case Bandwidth::BW_31_25HZ:
|
||||||
|
return "31.25 Hz";
|
||||||
|
case Bandwidth::BW_62_5HZ:
|
||||||
|
return "62.5 Hz";
|
||||||
|
case Bandwidth::BW_125HZ:
|
||||||
|
return "125 Hz";
|
||||||
|
case Bandwidth::BW_250HZ:
|
||||||
|
return "250 Hz";
|
||||||
|
case Bandwidth::BW_500HZ:
|
||||||
|
return "500 Hz";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *orientation_xy_to_string(OrientationXY orientation) {
|
||||||
|
switch (orientation) {
|
||||||
|
case OrientationXY::PORTRAIT_UPRIGHT:
|
||||||
|
return "Portrait Upright";
|
||||||
|
case OrientationXY::PORTRAIT_UPSIDE_DOWN:
|
||||||
|
return "Portrait Upside Down";
|
||||||
|
case OrientationXY::LANDSCAPE_LEFT:
|
||||||
|
return "Landscape Left";
|
||||||
|
case OrientationXY::LANDSCAPE_RIGHT:
|
||||||
|
return "Landscape Right";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *orientation_z_to_string(bool orientation) { return orientation ? "Downwards looking" : "Upwards looking"; }
|
||||||
|
|
||||||
|
void MSA3xxComponent::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MSA3xx...");
|
||||||
|
|
||||||
|
uint8_t part_id{0xff};
|
||||||
|
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::PART_ID), &part_id) || (part_id != MSA_3XX_PART_ID)) {
|
||||||
|
ESP_LOGE(TAG, "Part ID is wrong or missing. Got 0x%02X", part_id);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolution LSB/g
|
||||||
|
// Range : MSA301 : MSA311
|
||||||
|
// S2g : 1024 (2^10) : 4096 (2^12)
|
||||||
|
// S4g : 512 (2^9) : 2048 (2^11)
|
||||||
|
// S8g : 256 (2^8) : 1024 (2^10)
|
||||||
|
// S16g : 128 (2^7) : 512 (2^9)
|
||||||
|
if (this->model_ == Model::MSA301) {
|
||||||
|
this->device_params_.accel_data_width = 14;
|
||||||
|
this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 12;
|
||||||
|
} else if (this->model_ == Model::MSA311) {
|
||||||
|
this->device_params_.accel_data_width = 12;
|
||||||
|
this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 10;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Unknown model");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->setup_odr_(this->data_rate_);
|
||||||
|
this->setup_power_mode_bandwidth_(this->power_mode_, this->bandwidth_);
|
||||||
|
this->setup_range_resolution_(this->range_, this->resolution_); // 2g...16g, 14...8 bit
|
||||||
|
this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_); // calibration offsets
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::TAP_DURATION), 0b11000100); // set tap duration 250ms
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::SWAP_POLARITY), this->swap_.raw); // set axes polarity
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), 0b01110111); // enable all interrupts
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), 0b00011000); // including orientation
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "MSA3xx:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with MSA3xx failed!");
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: %s", model_to_string(this->model_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Power Mode: %s", power_mode_to_string(this->power_mode_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Bandwidth: %s", bandwidth_to_string(this->bandwidth_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Range: %s", range_to_string(this->range_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Resolution: %s", res_to_string(this->resolution_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}", this->offset_x_, this->offset_y_, this->offset_z_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}", YESNO(this->swap_.x_polarity),
|
||||||
|
YESNO(this->swap_.y_polarity), YESNO(this->swap_.z_polarity), YESNO(this->swap_.x_y_swap));
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
LOG_BINARY_SENSOR(" ", "Tap", this->tap_binary_sensor_);
|
||||||
|
LOG_BINARY_SENSOR(" ", "Double Tap", this->double_tap_binary_sensor_);
|
||||||
|
LOG_BINARY_SENSOR(" ", "Active", this->active_binary_sensor_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
LOG_SENSOR(" ", "Acceleration X", this->acceleration_x_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
LOG_TEXT_SENSOR(" ", "Orientation XY", this->orientation_xy_text_sensor_);
|
||||||
|
LOG_TEXT_SENSOR(" ", "Orientation Z", this->orientation_z_text_sensor_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MSA3xxComponent::read_data_() {
|
||||||
|
uint8_t accel_data[6];
|
||||||
|
if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::ACC_X_LSB), accel_data, 6)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto raw_to_x_bit = [](uint16_t lsb, uint16_t msb, uint8_t data_bits) -> uint16_t {
|
||||||
|
return ((msb << 8) | lsb) >> (16 - data_bits);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto lpf = [](float new_value, float old_value, float alpha = 0.5f) {
|
||||||
|
return alpha * new_value + (1.0f - alpha) * old_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
this->data_.lsb_x =
|
||||||
|
this->twos_complement_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width),
|
||||||
|
this->device_params_.accel_data_width);
|
||||||
|
this->data_.lsb_y =
|
||||||
|
this->twos_complement_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width),
|
||||||
|
this->device_params_.accel_data_width);
|
||||||
|
this->data_.lsb_z =
|
||||||
|
this->twos_complement_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width),
|
||||||
|
this->device_params_.accel_data_width);
|
||||||
|
|
||||||
|
this->data_.x = lpf(ldexp(this->data_.lsb_x, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.x);
|
||||||
|
this->data_.y = lpf(ldexp(this->data_.lsb_y, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.y);
|
||||||
|
this->data_.z = lpf(ldexp(this->data_.lsb_z, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.z);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MSA3xxComponent::read_motion_status_() {
|
||||||
|
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::MOTION_INTERRUPT), &this->status_.motion_int.raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->read_byte(static_cast<uint8_t>(RegisterMap::ORIENTATION_STATUS), &this->status_.orientation.raw)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::loop() {
|
||||||
|
if (!this->is_ready()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegMotionInterrupt old_motion_int = this->status_.motion_int;
|
||||||
|
|
||||||
|
if (!this->read_data_() || !this->read_motion_status_()) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->process_motions_(old_motion_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::update() {
|
||||||
|
ESP_LOGV(TAG, "Updating MSA3xx...");
|
||||||
|
|
||||||
|
if (!this->is_ready()) {
|
||||||
|
ESP_LOGV(TAG, "Component MSA3xx not ready for update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Acceleration: {x = %+1.3f m/s², y = %+1.3f m/s², z = %+1.3f m/s²}; ", this->data_.x, this->data_.y,
|
||||||
|
this->data_.z);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Orientation: {XY = %s, Z = %s}", orientation_xy_to_string(this->status_.orientation.orient_xy),
|
||||||
|
orientation_z_to_string(this->status_.orientation.orient_z));
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
if (this->acceleration_x_sensor_ != nullptr)
|
||||||
|
this->acceleration_x_sensor_->publish_state(this->data_.x);
|
||||||
|
if (this->acceleration_y_sensor_ != nullptr)
|
||||||
|
this->acceleration_y_sensor_->publish_state(this->data_.y);
|
||||||
|
if (this->acceleration_z_sensor_ != nullptr)
|
||||||
|
this->acceleration_z_sensor_->publish_state(this->data_.z);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
if (this->orientation_xy_text_sensor_ != nullptr &&
|
||||||
|
(this->status_.orientation.orient_xy != this->status_.orientation_old.orient_xy ||
|
||||||
|
this->status_.never_published)) {
|
||||||
|
this->orientation_xy_text_sensor_->publish_state(orientation_xy_to_string(this->status_.orientation.orient_xy));
|
||||||
|
}
|
||||||
|
if (this->orientation_z_text_sensor_ != nullptr &&
|
||||||
|
(this->status_.orientation.orient_z != this->status_.orientation_old.orient_z || this->status_.never_published)) {
|
||||||
|
this->orientation_z_text_sensor_->publish_state(orientation_z_to_string(this->status_.orientation.orient_z));
|
||||||
|
}
|
||||||
|
this->status_.orientation_old = this->status_.orientation;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->status_.never_published = false;
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
float MSA3xxComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z) {
|
||||||
|
this->offset_x_ = offset_x;
|
||||||
|
this->offset_y_ = offset_y;
|
||||||
|
this->offset_z_ = offset_z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) {
|
||||||
|
this->swap_.x_polarity = mirror_x;
|
||||||
|
this->swap_.y_polarity = mirror_y;
|
||||||
|
this->swap_.z_polarity = mirror_z;
|
||||||
|
this->swap_.x_y_swap = swap_xy;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::setup_odr_(DataRate rate) {
|
||||||
|
RegOutputDataRate reg_odr;
|
||||||
|
auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::ODR));
|
||||||
|
if (reg.has_value()) {
|
||||||
|
reg_odr.raw = reg.value();
|
||||||
|
} else {
|
||||||
|
reg_odr.raw = 0x0F; // defaut from datasheet
|
||||||
|
}
|
||||||
|
|
||||||
|
reg_odr.x_axis_disable = false;
|
||||||
|
reg_odr.y_axis_disable = false;
|
||||||
|
reg_odr.z_axis_disable = false;
|
||||||
|
reg_odr.odr = rate;
|
||||||
|
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::ODR), reg_odr.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth) {
|
||||||
|
// 0x11 POWER_MODE_BANDWIDTH
|
||||||
|
auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH));
|
||||||
|
|
||||||
|
RegPowerModeBandwidth power_mode_bandwidth;
|
||||||
|
if (reg.has_value()) {
|
||||||
|
power_mode_bandwidth.raw = reg.value();
|
||||||
|
} else {
|
||||||
|
power_mode_bandwidth.raw = 0xde; // defaut from datasheet
|
||||||
|
}
|
||||||
|
|
||||||
|
power_mode_bandwidth.power_mode = power_mode;
|
||||||
|
power_mode_bandwidth.low_power_bandwidth = bandwidth;
|
||||||
|
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH), power_mode_bandwidth.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::setup_range_resolution_(Range range, Resolution resolution) {
|
||||||
|
RegRangeResolution reg;
|
||||||
|
reg.raw = this->read_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION)).value_or(0x00);
|
||||||
|
reg.range = range;
|
||||||
|
if (this->model_ == Model::MSA301) {
|
||||||
|
reg.resolution = resolution;
|
||||||
|
}
|
||||||
|
this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset_z) {
|
||||||
|
uint8_t offset[3];
|
||||||
|
|
||||||
|
auto offset_g_to_lsb = [](float accel) -> int8_t {
|
||||||
|
float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX);
|
||||||
|
return static_cast<int8_t>(acccel_clamped * LSB_COEFF);
|
||||||
|
};
|
||||||
|
|
||||||
|
offset[0] = offset_g_to_lsb(offset_x);
|
||||||
|
offset[1] = offset_g_to_lsb(offset_y);
|
||||||
|
offset[2] = offset_g_to_lsb(offset_z);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Offset (%.3f, %.3f, %.3f)=>LSB(%d, %d, %d)", offset_x, offset_y, offset_z, offset[0], offset[1],
|
||||||
|
offset[2]);
|
||||||
|
|
||||||
|
this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) {
|
||||||
|
if (value > (1ULL << (bits - 1))) {
|
||||||
|
return (int64_t) (value - (1ULL << bits));
|
||||||
|
} else {
|
||||||
|
return (int64_t) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void binary_event_debounce(bool state, bool old_state, uint32_t now, uint32_t &last_ms, Trigger<> &trigger,
|
||||||
|
uint32_t cooldown_ms, void *bs, const char *desc) {
|
||||||
|
if (state && now - last_ms > cooldown_ms) {
|
||||||
|
ESP_LOGV(TAG, "%s detected", desc);
|
||||||
|
trigger.trigger();
|
||||||
|
last_ms = now;
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
if (bs != nullptr) {
|
||||||
|
static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else if (!state && now - last_ms > cooldown_ms && bs != nullptr) {
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
#define BS_OPTIONAL_PTR(x) ((void *) (x))
|
||||||
|
#else
|
||||||
|
#define BS_OPTIONAL_PTR(x) (nullptr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void MSA3xxComponent::process_motions_(RegMotionInterrupt old) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
binary_event_debounce(this->status_.motion_int.single_tap_interrupt, old.single_tap_interrupt, now,
|
||||||
|
this->status_.last_tap_ms, this->tap_trigger_, TAP_COOLDOWN_MS,
|
||||||
|
BS_OPTIONAL_PTR(this->tap_binary_sensor_), "Tap");
|
||||||
|
binary_event_debounce(this->status_.motion_int.double_tap_interrupt, old.double_tap_interrupt, now,
|
||||||
|
this->status_.last_double_tap_ms, this->double_tap_trigger_, DOUBLE_TAP_COOLDOWN_MS,
|
||||||
|
BS_OPTIONAL_PTR(this->double_tap_binary_sensor_), "Double Tap");
|
||||||
|
binary_event_debounce(this->status_.motion_int.active_interrupt, old.active_interrupt, now,
|
||||||
|
this->status_.last_action_ms, this->active_trigger_, ACTIVITY_COOLDOWN_MS,
|
||||||
|
BS_OPTIONAL_PTR(this->active_binary_sensor_), "Activity");
|
||||||
|
|
||||||
|
if (this->status_.motion_int.orientation_interrupt) {
|
||||||
|
ESP_LOGVV(TAG, "Orientation changed");
|
||||||
|
this->orientation_trigger_.trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace msa3xx
|
||||||
|
} // namespace esphome
|
311
esphome/components/msa3xx/msa3xx.h
Normal file
311
esphome/components/msa3xx/msa3xx.h
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace msa3xx {
|
||||||
|
|
||||||
|
// Combined register map of MSA301 and MSA311
|
||||||
|
// Differences
|
||||||
|
// What | MSA301 | MSA11 |
|
||||||
|
// - Resolution | 14-bit | 12-bit |
|
||||||
|
//
|
||||||
|
|
||||||
|
// I2c address
|
||||||
|
enum class Model : uint8_t {
|
||||||
|
MSA301 = 0x26,
|
||||||
|
MSA311 = 0x62,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combined MSA301 and MSA311 register map
|
||||||
|
enum class RegisterMap : uint8_t {
|
||||||
|
SOFT_RESET = 0x00,
|
||||||
|
PART_ID = 0x01,
|
||||||
|
ACC_X_LSB = 0x02,
|
||||||
|
ACC_X_MSB = 0x03,
|
||||||
|
ACC_Y_LSB = 0x04,
|
||||||
|
ACC_Y_MSB = 0x05,
|
||||||
|
ACC_Z_LSB = 0x06,
|
||||||
|
ACC_Z_MSB = 0x07,
|
||||||
|
MOTION_INTERRUPT = 0x09,
|
||||||
|
DATA_INTERRUPT = 0x0A,
|
||||||
|
TAP_ACTIVE_STATUS = 0x0B,
|
||||||
|
ORIENTATION_STATUS = 0x0C,
|
||||||
|
RESOLUTION_RANGE_CONFIG = 0x0D,
|
||||||
|
RANGE_RESOLUTION = 0x0F,
|
||||||
|
ODR = 0x10,
|
||||||
|
POWER_MODE_BANDWIDTH = 0x11,
|
||||||
|
SWAP_POLARITY = 0x12,
|
||||||
|
INT_SET_0 = 0x16,
|
||||||
|
INT_SET_1 = 0x17,
|
||||||
|
INT_MAP_0 = 0x19,
|
||||||
|
INT_MAP_1 = 0x1A,
|
||||||
|
INT_CONFIG = 0x20,
|
||||||
|
INT_LATCH = 0x21,
|
||||||
|
FREEFALL_DURATION = 0x22,
|
||||||
|
FREEFALL_THRESHOLD = 0x23,
|
||||||
|
FREEFALL_HYSTERESIS = 0x24,
|
||||||
|
ACTIVE_DURATION = 0x27,
|
||||||
|
ACTIVE_THRESHOLD = 0x28,
|
||||||
|
TAP_DURATION = 0x2A,
|
||||||
|
TAP_THRESHOLD = 0x2B,
|
||||||
|
ORIENTATION_CONFIG = 0x2C,
|
||||||
|
Z_BLOCK = 0x2D,
|
||||||
|
OFFSET_COMP_X = 0x38,
|
||||||
|
OFFSET_COMP_Y = 0x39,
|
||||||
|
OFFSET_COMP_Z = 0x3A,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Range : uint8_t {
|
||||||
|
RANGE_2G = 0b00,
|
||||||
|
RANGE_4G = 0b01,
|
||||||
|
RANGE_8G = 0b10,
|
||||||
|
RANGE_16G = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Resolution : uint8_t {
|
||||||
|
RES_14BIT = 0b00,
|
||||||
|
RES_12BIT = 0b01,
|
||||||
|
RES_10BIT = 0b10,
|
||||||
|
RES_8BIT = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PowerMode : uint8_t {
|
||||||
|
NORMAL = 0b00,
|
||||||
|
LOW_POWER = 0b01,
|
||||||
|
SUSPEND = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Bandwidth : uint8_t {
|
||||||
|
BW_1_95HZ = 0b0000,
|
||||||
|
BW_3_9HZ = 0b0011,
|
||||||
|
BW_7_81HZ = 0b0100,
|
||||||
|
BW_15_63HZ = 0b0101,
|
||||||
|
BW_31_25HZ = 0b0110,
|
||||||
|
BW_62_5HZ = 0b0111,
|
||||||
|
BW_125HZ = 0b1000,
|
||||||
|
BW_250HZ = 0b1001,
|
||||||
|
BW_500HZ = 0b1010,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class DataRate : uint8_t {
|
||||||
|
ODR_1HZ = 0b0000, // not available in normal mode
|
||||||
|
ODR_1_95HZ = 0b0001, // not available in normal mode
|
||||||
|
ODR_3_9HZ = 0b0010,
|
||||||
|
ODR_7_81HZ = 0b0011,
|
||||||
|
ODR_15_63HZ = 0b0100,
|
||||||
|
ODR_31_25HZ = 0b0101,
|
||||||
|
ODR_62_5HZ = 0b0110,
|
||||||
|
ODR_125HZ = 0b0111,
|
||||||
|
ODR_250HZ = 0b1000,
|
||||||
|
ODR_500HZ = 0b1001, // not available in low power mode
|
||||||
|
ODR_1000HZ = 0b1010, // not available in low power mode
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class OrientationXY : uint8_t {
|
||||||
|
PORTRAIT_UPRIGHT = 0b00,
|
||||||
|
PORTRAIT_UPSIDE_DOWN = 0b01,
|
||||||
|
LANDSCAPE_LEFT = 0b10,
|
||||||
|
LANDSCAPE_RIGHT = 0b11,
|
||||||
|
};
|
||||||
|
|
||||||
|
union Orientation {
|
||||||
|
struct {
|
||||||
|
OrientationXY xy : 2;
|
||||||
|
bool z : 1;
|
||||||
|
uint8_t reserved : 5;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x09
|
||||||
|
union RegMotionInterrupt {
|
||||||
|
struct {
|
||||||
|
bool freefall_interrupt : 1;
|
||||||
|
bool reserved_1 : 1;
|
||||||
|
bool active_interrupt : 1;
|
||||||
|
bool reserved_3 : 1;
|
||||||
|
bool double_tap_interrupt : 1;
|
||||||
|
bool single_tap_interrupt : 1;
|
||||||
|
bool orientation_interrupt : 1;
|
||||||
|
bool reserved_7 : 1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x0C
|
||||||
|
union RegOrientationStatus {
|
||||||
|
struct {
|
||||||
|
uint8_t reserved_0_3 : 4;
|
||||||
|
OrientationXY orient_xy : 2;
|
||||||
|
bool orient_z : 1;
|
||||||
|
uint8_t reserved_7 : 1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0x00};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x0f
|
||||||
|
union RegRangeResolution {
|
||||||
|
struct {
|
||||||
|
Range range : 2;
|
||||||
|
Resolution resolution : 2;
|
||||||
|
uint8_t reserved_2 : 4;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0x00};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x10
|
||||||
|
union RegOutputDataRate {
|
||||||
|
struct {
|
||||||
|
DataRate odr : 4;
|
||||||
|
uint8_t reserved_4 : 1;
|
||||||
|
bool z_axis_disable : 1;
|
||||||
|
bool y_axis_disable : 1;
|
||||||
|
bool x_axis_disable : 1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0xde};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x11
|
||||||
|
union RegPowerModeBandwidth {
|
||||||
|
struct {
|
||||||
|
uint8_t reserved_0 : 1;
|
||||||
|
Bandwidth low_power_bandwidth : 4;
|
||||||
|
uint8_t reserved_5 : 1;
|
||||||
|
PowerMode power_mode : 2;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0xde};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x12
|
||||||
|
union RegSwapPolarity {
|
||||||
|
struct {
|
||||||
|
bool x_y_swap : 1;
|
||||||
|
bool z_polarity : 1;
|
||||||
|
bool y_polarity : 1;
|
||||||
|
bool x_polarity : 1;
|
||||||
|
uint8_t reserved : 4;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0x2a
|
||||||
|
union RegTapDuration {
|
||||||
|
struct {
|
||||||
|
uint8_t duration : 3;
|
||||||
|
uint8_t reserved : 3;
|
||||||
|
bool tap_shock : 1;
|
||||||
|
bool tap_quiet : 1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
uint8_t raw{0x04};
|
||||||
|
};
|
||||||
|
|
||||||
|
class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void set_model(Model model) { this->model_ = model; }
|
||||||
|
void set_offset(float offset_x, float offset_y, float offset_z);
|
||||||
|
void set_range(Range range) { this->range_ = range; }
|
||||||
|
void set_bandwidth(Bandwidth bandwidth) { this->bandwidth_ = bandwidth; }
|
||||||
|
void set_resolution(Resolution resolution) { this->resolution_ = resolution; }
|
||||||
|
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy);
|
||||||
|
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
|
SUB_BINARY_SENSOR(tap)
|
||||||
|
SUB_BINARY_SENSOR(double_tap)
|
||||||
|
SUB_BINARY_SENSOR(active)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_SENSOR
|
||||||
|
SUB_SENSOR(acceleration_x)
|
||||||
|
SUB_SENSOR(acceleration_y)
|
||||||
|
SUB_SENSOR(acceleration_z)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
SUB_TEXT_SENSOR(orientation_xy)
|
||||||
|
SUB_TEXT_SENSOR(orientation_z)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Trigger<> *get_tap_trigger() { return &this->tap_trigger_; }
|
||||||
|
Trigger<> *get_double_tap_trigger() { return &this->double_tap_trigger_; }
|
||||||
|
Trigger<> *get_orientation_trigger() { return &this->orientation_trigger_; }
|
||||||
|
Trigger<> *get_freefall_trigger() { return &this->freefall_trigger_; }
|
||||||
|
Trigger<> *get_active_trigger() { return &this->active_trigger_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Model model_{Model::MSA311};
|
||||||
|
|
||||||
|
PowerMode power_mode_{PowerMode::NORMAL};
|
||||||
|
DataRate data_rate_{DataRate::ODR_250HZ};
|
||||||
|
Bandwidth bandwidth_{Bandwidth::BW_250HZ};
|
||||||
|
Range range_{Range::RANGE_2G};
|
||||||
|
Resolution resolution_{Resolution::RES_14BIT};
|
||||||
|
float offset_x_, offset_y_, offset_z_; // in m/s²
|
||||||
|
RegSwapPolarity swap_;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int scale_factor_exp;
|
||||||
|
uint8_t accel_data_width;
|
||||||
|
} device_params_{};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int16_t lsb_x, lsb_y, lsb_z;
|
||||||
|
float x, y, z;
|
||||||
|
} data_{};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
RegMotionInterrupt motion_int;
|
||||||
|
RegOrientationStatus orientation;
|
||||||
|
RegOrientationStatus orientation_old;
|
||||||
|
|
||||||
|
uint32_t last_tap_ms{0};
|
||||||
|
uint32_t last_double_tap_ms{0};
|
||||||
|
uint32_t last_action_ms{0};
|
||||||
|
|
||||||
|
bool never_published{true};
|
||||||
|
} status_{};
|
||||||
|
|
||||||
|
void setup_odr_(DataRate rate);
|
||||||
|
void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth);
|
||||||
|
void setup_range_resolution_(Range range, Resolution resolution);
|
||||||
|
void setup_offset_(float offset_x, float offset_y, float offset_z);
|
||||||
|
|
||||||
|
bool read_data_();
|
||||||
|
bool read_motion_status_();
|
||||||
|
|
||||||
|
int64_t twos_complement_(uint64_t value, uint8_t bits);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Actons / Triggers
|
||||||
|
//
|
||||||
|
Trigger<> tap_trigger_;
|
||||||
|
Trigger<> double_tap_trigger_;
|
||||||
|
Trigger<> orientation_trigger_;
|
||||||
|
Trigger<> freefall_trigger_;
|
||||||
|
Trigger<> active_trigger_;
|
||||||
|
|
||||||
|
void process_motions_(RegMotionInterrupt old);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace msa3xx
|
||||||
|
} // namespace esphome
|
42
esphome/components/msa3xx/sensor.py
Normal file
42
esphome/components/msa3xx/sensor.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ACCELERATION_X,
|
||||||
|
CONF_ACCELERATION_Y,
|
||||||
|
CONF_ACCELERATION_Z,
|
||||||
|
CONF_NAME,
|
||||||
|
ICON_BRIEFCASE_DOWNLOAD,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_METER_PER_SECOND_SQUARED,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
|
||||||
|
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["msa3xx"]
|
||||||
|
|
||||||
|
ACCELERATION_SENSORS = (CONF_ACCELERATION_X, CONF_ACCELERATION_Y, CONF_ACCELERATION_Z)
|
||||||
|
|
||||||
|
accel_schema = cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED,
|
||||||
|
icon=ICON_BRIEFCASE_DOWNLOAD,
|
||||||
|
accuracy_decimals=2,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
|
||||||
|
{cv.Optional(sensor): accel_schema for sensor in ACCELERATION_SENSORS}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
hub = await cg.get_variable(config[CONF_MSA3XX_ID])
|
||||||
|
for accel_key in ACCELERATION_SENSORS:
|
||||||
|
if accel_key in config:
|
||||||
|
sens = await sensor.new_sensor(config[accel_key])
|
||||||
|
cg.add(getattr(hub, f"set_{accel_key}_sensor")(sens))
|
38
esphome/components/msa3xx/text_sensor.py
Normal file
38
esphome/components/msa3xx/text_sensor.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import text_sensor
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_NAME
|
||||||
|
|
||||||
|
from . import CONF_MSA3XX_ID, MSA_SENSOR_SCHEMA
|
||||||
|
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["msa3xx"]
|
||||||
|
|
||||||
|
CONF_ORIENTATION_XY = "orientation_xy"
|
||||||
|
CONF_ORIENTATION_Z = "orientation_z"
|
||||||
|
ICON_SCREEN_ROTATION = "mdi:screen-rotation"
|
||||||
|
|
||||||
|
ORIENTATION_SENSORS = (CONF_ORIENTATION_XY, CONF_ORIENTATION_Z)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = MSA_SENSOR_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.Optional(sensor): cv.maybe_simple_value(
|
||||||
|
text_sensor.text_sensor_schema(icon=ICON_SCREEN_ROTATION),
|
||||||
|
key=CONF_NAME,
|
||||||
|
)
|
||||||
|
for sensor in ORIENTATION_SENSORS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_conf(config, key, hub):
|
||||||
|
if sensor_config := config.get(key):
|
||||||
|
var = await text_sensor.new_text_sensor(sensor_config)
|
||||||
|
cg.add(getattr(hub, f"set_{key}_text_sensor")(var))
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
hub = await cg.get_variable(config[CONF_MSA3XX_ID])
|
||||||
|
|
||||||
|
for key in ORIENTATION_SENSORS:
|
||||||
|
await setup_conf(config, key, hub)
|
@ -1,15 +1,17 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import display
|
from esphome.components import display
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_EXTERNAL_VCC,
|
|
||||||
CONF_LAMBDA,
|
|
||||||
CONF_MODEL,
|
|
||||||
CONF_RESET_PIN,
|
|
||||||
CONF_BRIGHTNESS,
|
CONF_BRIGHTNESS,
|
||||||
CONF_CONTRAST,
|
CONF_CONTRAST,
|
||||||
|
CONF_EXTERNAL_VCC,
|
||||||
CONF_INVERT,
|
CONF_INVERT,
|
||||||
|
CONF_LAMBDA,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_OFFSET_X,
|
||||||
|
CONF_OFFSET_Y,
|
||||||
|
CONF_RESET_PIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
|
ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
|
||||||
@ -18,8 +20,6 @@ SSD1306Model = ssd1306_base_ns.enum("SSD1306Model")
|
|||||||
|
|
||||||
CONF_FLIP_X = "flip_x"
|
CONF_FLIP_X = "flip_x"
|
||||||
CONF_FLIP_Y = "flip_y"
|
CONF_FLIP_Y = "flip_y"
|
||||||
CONF_OFFSET_X = "offset_x"
|
|
||||||
CONF_OFFSET_Y = "offset_y"
|
|
||||||
|
|
||||||
MODELS = {
|
MODELS = {
|
||||||
"SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
|
"SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
|
||||||
|
@ -546,6 +546,9 @@ CONF_OFF_SPEED_CYCLE = "off_speed_cycle"
|
|||||||
CONF_OFFSET = "offset"
|
CONF_OFFSET = "offset"
|
||||||
CONF_OFFSET_HEIGHT = "offset_height"
|
CONF_OFFSET_HEIGHT = "offset_height"
|
||||||
CONF_OFFSET_WIDTH = "offset_width"
|
CONF_OFFSET_WIDTH = "offset_width"
|
||||||
|
CONF_OFFSET_X = "offset_x"
|
||||||
|
CONF_OFFSET_Y = "offset_y"
|
||||||
|
CONF_OFFSET_Z = "offset_z"
|
||||||
CONF_ON = "on"
|
CONF_ON = "on"
|
||||||
CONF_ON_BLE_ADVERTISE = "on_ble_advertise"
|
CONF_ON_BLE_ADVERTISE = "on_ble_advertise"
|
||||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise"
|
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise"
|
||||||
|
48
tests/components/msa3xx/common.yaml
Normal file
48
tests/components/msa3xx/common.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
msa3xx:
|
||||||
|
i2c_id: i2c_msa3xx
|
||||||
|
type: msa301
|
||||||
|
range: 4G
|
||||||
|
resolution: 14
|
||||||
|
update_interval: 10s
|
||||||
|
calibration:
|
||||||
|
offset_x: -0.250
|
||||||
|
offset_y: -0.400
|
||||||
|
offset_z: -0.800
|
||||||
|
transform:
|
||||||
|
mirror_x: false
|
||||||
|
mirror_y: true
|
||||||
|
mirror_z: true
|
||||||
|
swap_xy: false
|
||||||
|
on_tap:
|
||||||
|
- then:
|
||||||
|
- logger.log: "Tapped"
|
||||||
|
on_double_tap:
|
||||||
|
- then:
|
||||||
|
- logger.log: "Double tapped"
|
||||||
|
on_active:
|
||||||
|
- then:
|
||||||
|
- logger.log: "Activity detected"
|
||||||
|
on_orientation:
|
||||||
|
- then:
|
||||||
|
- logger.log: "Orientation changed"
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: msa3xx
|
||||||
|
acceleration_x: Accel X
|
||||||
|
acceleration_y: Accel Y
|
||||||
|
acceleration_z: Accel Z
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: msa3xx
|
||||||
|
orientation_xy: Orientation XY
|
||||||
|
orientation_z: Orientation Z
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: msa3xx
|
||||||
|
tap: Single tap
|
||||||
|
double_tap:
|
||||||
|
name: Double tap
|
||||||
|
active:
|
||||||
|
name: Active
|
||||||
|
filters:
|
||||||
|
- delayed_off: 5000ms
|
6
tests/components/msa3xx/test.esp32-ard.yaml
Normal file
6
tests/components/msa3xx/test.esp32-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO16
|
||||||
|
sda: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/msa3xx/test.esp32-c3-ard.yaml
Normal file
6
tests/components/msa3xx/test.esp32-c3-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO5
|
||||||
|
sda: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/msa3xx/test.esp32-c3-idf.yaml
Normal file
6
tests/components/msa3xx/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO5
|
||||||
|
sda: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/msa3xx/test.esp32-idf.yaml
Normal file
6
tests/components/msa3xx/test.esp32-idf.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO16
|
||||||
|
sda: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/msa3xx/test.esp8266-ard.yaml
Normal file
6
tests/components/msa3xx/test.esp8266-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO5
|
||||||
|
sda: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
6
tests/components/msa3xx/test.rp2040-ard.yaml
Normal file
6
tests/components/msa3xx/test.rp2040-ard.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_msa3xx
|
||||||
|
scl: GPIO5
|
||||||
|
sda: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
Loading…
x
Reference in New Issue
Block a user