ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mics_5524.cpp
Go to the documentation of this file.
1#include "mics_5524.h"
2
3#include <algorithm>
4#include <cinttypes>
5
7#include "esphome/core/log.h"
8
10
11ESPHOME_LOG_TAG(TAG, "mics_5524");
12
14 const float ppm = micsmath::ppm_from_vendor_curve(ratio, this->curve_);
15 ESP_LOGD(TAG, "'%s': ratio=%.4f -> %.1f ppm", this->get_name().c_str(), ratio, ppm);
16 this->publish_state(ppm);
17}
18
20 if (this->has_reference_()) {
21 ESP_LOGI(TAG, "using the air reference from the configuration (%.4f V)", this->reference_);
22 }
23
24 if (this->warmup_time_ > 0) {
25 this->stop_poller();
26 this->set_timeout(this->warmup_time_, [this]() { this->start_poller(); });
27 ESP_LOGI(TAG, "warm-up of %" PRIu32 " s - no readings published before that", this->warmup_time_ / 1000);
28 }
29
30 if (this->calibration_samples_ > 0) {
31 // Nothing is stored across reboots; the warm-up window starts here, so the delay covers it.
32 const uint32_t delay = std::max(this->calibration_delay_, this->warmup_time_);
33 this->set_timeout(CALIBRATION_START_ID, delay, [this]() { this->begin_calibration_(); });
34 ESP_LOGI(TAG, "air reference calibration in %" PRIu32 " s - the sensor must be in clean air", delay / 1000);
35 } else if (!this->has_reference_()) {
36 ESP_LOGW(TAG, "no air reference available, the PPM values stay unknown\n"
37 " set 'air_reference:' or enable 'calibration:'");
38 }
39
40 // The loop only samples while a clean-air calibration runs; `update()` publishes.
41 this->disable_loop();
42}
43
45 ESP_LOGCONFIG(TAG,
46 "MiCS-5524:\n"
47 " VCC: %.2f V, AO multiplier: x%.3f, AO samples per reading: %u\n"
48 " Warm-up: %" PRIu32 " s, calibration: %s, sample spacing: %" PRIu32 " ms",
49 this->vcc_, this->voltage_multiplier_, static_cast<unsigned>(this->samples_), this->warmup_time_ / 1000,
50 this->calibration_samples_ > 0 ? LOG_STR_LITERAL("enabled") : LOG_STR_LITERAL("disabled"),
51 this->sample_interval_);
52 LOG_UPDATE_INTERVAL(this);
53 if (this->has_reference_()) {
54 ESP_LOGCONFIG(TAG, " Air reference (vcc - V_AO): %.4f V", this->reference_);
55 } else {
56 ESP_LOGCONFIG(TAG, " Air reference: not available yet");
57 }
58#ifdef MICS_5524_GAS_COUNT
59 for (auto *gas : this->gases_) {
60 LOG_SENSOR(" ", "Gas", gas);
61 ESP_LOGCONFIG(TAG, " Range: %.1f - %.1f ppm", gas->get_min_ppm(), gas->get_max_ppm());
62 }
63#endif
64}
65
67 // Taken back to back; waiting between them would block the main loop.
68 float sum = 0.0f;
69 for (uint8_t i = 0; i < this->samples_; i++)
70 sum += this->source_->sample();
71 return (sum / static_cast<float>(this->samples_)) * this->voltage_multiplier_;
72}
73
75#ifdef MICS_5524_GAS_COUNT
76 for (auto *gas : this->gases_) {
77 gas->publish_ratio(ratio);
78 }
79#endif
80}
81
83 const float voltage = this->sample_voltage_();
84 const float x = micsmath::air_value_from_voltage(voltage, this->vcc_);
85 if (std::isfinite(x))
86 return x;
87 // The status flag makes this log once per fault, not once per update.
88 if (!this->status_has_warning()) {
89 ESP_LOGW(TAG, "analog output reads %.4f V with vcc %.2f V\n check the AO wiring, 'vcc:' and 'voltage_multiplier:'",
90 voltage, this->vcc_);
91 }
92 this->status_set_warning(LOG_STR("analog output out of range"));
93 return NAN;
94}
95
97 // Every gas starts unknown and the reference is never cleared, so there is nothing to republish.
98 if (!this->has_reference_())
99 return;
100
101 const float air_value = this->read_air_value_();
102 if (std::isnan(air_value)) {
103 this->publish_ratio_(NAN);
104 return;
105 }
106 this->status_clear_warning();
107
108 const float ratio = micsmath::ratio_from_air_value(air_value, this->reference_);
109 ESP_LOGD(TAG, "V_AO=%.4f V, x=%.4f, ratio=%.4f", this->vcc_ - air_value, air_value, ratio);
110 this->publish_ratio_(ratio);
111}
112
115 this->calibration_samples_ = samples;
116 this->sample_interval_ = sample_interval;
117}
118
120 if (!this->calibrating_) {
121 // Only a calibration samples here; the deferred start is a timer.
122 this->disable_loop();
123 return;
124 }
125
128 return;
129
130 this->calibration_last_sample_ = now;
131 this->process_calibration_();
132}
133
135 this->calibrating_ = true;
137 this->calibration_count_ = 0;
138 this->calibration_attempts_ = 0;
139 this->calibration_sum_ = 0.0f;
140 this->calibration_min_ = INFINITY;
141 this->calibration_max_ = -INFINITY;
142 this->enable_loop();
143
144 ESP_LOGI(TAG, "calibrating the air reference, %" PRIu32 " samples - keep the sensor in clean air",
146}
147
149 this->calibration_attempts_++;
150
151 const float reference = this->read_air_value_();
152 if (!std::isnan(reference)) {
153 this->calibration_min_ = std::min(this->calibration_min_, reference);
154 this->calibration_max_ = std::max(this->calibration_max_, reference);
155 this->calibration_sum_ += reference;
156 this->calibration_count_++;
157 }
158
160 this->finish_calibration_();
161}
162
164 this->calibrating_ = false;
165 this->disable_loop();
166
167 const uint32_t attempts = this->calibration_attempts_;
168 const uint32_t valid = this->calibration_count_;
169 // A majority of the attempts has to have produced a sample, two at least.
170 const uint32_t required = std::max<uint32_t>((attempts + 1) / 2, 2);
171
172 if (valid < required) {
173 ESP_LOGW(TAG, "calibration failed, %" PRIu32 " of %" PRIu32 " samples were valid", valid, attempts);
175 return;
176 }
177
178 const float reference = this->calibration_sum_ / static_cast<float>(valid);
179
180 // A sensor still settling, or not in clean air, varies too much to be the reference.
182 ESP_LOGW(
183 TAG, "clean-air samples spread %.4f V around %.4f V (%.4f - %.4f V)\n the sensor was not settled in clean air",
184 this->calibration_max_ - this->calibration_min_, reference, this->calibration_min_, this->calibration_max_);
186 return;
187 }
188
189 this->reference_ = reference;
190 this->status_clear_warning();
191
192 ESP_LOGI(TAG,
193 "air reference = %.4f (%" PRIu32 "/%" PRIu32 " samples valid)\n"
194 " hard-code 'air_reference: %.4f' to skip the calibration at boot",
195 this->reference_, valid, attempts, this->reference_);
196}
197
199 this->status_set_warning();
200 // A zero delay would log the same failure about five times per second.
202 this->set_timeout(CALIBRATION_START_ID, delay, [this]() { this->begin_calibration_(); });
203 ESP_LOGW(TAG, "retrying the calibration in %" PRIu32 " s", delay / 1000);
204}
205
206} // namespace esphome::mics_5524
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void enable_loop()
Enable this component's loop.
Definition component.h:248
bool status_has_warning() const
Definition component.h:280
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:291
const StringRef & get_name() const
Definition entity_base.h:71
uint32_t calibration_samples_
0 when calibration is disabled.
Definition mics_5524.h:76
uint32_t sample_interval_
Spacing of the clean-air calibration samples (ms); the ~16 ms loop tick is the floor.
Definition mics_5524.h:78
static constexpr uint32_t CALIBRATION_START_ID
Definition mics_5524.h:80
float reference_
Clean-air reference x_air in volts, 0 while there is none.
Definition mics_5524.h:93
StaticVector< MiCS5524GasSensor *, MICS_5524_GAS_COUNT > gases_
Definition mics_5524.h:97
float sample_voltage_()
Averaged, scaled output voltage (V); the conversions run back to back.
Definition mics_5524.cpp:66
void set_calibration(uint32_t delay, uint32_t samples, uint32_t sample_interval)
Calibrate at boot: start after delay ms, average samples readings sample_interval ms apart.
float read_air_value_()
x = vcc - V_AO, or NAN (and a status warning) when the voltage is unusable.
Definition mics_5524.cpp:82
voltage_sampler::VoltageSampler * source_
Definition mics_5524.h:69
static constexpr uint32_t CALIBRATION_RETRY_MIN_MS
Floor of the delay between two calibration attempts (ms).
Definition mics_5524.h:82
float calibration_min_
Smallest and largest clean-air sample of the run, see calibration_is_stable().
Definition mics_5524.h:89
void publish_ratio(float ratio)
Publish the concentration for ratio; NAN publishes unknown.
Definition mics_5524.cpp:13
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
virtual float sample()=0
Get a voltage reading, in V.
bool calibration_is_stable(float min_value, float max_value, float mean)
True when the clean-air samples spread at most MAX_CALIBRATION_SPREAD of their mean.
Definition mics_math.h:30
float air_value_from_voltage(float voltage, float vcc)
x = VCC - V_ao: 0 at VCC (a saturated sensor, never clean air).
Definition mics_math.h:20
float ratio_from_air_value(float x, float x_air)
ratio = x / x_air (NaN when either value is unusable).
Definition mics_math.h:37
float ppm_from_vendor_curve(float ratio, const VendorCurve &curve)
ratio -> ppm as DFRobot_MICS getGasData(): 0 above the threshold or below the vendor range,...
Definition mics_math.h:45
ESPHOME_LOG_TAG(TAG, "mics_5524")
void HOT delay(uint32_t ms)
Definition hal.cpp:85
Application App
Global storage of Application pointer - only one Application can exist.
bool valid
static void uint32_t
uint16_t x
Definition tt21100.cpp:5