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