ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
motion_component.h
Go to the documentation of this file.
1#pragma once
2
7#include <array>
8#include <cmath>
9#include <numbers> // required for generated lambda code
10
11namespace esphome::motion {
12
13// ---Data class
14
15struct MotionData {
16 float acceleration[3]{NAN, NAN, NAN};
17 float angular_rate[3]{NAN, NAN, NAN};
18 // TODO - compass
19};
20
21// indices into data arrays
22static constexpr uint8_t X_AXIS = 0;
23static constexpr uint8_t Y_AXIS = 1;
24static constexpr uint8_t Z_AXIS = 2;
25
33inline float orientation_degrees(const MotionData &data, float flat_threshold) {
34 float ax = data.acceleration[X_AXIS];
35 float ay = data.acceleration[Y_AXIS];
36 float az = data.acceleration[Z_AXIS];
37 if (std::isnan(ax) || std::isnan(ay) || std::isnan(az))
38 return NAN;
39 float mag = std::sqrt(ax * ax + ay * ay + az * az);
40 if (mag < 0.1f)
41 return NAN;
42 // Horizontal component of gravity; near zero when the device lies flat.
43 float h = std::sqrt(ax * ax + ay * ay);
44 if (h / mag < flat_threshold)
45 return NAN;
46 // Direction of the horizontal component, snapped to the nearest 90°.
47 float angle = std::atan2(ay, ax) * (180.0f / std::numbers::pi_v<float>);
48 int quadrant = static_cast<int>(std::lround(angle / 90.0f));
49 quadrant = ((quadrant % 4) + 4) % 4; // normalise to 0..3
50 return quadrant * 90.0f;
51}
52
53// Persisted calibration. `base_hash` ties the stored matrix to the build-time
54// (axis_map / transform_matrix) base; if the base changes the saved calibration
55// is ignored. Stored under a stable, ID-derived key so it overwrites in place.
60
61// Main component class
63 public:
64 // Lifecycle
65 void setup() override;
66 void update() override;
67 void dump_config() override;
68
69 void set_matrix(const std::array<float, 9> &m) {
70 memcpy(this->base_matrix_, m.data(), sizeof(this->base_matrix_));
71 memcpy(this->matrix_, m.data(), sizeof(this->matrix_));
72 }
74
76 bool calibrate_level();
78 bool calibrate_heading();
80 bool save_calibration();
83 void clear_calibration(bool save = false);
84
85 template<typename F> void add_listener(F &&cb) { this->motion_data_callback_.add(std::forward<F>(cb)); }
86
87 protected:
88 // platforms must implement this method to update raw data.
89 virtual bool update_data(MotionData &data) = 0;
90
91 // for mapping axes
92 float matrix_[9]{
93 1, 0, 0, 0, 1, 0, 0, 0, 1,
94 };
95 // build-time base (axis_map / transform_matrix); used to detect config changes
96 // and to restore on clear_calibration().
97 float base_matrix_[9]{
98 1, 0, 0, 0, 1, 0, 0, 0, 1,
99 };
100
101 void map_axes_(float output[3], const float input[3]) const {
102 output[0] = input[X_AXIS] * this->matrix_[0] + input[Y_AXIS] * this->matrix_[1] + input[Z_AXIS] * this->matrix_[2];
103 output[1] = input[X_AXIS] * this->matrix_[3] + input[Y_AXIS] * this->matrix_[4] + input[Z_AXIS] * this->matrix_[5];
104 output[2] = input[X_AXIS] * this->matrix_[6] + input[Y_AXIS] * this->matrix_[7] + input[Z_AXIS] * this->matrix_[8];
105 }
106
109 uint32_t base_hash_{0}; // hash of base_matrix_, captured in setup()
111};
112
113// --- Actions ---
114
115template<typename... Ts> class CalibrateLevelAction final : public Action<Ts...> {
116 public:
117 explicit CalibrateLevelAction(MotionComponent *parent) : parent_(parent) {}
118 void set_save(bool save) { this->save_ = save; }
121
122 protected:
123 void play(const Ts &...) override {
124 if (this->parent_->calibrate_level()) {
125 // if not saving, calibration success is enough. If save required only report success after that succeeds too.
126 if (!this->save_ || this->parent_->save_calibration()) {
128 return;
129 }
130 }
131 this->error_trigger_.trigger();
132 }
133
137 bool save_{false};
138};
139
140template<typename... Ts> class CalibrateHeadingAction final : public Action<Ts...> {
141 public:
142 explicit CalibrateHeadingAction(MotionComponent *parent) : parent_(parent) {}
143 void set_save(bool save) { this->save_ = save; }
146
147 protected:
148 void play(const Ts &...) override {
149 if (this->parent_->calibrate_heading()) {
150 // if not saving, calibration success is enough. If save required only report success after that succeeds too.
151 if (!this->save_ || this->parent_->save_calibration()) {
153 return;
154 }
155 }
156 this->error_trigger_.trigger();
157 }
158
162 bool save_{false};
163};
164
165} // namespace esphome::motion
uint8_t m
Definition bl0906.h:1
uint8_t h
Definition bl0906.h:2
This class simplifies creating components that periodically check a state.
Definition component.h:519
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
CalibrateHeadingAction(MotionComponent *parent)
CalibrateLevelAction(MotionComponent *parent)
void play(const Ts &...) override
void map_axes_(float output[3], const float input[3]) const
bool calibrate_heading()
Assuming Y-axis rotation only, correct the heading so X/Y align correctly.
LazyCallbackManager< void(MotionData const &)> motion_data_callback_
void set_matrix(const std::array< float, 9 > &m)
virtual bool update_data(MotionData &data)=0
bool calibrate_level()
Calibrate the matrix so the current reading maps to [0, 0, 1] (device flat).
void clear_calibration(bool save=false)
Restore the build-time (axis_map / transform_matrix) base, discarding calibration,...
bool save_calibration()
Save the current matrix to NVS.
const char * key
float orientation_degrees(const MotionData &data, float flat_threshold)
Compute the device's in-plane orientation from the gravity vector.
struct esphome::motion::CalibrationPref PACKED
static void uint32_t