ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
ms8607.cpp
Go to the documentation of this file.
1#include "ms8607.h"
2
3#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7namespace esphome {
8namespace ms8607 {
9
11static const char *const TAG = "ms8607";
12
14static const uint8_t MS8607_PT_CMD_RESET = 0x1E;
15
18static const uint8_t MS8607_PROM_START = 0xA0;
20static const uint8_t MS8607_PROM_END = 0xAE;
22static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1;
23
25static const uint8_t MS8607_CMD_H_RESET = 0xFE;
27static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5;
29static const float MS8607_H_TEMP_COEFFICIENT = -0.18;
30
32static const uint8_t MS8607_CMD_ADC_READ = 0x00;
33
34// TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration.
35// ms8607 supports 6 different settings
36
38static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A;
40static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A;
41
44 NONE = 0,
46 PTH_RESET_FAILED = 1,
48 PT_RESET_FAILED = 2,
50 H_RESET_FAILED = 3,
52 PROM_READ_FAILED = 4,
54 PROM_CRC_FAILED = 5,
55};
56
59 NEEDS_RESET,
61 NEEDS_PROM_READ,
63 SUCCESSFUL,
64};
65
66static uint8_t crc4(uint16_t *buffer, size_t length);
67static uint8_t hsensor_crc_check(uint16_t value);
68
72
73 // I do not know why the device sometimes NACKs the reset command, but
74 // try 3 times in case it's a transitory issue on this boot
75 // Backoff: executes at now, +5ms, +30ms
77 this->reset_interval_ = 5;
78 this->try_reset_();
79}
80
82 ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_, this->humidity_device_->get_address());
83 // I believe sending the reset command to both addresses is preferable to
84 // skipping humidity if PT fails for some reason.
85 // However, only consider the reset successful if they both ACK
86 bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0);
87 bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0);
88
89 if (!(pt_successful && h_successful)) {
90 ESP_LOGE(TAG, "Resetting I2C devices failed");
91 if (!pt_successful && !h_successful) {
93 } else if (!pt_successful) {
95 } else {
97 }
98
99 if (--this->reset_attempts_remaining_ > 0) {
100 uint32_t delay = this->reset_interval_;
101 this->reset_interval_ *= 5;
102 this->set_timeout("reset", delay, [this]() { this->try_reset_(); });
103 this->status_set_error();
104 } else {
105 this->mark_failed();
106 }
107 return;
108 }
109
112 this->status_clear_error();
113
114 // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library
115 this->set_timeout("prom-read", 15, [this]() {
118 this->status_clear_error();
119 } else {
120 this->mark_failed();
121 return;
122 }
123 });
124}
125
128 // setup is still occurring, either because reset had to retry or due to the 15ms
129 // delay needed between reset & reading the PROM values
130 return;
131 }
132
133 // Updating happens async and sequentially.
134 // Temperature, then pressure, then humidity
136}
137
139 ESP_LOGCONFIG(TAG, "MS8607:");
140 LOG_I2C_DEVICE(this);
141 // LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address()
142 ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->humidity_device_->get_address());
143 if (this->is_failed()) {
144 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
145 switch (this->error_code_) {
147 ESP_LOGE(TAG, "Temperature/Pressure RESET failed");
148 break;
150 ESP_LOGE(TAG, "Humidity RESET failed");
151 break;
153 ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed");
154 break;
156 ESP_LOGE(TAG, "Reading PROM failed");
157 break;
159 ESP_LOGE(TAG, "PROM values failed CRC");
160 break;
161 case ErrorCode::NONE:
162 default:
163 ESP_LOGE(TAG, "Error reason unknown %u", static_cast<uint8_t>(this->error_code_));
164 break;
165 }
166 }
167 LOG_UPDATE_INTERVAL(this);
168 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
169 LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
170 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
171}
172
174 ESP_LOGD(TAG, "Reading calibration values from PROM");
175
176 uint16_t buffer[MS8607_PROM_COUNT];
177 bool successful = true;
178
179 for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) {
180 uint8_t const address_to_read = MS8607_PROM_START + (idx * 2);
181 successful &= this->read_byte_16(address_to_read, &buffer[idx]);
182 }
183
184 if (!successful) {
185 ESP_LOGE(TAG, "Reading calibration values from PROM failed");
187 return false;
188 }
189
190 ESP_LOGD(TAG, "Checking CRC of calibration values from PROM");
191 uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12; // first 4 bits
192 buffer[0] &= 0x0FFF; // strip CRC from buffer, in order to run CRC
193 uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT);
194
195 if (expected_crc != actual_crc) {
196 ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc);
198 return false;
199 }
200
202 this->calibration_values_.pressure_offset = buffer[2];
207 ESP_LOGD(TAG, "Finished reading calibration values");
208
209 // Skipping reading Humidity PROM, since it doesn't have anything interesting for us
210
211 return true;
212}
213
220static uint8_t crc4(uint16_t *buffer, size_t length) {
221 uint16_t crc_remainder = 0;
222
223 // algorithm to add a byte into the crc
224 auto apply_crc = [&crc_remainder](uint8_t next) {
225 crc_remainder ^= next;
226 for (uint8_t bit = 8; bit > 0; --bit) {
227 if (crc_remainder & 0x8000) {
228 crc_remainder = (crc_remainder << 1) ^ 0x3000;
229 } else {
230 crc_remainder = (crc_remainder << 1);
231 }
232 }
233 };
234
235 // add all the bytes
236 for (size_t idx = 0; idx < length; ++idx) {
237 for (auto byte : decode_value(buffer[idx])) {
238 apply_crc(byte);
239 }
240 }
241 // For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through
242 apply_crc(0);
243 apply_crc(0);
244
245 return (crc_remainder >> 12) & 0xF; // only the most significant 4 bits
246}
247
257static uint8_t hsensor_crc_check(uint16_t value) {
258 uint32_t polynom = 0x988000; // x^8 + x^5 + x^4 + 1
259 uint32_t msb = 0x800000;
260 uint32_t mask = 0xFF8000;
261 uint32_t result = (uint32_t) value << 8; // Pad with zeros as specified in spec
262
263 while (msb != 0x80) {
264 // Check if msb of current value is 1 and apply XOR mask
265 if (result & msb) {
266 result = ((result ^ polynom) & mask) | (result & ~mask);
267 }
268
269 // Shift by one
270 msb >>= 1;
271 mask >>= 1;
272 polynom >>= 1;
273 }
274 return result & 0xFF;
275}
276
278 // Tell MS8607 to start ADC conversion of temperature sensor
279 if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) {
280 this->status_set_warning();
281 return;
282 }
283
284 auto f = std::bind(&MS8607Component::read_temperature_, this);
285 // datasheet says 17.2ms max conversion time at OSR 8192
286 this->set_timeout("temperature", 20, f);
287}
288
290 uint8_t bytes[3]; // 24 bits
291 if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) {
292 this->status_set_warning();
293 return;
294 }
295
296 const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]);
297 this->request_read_pressure_(d2_raw_temperature);
298}
299
300void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) {
301 if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) {
302 this->status_set_warning();
303 return;
304 }
305
306 auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature);
307 // datasheet says 17.2ms max conversion time at OSR 8192
308 this->set_timeout("pressure", 20, f);
309}
310
311void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) {
312 uint8_t bytes[3]; // 24 bits
313 if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) {
314 this->status_set_warning();
315 return;
316 }
317 const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]);
318 this->calculate_values_(d2_raw_temperature, d1_raw_pressure);
319}
320
321void MS8607Component::request_read_humidity_(float temperature_float) {
322 if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) {
323 ESP_LOGW(TAG, "Request to measure humidity failed");
324 this->status_set_warning();
325 return;
326 }
327
328 auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float);
329 // datasheet says 15.89ms max conversion time at OSR 8192
330 this->set_timeout("humidity", 20, f);
331}
332
333void MS8607Component::read_humidity_(float temperature_float) {
334 uint8_t bytes[3];
335 if (!this->humidity_device_->read_bytes_raw(bytes, 3)) {
336 ESP_LOGW(TAG, "Failed to read the measured humidity value");
337 this->status_set_warning();
338 return;
339 }
340
341 // "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information.
342 // Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned"
343 uint16_t humidity = encode_uint16(bytes[0], bytes[1]);
344 uint8_t const expected_crc = bytes[2];
345 uint8_t const actual_crc = hsensor_crc_check(humidity);
346 if (expected_crc != actual_crc) {
347 ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc,
348 actual_crc);
349 this->status_set_warning();
350 return;
351 }
352 if (!(humidity & 0x2)) {
353 // data sheet says Bit1 should always set, but nothing about what happens if it isn't
354 ESP_LOGE(TAG, "Humidity status bit was not set to 1?");
355 }
356 humidity &= ~(0b11); // strip status & unassigned bits from data
357
358 // map 16 bit humidity value into range [-6%, 118%]
359 float const humidity_partial = double(humidity) / (1 << 16);
360 float const humidity_percentage = std::lerp(-6.0, 118.0, humidity_partial);
361 float const compensated_humidity_percentage =
362 humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT;
363 ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage);
364
365 if (this->humidity_sensor_ != nullptr) {
366 this->humidity_sensor_->publish_state(compensated_humidity_percentage);
367 }
368 this->status_clear_warning();
369}
370
371void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) {
372 // Perform the first order pressure/temperature calculation
373
374 // d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8
375 const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8);
376 // actual temperature as hundredths of degree celsius in range [-4000, 8500]
377 // 2000 + d_t * [C6] / (2**23)
378 int32_t temperature =
379 2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23);
380
381 // offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6))
382 int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) +
384 // sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7)
385 int64_t pressure_sensitivity =
386 (int64_t(this->calibration_values_.pressure_sensitivity) << 16) +
388
389 // Perform the second order compensation, for non-linearity over temperature range
390 const int64_t d_t_squared = int64_t(d_t) * d_t;
391 int64_t temperature_2 = 0;
392 int32_t pressure_offset_2 = 0;
393 int32_t pressure_sensitivity_2 = 0;
394 if (temperature < 2000) {
395 // (TEMP - 2000)**2 / 2**4
396 const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4;
397
398 // T2 = 3 * (d_t**2) / 2**33
399 temperature_2 = (3 * d_t_squared) >> 33;
400 // OFF2 = 61 * (TEMP-2000)**2 / 2**4
401 pressure_offset_2 = 61 * low_temperature_adjustment;
402 // SENS2 = 29 * (TEMP-2000)**2 / 2**4
403 pressure_sensitivity_2 = 29 * low_temperature_adjustment;
404
405 if (temperature < -1500) {
406 // (TEMP+1500)**2
407 const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500);
408
409 // OFF2 = OFF2 + 17 * (TEMP+1500)**2
410 pressure_offset_2 += 17 * very_low_temperature_adjustment;
411 // SENS2 = SENS2 + 9 * (TEMP+1500)**2
412 pressure_sensitivity_2 += 9 * very_low_temperature_adjustment;
413 }
414 } else {
415 // T2 = 5 * (d_t**2) / 2**38
416 temperature_2 = (5 * d_t_squared) >> 38;
417 }
418
419 temperature -= temperature_2;
420 pressure_offset -= pressure_offset_2;
421 pressure_sensitivity -= pressure_sensitivity_2;
422
423 // Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar]
424 const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15;
425
426 const float temperature_float = temperature / 100.0f;
427 const float pressure_float = pressure / 100.0f;
428 ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float);
429
430 if (this->temperature_sensor_ != nullptr) {
431 this->temperature_sensor_->publish_state(temperature_float);
432 }
433 if (this->pressure_sensor_ != nullptr) {
434 this->pressure_sensor_->publish_state(pressure_float); // hPa aka mbar
435 }
436 this->status_clear_warning();
437
438 if (this->humidity_sensor_ != nullptr) {
439 // now that we have temperature (to compensate the humidity with), kick off that read
440 this->request_read_humidity_(temperature_float);
441 }
442}
443
444} // namespace ms8607
445} // namespace esphome
void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:443
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
Definition component.h:387
void status_clear_warning()
bool read_bytes_raw(uint8_t *data, uint8_t len) const
Definition i2c.h:221
uint8_t address_
store the address of the device on the bus
Definition i2c.h:270
bool read_byte_16(uint8_t a_register, uint16_t *data)
Definition i2c.h:249
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const
Definition i2c.h:251
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:217
void read_humidity_(float temperature_float)
process async humidity read
Definition ms8607.cpp:333
void request_read_temperature_()
Start async temperature read.
Definition ms8607.cpp:277
@ PROM_CRC_FAILED
The PROM calibration values failed the CRC check.
@ H_RESET_FAILED
Asking the Humidity sensor to reset failed.
@ PT_RESET_FAILED
Asking the Pressure/Temperature sensor to reset failed.
@ PROM_READ_FAILED
Reading the PROM calibration values failed.
@ PTH_RESET_FAILED
Both the Pressure/Temperature address and the Humidity address failed to reset.
@ NONE
Component hasn't failed (yet?)
struct esphome::ms8607::MS8607Component::CalibrationValues calibration_values_
MS8607HumidityDevice * humidity_device_
I2CDevice object to communicate with secondary I2C address for the humidity sensor.
Definition ms8607.h:80
sensor::Sensor * temperature_sensor_
Definition ms8607.h:70
sensor::Sensor * humidity_sensor_
Definition ms8607.h:72
SetupStatus setup_status_
Current step in the multi-step & possibly delayed setup() process.
Definition ms8607.h:106
void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure)
use raw temperature & pressure to calculate & publish values
Definition ms8607.cpp:371
void read_pressure_(uint32_t raw_temperature)
process async pressure read
Definition ms8607.cpp:311
ErrorCode error_code_
Keep track of the reason why this component failed, to augment the dumped config.
Definition ms8607.h:101
bool read_calibration_values_from_prom_()
Read and store the Pressure & Temperature calibration settings from the PROM.
Definition ms8607.cpp:173
void request_read_pressure_(uint32_t raw_temperature)
start async pressure read
Definition ms8607.cpp:300
sensor::Sensor * pressure_sensor_
Definition ms8607.h:71
@ NEEDS_PROM_READ
Reset commands succeeded, need to wait >= 15ms to read PROM.
@ NEEDS_RESET
This component has not successfully reset the PT & H devices.
@ SUCCESSFUL
Successfully read PROM and ready to update sensors.
void try_reset_()
Attempt to reset both I2C devices, retrying with backoff on failure.
Definition ms8607.cpp:81
void read_temperature_()
Process async temperature read.
Definition ms8607.cpp:289
void request_read_humidity_(float temperature_float)
start async humidity read
Definition ms8607.cpp:321
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:661
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:653
void HOT delay(uint32_t ms)
Definition core.cpp:27
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition helpers.h:682
uint16_t pressure_offset_temperature_coefficient
Temperature coefficient of pressure offset | TCO. [C4].
Definition ms8607.h:91
uint16_t temperature_coefficient_of_temperature
Temperature coefficient of the temperature | TEMPSENS. [C6].
Definition ms8607.h:95
uint16_t pressure_sensitivity_temperature_coefficient
Temperature coefficient of pressure sensitivity | TCS. [C3].
Definition ms8607.h:87
uint16_t pressure_sensitivity
Pressure sensitivity | SENS-T1. [C1].
Definition ms8607.h:85
uint16_t pressure_offset
Pressure offset | OFF-T1. [C2].
Definition ms8607.h:89
uint16_t reference_temperature
Reference temperature | T-REF. [C5].
Definition ms8607.h:93
uint16_t temperature
Definition sun_gtil2.cpp:12
uint16_t length
Definition tt21100.cpp:0
uint8_t pressure
Definition tt21100.cpp:7