ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
msa3xx.cpp
Go to the documentation of this file.
1#include "msa3xx.h"
2#include "esphome/core/hal.h"
4#include "esphome/core/log.h"
5
6namespace esphome::msa3xx {
7
8ESPHOME_LOG_TAG(TAG, "msa3xx");
9
10const uint8_t MSA_3XX_PART_ID = 0x13;
11
12const float GRAVITY_EARTH = 9.80665f;
13const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9f); // LSB to 1 LSB = 3.9mg = 0.0039g
14const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
15const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
16
17const uint8_t RESOLUTION[] = {14, 12, 10, 8};
18
22
23const char *model_to_string(Model model) {
24 switch (model) {
25 case Model::MSA301:
26 return "MSA301";
27 case Model::MSA311:
28 return "MSA311";
29 default:
30 return "Unknown";
31 }
32}
33
35 switch (power_mode) {
37 return "Normal";
39 return "Low Power";
41 return "Suspend";
42 default:
43 return "Unknown";
44 }
45}
46
48 switch (resolution) {
50 return "14-bit";
52 return "12-bit";
54 return "10-bit";
56 return "8-bit";
57 default:
58 return "Unknown";
59 }
60}
61
63 switch (range) {
64 case Range::RANGE_2G:
65 return "±2g";
66 case Range::RANGE_4G:
67 return "±4g";
68 case Range::RANGE_8G:
69 return "±8g";
71 return "±16g";
72 default:
73 return "Unknown";
74 }
75}
76
77const char *bandwidth_to_string(Bandwidth bandwidth) {
78 switch (bandwidth) {
80 return "1.95 Hz";
82 return "3.9 Hz";
84 return "7.81 Hz";
86 return "15.63 Hz";
88 return "31.25 Hz";
90 return "62.5 Hz";
92 return "125 Hz";
94 return "250 Hz";
96 return "500 Hz";
97 default:
98 return "Unknown";
99 }
100}
101
103 switch (orientation) {
105 return "Portrait Upright";
107 return "Portrait Upside Down";
109 return "Landscape Left";
111 return "Landscape Right";
112 default:
113 return "Unknown";
114 }
115}
116
117const char *orientation_z_to_string(bool orientation) { return orientation ? "Downwards looking" : "Upwards looking"; }
118
120 if (!this->check_chip_id(this, static_cast<uint8_t>(RegisterMap::PART_ID), MSA_3XX_PART_ID))
121 return;
122
123 // Resolution LSB/g
124 // Range : MSA301 : MSA311
125 // S2g : 1024 (2^10) : 4096 (2^12)
126 // S4g : 512 (2^9) : 2048 (2^11)
127 // S8g : 256 (2^8) : 1024 (2^10)
128 // S16g : 128 (2^7) : 512 (2^9)
129 if (this->model_ == Model::MSA301) {
130 this->device_params_.accel_data_width = 14;
131 this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 12;
132 } else if (this->model_ == Model::MSA311) {
133 this->device_params_.accel_data_width = 12;
134 this->device_params_.scale_factor_exp = static_cast<uint8_t>(this->range_) - 10;
135 } else {
136 this->mark_failed(LOG_STR("Unknown model"));
137 return;
138 }
139
140 this->setup_odr_(this->data_rate_);
142 this->setup_range_resolution_(this->range_, this->resolution_); // 2g...16g, 14...8 bit
143 this->setup_offset_(this->offset_x_, this->offset_y_, this->offset_z_); // calibration offsets
144 this->write_byte(static_cast<uint8_t>(RegisterMap::TAP_DURATION), 0b11000100); // set tap duration 250ms
145 this->write_byte(static_cast<uint8_t>(RegisterMap::SWAP_POLARITY), this->swap_.raw); // set axes polarity
146 this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_0), 0b01110111); // enable all interrupts
147 this->write_byte(static_cast<uint8_t>(RegisterMap::INT_SET_1), 0b00011000); // including orientation
148}
149
151 ESP_LOGCONFIG(TAG, "MSA3xx:");
152 LOG_I2C_DEVICE(this);
153 ESP_LOGCONFIG(TAG,
154 " Model: %s\n"
155 " Power Mode: %s\n"
156 " Bandwidth: %s\n"
157 " Range: %s\n"
158 " Resolution: %s\n"
159 " Offsets: {%.3f m/s², %.3f m/s², %.3f m/s²}\n"
160 " Transform: {mirror_x=%s, mirror_y=%s, mirror_z=%s, swap_xy=%s}",
163 this->offset_x_, this->offset_y_, this->offset_z_, YESNO(this->swap_.x_polarity),
164 YESNO(this->swap_.y_polarity), YESNO(this->swap_.z_polarity), YESNO(this->swap_.x_y_swap));
165 LOG_UPDATE_INTERVAL(this);
166
167#ifdef USE_BINARY_SENSOR
168 LOG_BINARY_SENSOR(" ", "Tap", this->tap_binary_sensor_);
169 LOG_BINARY_SENSOR(" ", "Double Tap", this->double_tap_binary_sensor_);
170 LOG_BINARY_SENSOR(" ", "Active", this->active_binary_sensor_);
171#endif
172
173#ifdef USE_SENSOR
174 LOG_SENSOR(" ", "Acceleration X", this->acceleration_x_sensor_);
175 LOG_SENSOR(" ", "Acceleration Y", this->acceleration_y_sensor_);
176 LOG_SENSOR(" ", "Acceleration Z", this->acceleration_z_sensor_);
177#endif
178
179#ifdef USE_TEXT_SENSOR
180 LOG_TEXT_SENSOR(" ", "Orientation XY", this->orientation_xy_text_sensor_);
181 LOG_TEXT_SENSOR(" ", "Orientation Z", this->orientation_z_text_sensor_);
182#endif
183}
184
186 uint8_t accel_data[6];
187 if (!this->read_bytes(static_cast<uint8_t>(RegisterMap::ACC_X_LSB), accel_data, 6)) {
188 return false;
189 }
190
191 auto raw_to_x_bit = [](uint16_t lsb, uint16_t msb, uint8_t data_bits) -> uint16_t {
192 return ((msb << 8) | lsb) >> (16 - data_bits);
193 };
194
195 auto lpf = [](float new_value, float old_value, float alpha = 0.5f) {
196 return alpha * new_value + (1.0f - alpha) * old_value;
197 };
198
199 this->data_.lsb_x =
200 this->twos_complement_(raw_to_x_bit(accel_data[0], accel_data[1], this->device_params_.accel_data_width),
201 this->device_params_.accel_data_width);
202 this->data_.lsb_y =
203 this->twos_complement_(raw_to_x_bit(accel_data[2], accel_data[3], this->device_params_.accel_data_width),
204 this->device_params_.accel_data_width);
205 this->data_.lsb_z =
206 this->twos_complement_(raw_to_x_bit(accel_data[4], accel_data[5], this->device_params_.accel_data_width),
207 this->device_params_.accel_data_width);
208
209 this->data_.x = lpf(ldexp(this->data_.lsb_x, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.x);
210 this->data_.y = lpf(ldexp(this->data_.lsb_y, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.y);
211 this->data_.z = lpf(ldexp(this->data_.lsb_z, this->device_params_.scale_factor_exp) * GRAVITY_EARTH, this->data_.z);
212
213 return true;
214}
215
217 if (!this->read_byte(static_cast<uint8_t>(RegisterMap::MOTION_INTERRUPT), &this->status_.motion_int.raw)) {
218 return false;
219 }
220
221 if (!this->read_byte(static_cast<uint8_t>(RegisterMap::ORIENTATION_STATUS), &this->status_.orientation.raw)) {
222 return false;
223 }
224
225 return true;
226}
227
229 if (!this->is_ready()) {
230 return;
231 }
232
233 RegMotionInterrupt old_motion_int = this->status_.motion_int;
234
235 if (!this->read_data_() || !this->read_motion_status_()) {
236 this->status_set_warning();
237 return;
238 }
239
240 this->process_motions_(old_motion_int);
241}
242
244 ESP_LOGV(TAG, "Updating");
245
246 if (!this->is_ready()) {
247 ESP_LOGV(TAG, "Not ready for update");
248 return;
249 }
250 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,
251 this->data_.z);
252
253 ESP_LOGV(TAG, "Orientation: {XY = %s, Z = %s}", orientation_xy_to_string(this->status_.orientation.orient_xy),
254 orientation_z_to_string(this->status_.orientation.orient_z));
255
256#ifdef USE_SENSOR
257 if (this->acceleration_x_sensor_ != nullptr)
258 this->acceleration_x_sensor_->publish_state(this->data_.x);
259 if (this->acceleration_y_sensor_ != nullptr)
260 this->acceleration_y_sensor_->publish_state(this->data_.y);
261 if (this->acceleration_z_sensor_ != nullptr)
262 this->acceleration_z_sensor_->publish_state(this->data_.z);
263#endif
264
265#ifdef USE_TEXT_SENSOR
266 if (this->orientation_xy_text_sensor_ != nullptr &&
267 (this->status_.orientation.orient_xy != this->status_.orientation_old.orient_xy ||
268 this->status_.never_published)) {
269 this->orientation_xy_text_sensor_->publish_state(orientation_xy_to_string(this->status_.orientation.orient_xy));
270 }
271 if (this->orientation_z_text_sensor_ != nullptr &&
272 (this->status_.orientation.orient_z != this->status_.orientation_old.orient_z || this->status_.never_published)) {
273 this->orientation_z_text_sensor_->publish_state(orientation_z_to_string(this->status_.orientation.orient_z));
274 }
275 this->status_.orientation_old = this->status_.orientation;
276#endif
277
278 this->status_.never_published = false;
279 this->status_clear_warning();
280}
281
282void MSA3xxComponent::set_offset(float offset_x, float offset_y, float offset_z) {
283 this->offset_x_ = offset_x;
284 this->offset_y_ = offset_y;
285 this->offset_z_ = offset_z;
286}
287
288void MSA3xxComponent::set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy) {
289 this->swap_.x_polarity = mirror_x;
290 this->swap_.y_polarity = mirror_y;
291 this->swap_.z_polarity = mirror_z;
292 this->swap_.x_y_swap = swap_xy;
293}
294
296 RegOutputDataRate reg_odr;
297 auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::ODR));
298 if (reg.has_value()) {
299 reg_odr.raw = reg.value();
300 } else {
301 reg_odr.raw = 0x0F; // defaut from datasheet
302 }
303
304 reg_odr.x_axis_disable = false;
305 reg_odr.y_axis_disable = false;
306 reg_odr.z_axis_disable = false;
307 reg_odr.odr = rate;
308
309 this->write_byte(static_cast<uint8_t>(RegisterMap::ODR), reg_odr.raw);
310}
311
313 // 0x11 POWER_MODE_BANDWIDTH
314 auto reg = this->read_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH));
315
316 RegPowerModeBandwidth power_mode_bandwidth;
317 if (reg.has_value()) {
318 power_mode_bandwidth.raw = reg.value();
319 } else {
320 power_mode_bandwidth.raw = 0xde; // defaut from datasheet
321 }
322
323 power_mode_bandwidth.power_mode = power_mode;
324 power_mode_bandwidth.low_power_bandwidth = bandwidth;
325
326 this->write_byte(static_cast<uint8_t>(RegisterMap::POWER_MODE_BANDWIDTH), power_mode_bandwidth.raw);
327}
328
331 reg.raw = this->read_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION)).value_or(0x00);
332 reg.range = range;
333 if (this->model_ == Model::MSA301) {
335 }
336 this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw);
337}
338
339void MSA3xxComponent::setup_offset_(float offset_x, float offset_y, float offset_z) {
340 uint8_t offset[3];
341
342 auto offset_g_to_lsb = [](float accel) -> int8_t {
343 float acccel_clamped = clamp(accel, G_OFFSET_MIN, G_OFFSET_MAX);
344 return static_cast<int8_t>(acccel_clamped * LSB_COEFF);
345 };
346
347 offset[0] = offset_g_to_lsb(offset_x);
348 offset[1] = offset_g_to_lsb(offset_y);
349 offset[2] = offset_g_to_lsb(offset_z);
350
351 ESP_LOGV(TAG, "Offset (%.3f, %.3f, %.3f)=>LSB(%d, %d, %d)", offset_x, offset_y, offset_z, offset[0], offset[1],
352 offset[2]);
353
354 this->write_bytes(static_cast<uint8_t>(RegisterMap::OFFSET_COMP_X), (uint8_t *) &offset, 3);
355}
356
357int64_t MSA3xxComponent::twos_complement_(uint64_t value, uint8_t bits) {
358 return (int64_t) (value << (64 - bits)) >> (64 - bits);
359}
360
361void binary_event_debounce(bool state, bool old_state, uint32_t now, uint32_t &last_ms, Trigger<> &trigger,
362 uint32_t cooldown_ms, void *bs, const char *desc) {
363 if (state && now - last_ms > cooldown_ms) {
364 ESP_LOGV(TAG, "%s detected", desc);
365 trigger.trigger();
366 last_ms = now;
367#ifdef USE_BINARY_SENSOR
368 if (bs != nullptr) {
369 static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(true);
370 }
371#endif
372 } else if (!state && now - last_ms > cooldown_ms && bs != nullptr) {
373#ifdef USE_BINARY_SENSOR
374 static_cast<binary_sensor::BinarySensor *>(bs)->publish_state(false);
375#endif
376 }
377}
378
379#ifdef USE_BINARY_SENSOR
380#define BS_OPTIONAL_PTR(x) ((void *) (x))
381#else
382#define BS_OPTIONAL_PTR(x) (nullptr)
383#endif
384
386 uint32_t now = millis();
387
388 binary_event_debounce(this->status_.motion_int.single_tap_interrupt, old.single_tap_interrupt, now,
389 this->status_.last_tap_ms, this->tap_trigger_, TAP_COOLDOWN_MS,
390 BS_OPTIONAL_PTR(this->tap_binary_sensor_), "Tap");
391 binary_event_debounce(this->status_.motion_int.double_tap_interrupt, old.double_tap_interrupt, now,
392 this->status_.last_double_tap_ms, this->double_tap_trigger_, DOUBLE_TAP_COOLDOWN_MS,
393 BS_OPTIONAL_PTR(this->double_tap_binary_sensor_), "Double Tap");
394 binary_event_debounce(this->status_.motion_int.active_interrupt, old.active_interrupt, now,
395 this->status_.last_action_ms, this->active_trigger_, ACTIVITY_COOLDOWN_MS,
396 BS_OPTIONAL_PTR(this->active_binary_sensor_), "Activity");
397
398 if (this->status_.motion_int.orientation_interrupt) {
399 ESP_LOGVV(TAG, "Orientation changed");
401 }
402}
403
404} // namespace esphome::msa3xx
void mark_failed()
Mark this component as failed.
bool is_ready() const
void status_clear_warning()
Definition component.h:296
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
Base class for all binary_sensor-type classes.
bool check_chip_id(Component *component, uint8_t a_register, uint8_t expected, uint8_t mask=0xFF)
Read the 8-bit ID at a_register and accept it when it matches expected in the bits set in mask.
Definition i2c.h:232
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:312
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:272
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:164
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
Definition i2c.h:298
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len)
Compat APIs All methods below have been added for compatibility reasons.
Definition i2c.h:249
void process_motions_(RegMotionInterrupt old)
Definition msa3xx.cpp:385
int64_t twos_complement_(uint64_t value, uint8_t bits)
Definition msa3xx.cpp:357
struct esphome::msa3xx::MSA3xxComponent::@146 device_params_
void setup_odr_(DataRate rate)
Definition msa3xx.cpp:295
void setup_range_resolution_(Range range, Resolution resolution)
Definition msa3xx.cpp:329
void setup_offset_(float offset_x, float offset_y, float offset_z)
Definition msa3xx.cpp:339
void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth)
Definition msa3xx.cpp:312
struct esphome::msa3xx::MSA3xxComponent::@148 status_
struct esphome::msa3xx::MSA3xxComponent::@147 data_
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy)
Definition msa3xx.cpp:288
void set_offset(float offset_x, float offset_y, float offset_z)
Definition msa3xx.cpp:282
bool state
Definition fan.h:2
Resolution resolution
Definition msa3xx.h:1
PowerMode power_mode
Definition msa3xx.h:3
Range range
Definition msa3xx.h:0
const char * orientation_xy_to_string(OrientationXY orientation)
Definition msa3xx.cpp:102
const char * range_to_string(Range range)
Definition msa3xx.cpp:62
const uint32_t DOUBLE_TAP_COOLDOWN_MS
Definition msa3xx.cpp:20
const float G_OFFSET_MAX
Definition msa3xx.cpp:15
const float G_OFFSET_MIN
Definition msa3xx.cpp:14
const char * orientation_z_to_string(bool orientation)
Definition msa3xx.cpp:117
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)
Definition msa3xx.cpp:361
const char * bandwidth_to_string(Bandwidth bandwidth)
Definition msa3xx.cpp:77
const float GRAVITY_EARTH
Definition msa3xx.cpp:12
const uint32_t ACTIVITY_COOLDOWN_MS
Definition msa3xx.cpp:21
const char * model_to_string(Model model)
Definition msa3xx.cpp:23
const uint32_t TAP_COOLDOWN_MS
Definition msa3xx.cpp:19
ESPHOME_LOG_TAG(TAG, "msa3xx")
const uint8_t RESOLUTION[]
Definition msa3xx.cpp:17
const char * res_to_string(Resolution resolution)
Definition msa3xx.cpp:47
const float LSB_COEFF
Definition msa3xx.cpp:13
const char * power_mode_to_string(PowerMode power_mode)
Definition msa3xx.cpp:34
const uint8_t MSA_3XX_PART_ID
Definition msa3xx.cpp:10
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t
uint8_t orientation
Definition tt21100.cpp:9