ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mlx90614.cpp
Go to the documentation of this file.
1#include "mlx90614.h"
2
3#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
8
9static const uint8_t MLX90614_RAW_IR_1 = 0x04;
10static const uint8_t MLX90614_RAW_IR_2 = 0x05;
11static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06;
12static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07;
13static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08;
14
15static const uint8_t MLX90614_TOMAX = 0x20;
16static const uint8_t MLX90614_TOMIN = 0x21;
17static const uint8_t MLX90614_PWMCTRL = 0x22;
18static const uint8_t MLX90614_TARANGE = 0x23;
19static const uint8_t MLX90614_EMISSIVITY = 0x24;
20static const uint8_t MLX90614_CONFIG = 0x25;
21static const uint8_t MLX90614_ADDR = 0x2E;
22static const uint8_t MLX90614_ID1 = 0x3C;
23static const uint8_t MLX90614_ID2 = 0x3D;
24static const uint8_t MLX90614_ID3 = 0x3E;
25static const uint8_t MLX90614_ID4 = 0x3F;
26
27ESPHOME_LOG_TAG(TAG, "mlx90614");
28
29// The EEPROM cell has a limited number of write cycles, so stop retrying after a few failures
30static constexpr uint8_t EMISSIVITY_WRITE_ATTEMPTS = 3;
31
32// SMBus packet error code: CRC-8 with polynomial 0x07, MSB first
33static uint8_t crc8_pec(const uint8_t *data, uint8_t len) { return crc8(data, len, 0x00, 0x07, true); }
34
36 if (std::isnan(this->emissivity_)) {
37 return;
38 }
39 this->emissivity_write_attempts_ = EMISSIVITY_WRITE_ATTEMPTS;
41 if (this->emissivity_write_attempts_ != 0) {
42 this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
43 }
44}
45
47 if (this->emissivity_write_attempts_ == 0) {
48 return;
49 }
50 if (this->write_emissivity_()) {
52 return;
53 }
54 if (--this->emissivity_write_attempts_ == 0) {
55 ESP_LOGE(TAG, "Giving up on writing emissivity after %u attempts", EMISSIVITY_WRITE_ATTEMPTS);
56 this->emissivity_write_failed_ = true;
57 }
58}
59
61 // Skip the write when the EEPROM already holds the desired value to save write cycles
62 uint16_t current_emissivity;
63 if (this->read_register_(MLX90614_EMISSIVITY, current_emissivity) != i2c::ERROR_OK) {
64 return false;
65 }
66
67 const auto desired_emissivity = static_cast<uint16_t>(this->emissivity_ * 0xFFFF);
68 if (current_emissivity == desired_emissivity) {
69 return true;
70 }
71
72 return this->write_register_(MLX90614_EMISSIVITY, desired_emissivity);
73}
74
75bool MLX90614Component::write_register_(uint8_t reg, uint16_t data) {
76 // The PEC covers the whole write transaction: SLA+W, command, data low, data high
77 uint8_t buf[5];
78 buf[0] = this->address_ << 1;
79 buf[1] = reg;
80
81 // See datasheet 8.3.3.1 EEPROM write sequence
82 // 1. Write 0x0000 into the cell of interest (erases the cell)
83 buf[2] = buf[3] = 0;
84 buf[4] = crc8_pec(buf, 4);
85 auto ec = this->write_register(reg, buf + 2, 3);
86 if (ec != i2c::ERROR_OK) {
87 ESP_LOGW(TAG, "Can't erase register 0x%02X, error %d", reg, ec);
88 return false;
89 }
90
91 // 2. Wait at least 5ms
92 delay(10);
93
94 // 3. Write the new value
95 if (data != 0) {
96 buf[2] = data & 0xFF;
97 buf[3] = data >> 8;
98 buf[4] = crc8_pec(buf, 4);
99 ec = this->write_register(reg, buf + 2, 3);
100 if (ec != i2c::ERROR_OK) {
101 ESP_LOGW(TAG, "Can't write register 0x%02X, error %d", reg, ec);
102 return false;
103 }
104 // 4. Wait at least 5ms
105 delay(10);
106 }
107
108 // 5. Read back to confirm the value was stored
109 uint16_t read_back;
110 ec = this->read_register_(reg, read_back);
111 if (ec != i2c::ERROR_OK) {
112 ESP_LOGW(TAG, "Can't check register 0x%02X value, error %d", reg, ec);
113 return false;
114 }
115
116 if (read_back != data) {
117 ESP_LOGW(TAG, "Read back mismatch on register 0x%02X. Expected 0x%04X, got 0x%04X", reg, data, read_back);
118 return false;
119 }
120
121 return true;
122}
123
125 // The PEC covers the whole read transaction: SLA+W, command, SLA+R, data low, data high
126 uint8_t buf[6];
127 buf[0] = this->address_ << 1;
128 buf[1] = reg;
129 buf[2] = (this->address_ << 1) | 0x01;
130
131 const auto ec = this->read_register(reg, buf + 3, 3);
132 if (ec != i2c::ERROR_OK) {
133 ESP_LOGW(TAG, "i2c read error %d", ec);
134 return ec;
135 }
136
137 const auto expected_pec = crc8_pec(buf, 5);
138 if (buf[5] != expected_pec) {
139 ESP_LOGW(TAG, "i2c CRC error. Expected 0x%02X, got 0x%02X", expected_pec, buf[5]);
140 return i2c::ERROR_CRC;
141 }
142
143 data = encode_value_le<uint16_t>(&buf[3]);
144 return i2c::ERROR_OK;
145}
146
148 ESP_LOGCONFIG(TAG, "MLX90614:");
149 LOG_I2C_DEVICE(this);
150 if (this->emissivity_write_attempts_ != 0) {
151 ESP_LOGW(TAG, " Emissivity not written yet, will retry");
152 }
153 LOG_UPDATE_INTERVAL(this);
154 LOG_SENSOR(" ", "Ambient", this->ambient_sensor_);
155 LOG_SENSOR(" ", "Object", this->object_sensor_);
156}
157
159 // Temperature reads run regardless of the emissivity state so a failure still shows up as NAN
160 this->try_write_emissivity_();
161
162 // Publishes NAN on a bus or CRC failure so a stuck reading is visible instead of silently stale
163 auto publish_sensor = [this](sensor::Sensor *sensor, uint8_t reg) {
164 if (sensor == nullptr) {
165 return i2c::ERROR_OK;
166 }
167
168 uint16_t raw;
169 const auto ec = this->read_register_(reg, raw);
170 if (ec != i2c::ERROR_OK) {
171 sensor->publish_state(NAN);
172 return ec;
173 }
174
175 // Bit 15 set means the device flagged the reading as invalid
176 const float temperature = (raw & 0x8000) ? NAN : raw * 0.02f - 273.15f;
177 ESP_LOGD(TAG, "'%s': Got temperature=%.1f°C", LOG_STR_ARG(sensor->get_log_name()), temperature);
178 sensor->publish_state(temperature);
179 return ec;
180 };
181
182 const auto object_ec = publish_sensor(this->object_sensor_, MLX90614_TEMPERATURE_OBJECT_1);
183 const auto ambient_ec = publish_sensor(this->ambient_sensor_, MLX90614_TEMPERATURE_AMBIENT);
184
185 if (object_ec != i2c::ERROR_OK || ambient_ec != i2c::ERROR_OK) {
186 this->status_set_warning(LOG_STR("Failed to read some sensors"));
187 } else if (this->emissivity_write_failed_) {
188 this->status_set_warning(LOG_STR("Failed to write emissivity"));
189 } else if (this->emissivity_write_attempts_ != 0) {
190 this->status_set_warning(LOG_STR("Failed to write emissivity, will retry"));
191 } else {
192 this->status_clear_warning();
193 }
194}
195
196} // namespace esphome::mlx90614
uint8_t raw[35]
Definition bl0939.h:0
void status_clear_warning()
Definition component.h:296
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) const
writes an array of bytes to a specific register in the I²C device
Definition i2c.cpp:37
uint8_t address_
store the address of the device on the bus
Definition i2c.h:327
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:164
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len)
reads an array of bytes from a specific register in the I²C device
Definition i2c.cpp:28
bool write_register_(uint8_t reg, uint16_t data)
Definition mlx90614.cpp:75
i2c::ErrorCode read_register_(uint8_t reg, uint16_t &data)
Definition mlx90614.cpp:124
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition i2c_bus.h:13
@ ERROR_CRC
bytes received with a CRC error
Definition i2c_bus.h:22
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "mlx90614")
const void size_t len
Definition hal.h:68
constexpr T encode_value_le(const uint8_t *bytes)
Encode a value from its constituent bytes (from least to most significant) in an array with length si...
Definition helpers.h:1039
uint8_t crc8(const uint8_t *data, uint8_t len, uint8_t crc, uint8_t poly, bool msb_first)
Calculate a CRC-8 checksum of data with size len.
Definition helpers.cpp:68
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint16_t temperature
Definition sun_gtil2.cpp:12