tests and calib/transform options

This commit is contained in:
Anton Viktorov 2024-05-23 09:18:19 +02:00
parent 78aef0a348
commit d7e0ca6eed
11 changed files with 244 additions and 71 deletions

View file

@ -3,11 +3,16 @@ import esphome.config_validation as cv
from esphome.components import i2c
from esphome import automation
from esphome.const import (
CONF_ID,
CONF_ADDRESS,
CONF_CALIBRATION,
CONF_ID,
CONF_MIRROR_X,
CONF_MIRROR_Y,
CONF_MODEL,
CONF_RANGE,
CONF_RESOLUTION,
CONF_SWAP_XY,
CONF_TRANSFORM,
)
DEPENDENCIES = ["i2c"]
@ -15,13 +20,15 @@ AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor"]
CONF_MSA3XX_ID = "msa3xx_id"
CONF_MIRROR_Z = "mirror_z"
CONF_OFFSET_X = "offset_x"
CONF_OFFSET_Y = "offset_y"
CONF_OFFSET_Z = "offset_z"
CONF_ON_TAP = "on_tap"
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"
msa3xx_ns = cg.esphome_ns.namespace("msa3xx")
@ -72,9 +79,28 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE, default="2G"): cv.enum(MSA_RANGES, upper=True),
# cv.Optional(CONF_BANDWIDTH, default="250HZ"): cv.enum(MSA_BANDWIDTHS, upper=True),
cv.Optional(CONF_RESOLUTION): cv.enum(MSA_RESOLUTIONS),
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_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
@ -136,16 +162,31 @@ async def to_code(config):
await i2c.register_i2c_device(var, config)
cg.add(var.set_model(config[CONF_MODEL]))
cg.add(
var.set_offset(
config[CONF_OFFSET_X], config[CONF_OFFSET_Y], config[CONF_OFFSET_Z]
)
)
cg.add(var.set_range(MSA_RANGES[config[CONF_RANGE]]))
cg.add(var.set_resolution(MSA_RESOLUTIONS[config[CONF_RESOLUTION]]))
irq_set_0 = 0
irq_set_1 = 0
if CONF_TRANSFORM in config:
transform = config[CONF_TRANSFORM]
cg.add(
var.set_transform(
transform[CONF_MIRROR_X],
transform[CONF_MIRROR_Y],
transform[CONF_MIRROR_Z],
transform[CONF_SWAP_XY],
)
)
if CONF_CALIBRATION in config:
calibration_config = config[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(
@ -153,7 +194,6 @@ async def to_code(config):
[],
config[CONF_ON_ORIENTATION],
)
irq_set_0 |= 1 << 6
if CONF_ON_TAP in config:
await automation.build_automation(
@ -161,7 +201,6 @@ async def to_code(config):
[],
config[CONF_ON_TAP],
)
irq_set_0 |= 1 << 5
if CONF_ON_DOUBLE_TAP in config:
await automation.build_automation(
@ -169,7 +208,13 @@ async def to_code(config):
[],
config[CONF_ON_DOUBLE_TAP],
)
irq_set_0 |= 1 << 4
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(
@ -177,6 +222,3 @@ async def to_code(config):
[],
config[CONF_ON_FREEFALL],
)
irq_set_1 |= 1 << 3
cg.add(var.set_interrupts(irq_set_0, irq_set_1))

View file

@ -7,8 +7,12 @@ namespace msa3xx {
static const char *const TAG = "msa3xx";
const uint8_t MSA3xx_PART_ID = 0x13;
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};
@ -36,8 +40,8 @@ const char *power_mode_to_string(PowerMode power_mode) {
}
}
const char *res_to_string(Resolution Resolution) {
switch (Resolution) {
const char *res_to_string(Resolution resolution) {
switch (resolution) {
case Resolution::RES_14BIT:
return "14-bit";
case Resolution::RES_12BIT:
@ -91,11 +95,28 @@ const char *bandwidth_to_string(Bandwidth bandwidth) {
}
}
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 != MSA3xx_PART_ID)) {
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;
@ -113,7 +134,6 @@ void MSA3xxComponent::setup() {
} 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();
@ -122,11 +142,12 @@ void MSA3xxComponent::setup() {
this->setup_odr_(this->data_rate_);
this->setup_power_mode_bandwidth_(this->power_mode_, this->bandwidth_);
this->setup_range_resolution_(this->range_, this->resolution_);
this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_);
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), this->int_set_0_);
this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), this->int_set_1_);
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() {
@ -140,13 +161,15 @@ void MSA3xxComponent::dump_config() {
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, " 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_SENSOR
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(" ", "Acceleration X", this->acceleration_x_sensor_);
LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
#endif
}
@ -165,22 +188,19 @@ bool MSA3xxComponent::read_data_() {
};
this->data_.lsb_x =
this->two_complement_to_normal_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width),
this->device_params_.accel_data_width);
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->two_complement_to_normal_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width),
this->device_params_.accel_data_width);
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->two_complement_to_normal_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width),
this->device_params_.accel_data_width);
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);
// ESP_LOGVV(TAG, "Got raw data {x=%5d, y=%5d, z=%5d}, accel={x=%+1.3f m/s², y=%+1.3f m/s², z=%+1.3f m/s²}",
// this->data_.lsb_x, this->data_.lsb_y, this->data_.lsb_z, this->data_.x, this->data_.y, this->data_.z);
return true;
}
@ -206,6 +226,11 @@ void MSA3xxComponent::loop() {
return;
}
// ESP_LOGVV(TAG, "Got raw data {x=%5d, y=%5d, z=%5d}, accel={x=%+1.3f m/s², y=%+1.3f m/s², z=%+1.3f m/s²}",
// this->data_.lsb_x, this->data_.lsb_y, this->data_.lsb_z, 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));
this->process_interrupts_();
}
@ -216,16 +241,19 @@ void MSA3xxComponent::update() {
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,
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->accel_x_sensor_ != nullptr)
this->accel_x_sensor_->publish_state(this->data_.x);
if (this->accel_y_sensor_ != nullptr)
this->accel_y_sensor_->publish_state(this->data_.y);
if (this->accel_z_sensor_ != nullptr)
this->accel_z_sensor_->publish_state(this->data_.z);
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
this->status_clear_warning();
@ -238,6 +266,13 @@ void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z)
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));
@ -247,9 +282,9 @@ void MSA3xxComponent::setup_odr_(DataRate rate) {
reg_odr.raw = 0x0F; // defaut from datasheet
}
reg_odr.x_axis_disable = 0;
reg_odr.y_axis_disable = 0;
reg_odr.z_axis_disable = 0;
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);
@ -284,10 +319,8 @@ void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset
uint8_t offset[3];
auto offset_g_to_lsb = [](float accel) -> int8_t {
// 1 LSB = 3.9mg = 0.0039g
// -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
float acccel_clamped = clamp(accel, -4.5f, 4.5f);
return static_cast<int8_t>(accel * 1000 / (GRAVITY_EARTH * 3.9));
float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX);
return static_cast<int8_t>(accel * LSB_COEFF);
};
offset[0] = offset_g_to_lsb(offset_x);
offset[1] = offset_g_to_lsb(offset_y);
@ -299,7 +332,7 @@ void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset
this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3);
}
int64_t MSA3xxComponent::two_complement_to_normal_(uint64_t value, uint8_t bits) {
int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) {
if (value > (1ULL << (bits - 1))) {
return (int64_t) (value - (1ULL << bits));
} else {
@ -326,6 +359,11 @@ void MSA3xxComponent::process_interrupts_() {
ESP_LOGW(TAG, "Orientation changed");
this->orientation_trigger_.trigger();
}
if (this->status_.motion_int.active_interrupt) {
ESP_LOGW(TAG, "Activity detected");
this->active_trigger_.trigger();
}
}
} // namespace msa3xx

