ESPHome  2024.4.1
absolute_humidity.cpp
Go to the documentation of this file.
1 #include "esphome/core/log.h"
2 #include "absolute_humidity.h"
3 
4 namespace esphome {
5 namespace absolute_humidity {
6 
7 static const char *const TAG = "absolute_humidity.sensor";
8 
10  ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str());
11 
12  ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
13  this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
14  if (this->temperature_sensor_->has_state()) {
16  }
17 
18  ESP_LOGD(TAG, " Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str());
19  this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); });
20  if (this->humidity_sensor_->has_state()) {
22  }
23 }
24 
26  LOG_SENSOR("", "Absolute Humidity", this);
27 
28  switch (this->equation_) {
29  case BUCK:
30  ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck");
31  break;
32  case TETENS:
33  ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens");
34  break;
35  case WOBUS:
36  ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus");
37  break;
38  default:
39  ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
40  break;
41  }
42 
43  ESP_LOGCONFIG(TAG, "Sources");
44  ESP_LOGCONFIG(TAG, " Temperature: '%s'", this->temperature_sensor_->get_name().c_str());
45  ESP_LOGCONFIG(TAG, " Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str());
46 }
47 
49 
51  if (!this->next_update_) {
52  return;
53  }
54  this->next_update_ = false;
55 
56  // Ensure we have source data
57  const bool no_temperature = std::isnan(this->temperature_);
58  const bool no_humidity = std::isnan(this->humidity_);
59  if (no_temperature || no_humidity) {
60  if (no_temperature) {
61  ESP_LOGW(TAG, "No valid state from temperature sensor!");
62  }
63  if (no_humidity) {
64  ESP_LOGW(TAG, "No valid state from temperature sensor!");
65  }
66  ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
67  this->publish_state(NAN);
68  this->status_set_warning();
69  return;
70  }
71 
72  // Convert to desired units
73  const float temperature_c = this->temperature_;
74  const float temperature_k = temperature_c + 273.15;
75  const float hr = this->humidity_ / 100;
76 
77  // Calculate saturation vapor pressure
78  float es;
79  switch (this->equation_) {
80  case BUCK:
81  es = es_buck(temperature_c);
82  break;
83  case TETENS:
84  es = es_tetens(temperature_c);
85  break;
86  case WOBUS:
87  es = es_wobus(temperature_c);
88  break;
89  default:
90  ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
91  this->publish_state(NAN);
92  this->status_set_error();
93  return;
94  }
95  ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);
96 
97  // Calculate absolute humidity
98  const float absolute_humidity = vapor_density(es, hr, temperature_k);
99 
100  // Publish absolute humidity
101  ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity);
102  this->status_clear_warning();
103  this->publish_state(absolute_humidity);
104 }
105 
106 // Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation)
107 // More accurate than Tetens in normal meteorologic conditions
108 float AbsoluteHumidityComponent::es_buck(float temperature_c) {
109  float a, b, c, d;
110  if (temperature_c >= 0) {
111  a = 0.61121;
112  b = 18.678;
113  c = 234.5;
114  d = 257.14;
115  } else {
116  a = 0.61115;
117  b = 18.678;
118  c = 233.7;
119  d = 279.82;
120  }
121  return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c)));
122 }
123 
124 // Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation)
125 float AbsoluteHumidityComponent::es_tetens(float temperature_c) {
126  float a, b;
127  if (temperature_c >= 0) {
128  a = 17.27;
129  b = 237.3;
130  } else {
131  a = 21.875;
132  b = 265.5;
133  }
134  return 0.61078 * expf((a * temperature_c) / (temperature_c + b));
135 }
136 
137 // Wobus equation
138 // https://wahiduddin.net/calc/density_altitude.htm
139 // https://wahiduddin.net/calc/density_algorithms.htm
140 // Calculate the saturation vapor pressure (kPa)
142  // THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS)
143  // OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL
144  // APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO
145  // WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA,
146  // BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE
147  // CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH-
148  // SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE
149  // APPROXIMATION IS VALID FOR -50 < T < 100C.
150  //
151  // Baker, Schlatter 17-MAY-1982 Original version.
152 
153  const float c0 = +0.99999683e00;
154  const float c1 = -0.90826951e-02;
155  const float c2 = +0.78736169e-04;
156  const float c3 = -0.61117958e-06;
157  const float c4 = +0.43884187e-08;
158  const float c5 = -0.29883885e-10;
159  const float c6 = +0.21874425e-12;
160  const float c7 = -0.17892321e-14;
161  const float c8 = +0.11112018e-16;
162  const float c9 = -0.30994571e-19;
163  const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9)))))))));
164  return 0.61078 / pow(p, 8);
165 }
166 
167 // From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/
168 // H/T to https://esphome.io/cookbook/bme280_environment.html
169 // H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
170 float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) {
171  // es = saturated vapor pressure (kPa)
172  // hr = relative humidity [0-1]
173  // ta = absolute temperature (K)
174 
175  const float ea = hr * es * 1000; // vapor pressure of the air (Pa)
176  const float mw = 18.01528; // molar mass of water (g⋅mol⁻¹)
177  const float r = 8.31446261815324; // molar gas constant (J⋅K⁻¹)
178  return (ea * mw) / (r * ta);
179 }
180 
181 } // namespace absolute_humidity
182 } // namespace esphome
void add_on_state_callback(std::function< void(float)> &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition: sensor.cpp:52
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
static float vapor_density(float es, float hr, float ta)
Calculate vapor density (absolute humidity) in g/m³.
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
static float es_tetens(float temperature_c)
Tetens equation for saturation vapor pressure in kPa.
float state
This member variable stores the last state that has passed through all filters.
Definition: sensor.h:131
void status_set_error(const char *message="unspecified")
Definition: component.cpp:159
void status_clear_warning()
Definition: component.cpp:166
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
constexpr const char * c_str() const
Definition: string_ref.h:68
float get_state() const
Getter-syntax for .state.
Definition: sensor.cpp:86
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
bool has_state() const
Return whether this sensor has gotten a full state (that passed through all filters) yet...
Definition: sensor.cpp:97
static float es_wobus(float temperature_c)
Wobus equation for saturation vapor pressure in kPa.
const StringRef & get_name() const
Definition: entity_base.cpp:10
static float es_buck(float temperature_c)
Buck equation for saturation vapor pressure in kPa.