ESPHome 2026.10.1
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 float get_setup_priority() const override { return setup_priority::DATA; }
69
70 void set_matrix(const std::array<float, 9> &m) {
71 memcpy(this->base_matrix_, m.data(), sizeof(this->base_matrix_));
72 memcpy(this->matrix_, m.data(), sizeof(this->matrix_));
73 }
74 void set_calibration_key(uint32_t key) { this->pref_key_ = key; }
75
77 bool calibrate_level();
79 bool calibrate_heading();
81 bool save_calibration();
84 void clear_calibration(bool save = false);
85
86 template<typename F> void add_listener(F &&cb) { this->motion_data_callback_.add(std::forward<F>(cb)); }
87
88 protected:
89 // platforms must implement this method to update raw data.
90 virtual bool update_data(MotionData &data) = 0;
91
92 // for mapping axes
93 float matrix_[9]{
94 1, 0, 0, 0, 1, 0, 0, 0, 1,
95 };
96 // build-time base (axis_map / transform_matrix); used to detect config changes
97 // and to restore on clear_calibration().
98 float base_matrix_[9]{
99 1, 0, 0, 0, 1, 0, 0, 0, 1,
100 };
101
102 void map_axes_(float output[3], const float input[3]) const {
103 output[0] = input[X_AXIS] * this->matrix_[0] + input[Y_AXIS] * this->matrix_[1] + input[Z_AXIS] * this->matrix_[2];
104 output[1] = input[X_AXIS] * this->matrix_[3] + input[Y_AXIS] * this->matrix_[4] + input[Z_AXIS] * this->matrix_[5];
105 output[2] = input[X_AXIS] * this->matrix_[6] + input[Y_AXIS] * this->matrix_[7] + input[Z_AXIS] * this->matrix_[8];
106 }
107
110 uint32_t base_hash_{0}; // hash of base_matrix_, captured in setup()
112};
113
114// --- Actions ---
115
116template<typename... Ts> class CalibrateLevelAction final : public Action<Ts...> {
117 public:
118 explicit CalibrateLevelAction(MotionComponent *parent) : parent_(parent) {}
119 void set_save(bool save) { this->save_ = save; }
122
123 protected:
124 void play(const Ts &...) override {
125 if (this->parent_->calibrate_level()) {
126 // if not saving, calibration success is enough. If save required only report success after that succeeds too.
127 if (!this->save_ || this->parent_->save_calibration()) {
129 return;
130 }
131 }
132 this->error_trigger_.trigger();
133 }
134
138 bool save_{false};
139};
140
141template<typename... Ts> class CalibrateHeadingAction final : public Action<Ts...> {
142 public:
143 explicit CalibrateHeadingAction(MotionComponent *parent) : parent_(parent) {}
144 void set_save(bool save) { this->save_ = save; }
147
148 protected:
149 void play(const Ts &...) override {
150 if (this->parent_->calibrate_heading()) {
151 // if not saving, calibration success is enough. If save required only report success after that succeeds too.
152 if (!this->save_ || this->parent_->save_calibration()) {
154 return;
155 }
156 }
157 this->error_trigger_.trigger();
158 }
159
163 bool save_{false};
164};
165
166} // 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:510
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:528
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_
float get_setup_priority() const override
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.
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
constexpr float DATA
For components that import data from directly connected sensors like DHT.
Definition component.h:45
static void uint32_t