ESPHome 2025.5.0
Loading...
Searching...
No Matches
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"
5
6namespace esphome {
7namespace msa3xx {
8
9static const char *const TAG = "msa3xx";
10
11const uint8_t MSA_3XX_PART_ID = 0x13;
12
13const float GRAVITY_EARTH = 9.80665f;
14const float LSB_COEFF = 1000.0f / (GRAVITY_EARTH * 3.9); // LSB to 1 LSB = 3.9mg = 0.0039g
15const float G_OFFSET_MIN = -4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
16const float G_OFFSET_MAX = 4.5f; // -127...127 LSB = +- 0.4953g = +- 4.857 m/s^2 => +- 4.5 for the safe
17
18const uint8_t RESOLUTION[] = {14, 12, 10, 8};
19
20const uint32_t TAP_COOLDOWN_MS = 500;
21const uint32_t DOUBLE_TAP_COOLDOWN_MS = 500;
22const uint32_t ACTIVITY_COOLDOWN_MS = 500;
23
24const 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) {
38 return "Normal";
40 return "Low Power";
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
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";
72 return "±16g";
73 default:
74 return "Unknown";
75 }
76}
77
78const 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
118const 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
290void 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
296void 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) {
343 }
344 this->write_byte(static_cast<uint8_t>(RegisterMap::RANGE_RESOLUTION), reg.raw);
345}
346
347void 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
365int64_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
373void 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
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
bool is_ready() const
void status_set_warning(const char *message="unspecified")
void status_clear_warning()
void trigger(Ts... x)
Inform the parent automation that the event has triggered.
Definition automation.h:96
Base class for all binary_sensor-type classes.
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop=true)
Definition i2c.h:252
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition i2c.h:266
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:153
bool read_byte(uint8_t a_register, uint8_t *data, bool stop=true)
Definition i2c.h:239
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:216
void process_motions_(RegMotionInterrupt old)
Definition msa3xx.cpp:397
int64_t twos_complement_(uint64_t value, uint8_t bits)
Definition msa3xx.cpp:365
void setup_odr_(DataRate rate)
Definition msa3xx.cpp:303
void setup_range_resolution_(Range range, Resolution resolution)
Definition msa3xx.cpp:337
void setup_offset_(float offset_x, float offset_y, float offset_z)
Definition msa3xx.cpp:347
struct esphome::msa3xx::MSA3xxComponent::@135 data_
struct esphome::msa3xx::MSA3xxComponent::@136 status_
void setup_power_mode_bandwidth_(PowerMode power_mode, Bandwidth bandwidth)
Definition msa3xx.cpp:320
float get_setup_priority() const override
Definition msa3xx.cpp:288
struct esphome::msa3xx::MSA3xxComponent::@134 device_params_
void set_transform(bool mirror_x, bool mirror_y, bool mirror_z, bool swap_xy)
Definition msa3xx.cpp:296
void set_offset(float offset_x, float offset_y, float offset_z)
Definition msa3xx.cpp:290
bool state
Definition fan.h:0
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:103
const char * range_to_string(Range range)
Definition msa3xx.cpp:63
const uint32_t DOUBLE_TAP_COOLDOWN_MS
Definition msa3xx.cpp:21
const float G_OFFSET_MAX
Definition msa3xx.cpp:16
const float G_OFFSET_MIN
Definition msa3xx.cpp:15
const char * orientation_z_to_string(bool orientation)
Definition msa3xx.cpp:118
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
const char * bandwidth_to_string(Bandwidth bandwidth)
Definition msa3xx.cpp:78
const float GRAVITY_EARTH
Definition msa3xx.cpp:13
const uint32_t ACTIVITY_COOLDOWN_MS
Definition msa3xx.cpp:22
const char * model_to_string(Model model)
Definition msa3xx.cpp:24
const uint32_t TAP_COOLDOWN_MS
Definition msa3xx.cpp:20
const uint8_t RESOLUTION[]
Definition msa3xx.cpp:18
const char * res_to_string(Resolution resolution)
Definition msa3xx.cpp:48
const float LSB_COEFF
Definition msa3xx.cpp:14
const char * power_mode_to_string(PowerMode power_mode)
Definition msa3xx.cpp:35
const uint8_t MSA_3XX_PART_ID
Definition msa3xx.cpp:11
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
constexpr const T & clamp(const T &v, const T &lo, const T &hi, Compare comp)
Definition helpers.h:101
uint8_t orientation
Definition tt21100.cpp:9