View file

@ -105,6 +105,22 @@ enum class DataRate : uint8_t {
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;
};
uint8_t raw;
};
// 0x09
union RegMotionInterrupt {
struct {
@ -124,7 +140,8 @@ union RegMotionInterrupt {
union RegOrientationStatus {
struct {
uint8_t reserved_0_3 : 4;
uint8_t orient : 3;
OrientationXY orient_xy : 2;
bool orient_z : 1;
uint8_t reserved_7 : 1;
};
uint8_t raw{0x00};
@ -163,6 +180,18 @@ union RegPowerModeBandwidth {
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;
};
uint8_t raw{0};
};
// 0x2a
union RegTapDuration {
struct {
@ -189,21 +218,19 @@ class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
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_interrupts(uint8_t set0, uint8_t set1) {
this->int_set_0_ = set0;
this->int_set_1_ = set1;
};
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy);
#ifdef USE_SENSOR
SUB_SENSOR(accel_x)
SUB_SENSOR(accel_y)
SUB_SENSOR(accel_z)
SUB_SENSOR(acceleration_x)
SUB_SENSOR(acceleration_y)
SUB_SENSOR(acceleration_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};
@ -213,11 +240,8 @@ class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
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²
uint8_t int_set_0_{0x00};
uint8_t int_set_1_{0x00};
RegSwapPolarity swap_;
struct {
int scale_factor_exp;
@ -242,7 +266,7 @@ class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
bool read_data_();
bool read_motion_status_();
int64_t two_complement_to_normal_(uint64_t value, uint8_t bits);
int64_t twos_complement_(uint64_t value, uint8_t bits);
//
// Actons / Triggers
@ -251,10 +275,10 @@ class MSA3xxComponent : public PollingComponent, public i2c::I2CDevice {
Trigger<> double_tap_trigger_;
Trigger<> orientation_trigger_;
Trigger<> freefall_trigger_;
Trigger<> active_trigger_;
void process_interrupts_();
};
;
} // namespace msa3xx
} // namespace esphome

View file

@ -45,4 +45,4 @@ async def to_code(config):
accel_key = f"acceleration_{d}"
if accel_key in config:
sens = await sensor.new_sensor(config[accel_key])
cg.add(getattr(hub, f"set_accel_{d}_sensor")(sens))
cg.add(getattr(hub, f"set_acceleration_{d}_sensor")(sens))

View file

@ -0,0 +1,33 @@
msa3xx:
i2c_id: i2c_msa3xx
model: 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

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO5
sda: GPIO4
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO5
sda: GPIO4
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO16
sda: GPIO17
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO16
sda: GPIO17
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO5
sda: GPIO4
<<: !include common.yaml

View file

@ -0,0 +1,6 @@
i2c:
- id: i2c_msa3xx
scl: GPIO5
sda: GPIO4
<<: !include common.yaml