ESPHome 2025.5.2
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
ens210.cpp
Go to the documentation of this file.
1// ENS210 relative humidity and temperature sensor with I2C interface from ScioSense
2//
3// Datasheet: https://www.sciosense.com/wp-content/uploads/2021/01/ENS210.pdf
4//
5// Implementation based on:
6// https://github.com/maarten-pennings/ENS210
7// https://github.com/sciosense/ENS210_driver
8
9#include "ens210.h"
10#include "esphome/core/log.h"
11#include "esphome/core/hal.h"
12
13namespace esphome {
14namespace ens210 {
15
16static const char *const TAG = "ens210";
17
18// ENS210 chip constants
19static const uint8_t ENS210_BOOTING_MS = 2; // Booting time in ms (also after reset, or going to high power)
20static const uint8_t ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS =
21 130; // Conversion time in ms for single shot T/H measurement
22static const uint16_t ENS210_PART_ID = 0x0210; // The expected part id of the ENS210
23
24// Addresses of the ENS210 registers
25static const uint8_t ENS210_REGISTER_PART_ID = 0x00;
26static const uint8_t ENS210_REGISTER_UID = 0x04;
27static const uint8_t ENS210_REGISTER_SYS_CTRL = 0x10;
28static const uint8_t ENS210_REGISTER_SYS_STAT = 0x11;
29static const uint8_t ENS210_REGISTER_SENS_RUN = 0x21;
30static const uint8_t ENS210_REGISTER_SENS_START = 0x22;
31static const uint8_t ENS210_REGISTER_SENS_STOP = 0x23;
32static const uint8_t ENS210_REGISTER_SENS_STAT = 0x24;
33static const uint8_t ENS210_REGISTER_T_VAL = 0x30;
34static const uint8_t ENS210_REGISTER_H_VAL = 0x33;
35
36// CRC-7 constants
37static const uint8_t CRC7_WIDTH = 7; // A 7 bits CRC has polynomial of 7th order, which has 8 terms
38static const uint8_t CRC7_POLY = 0x89; // The 8 coefficients of the polynomial
39static const uint8_t CRC7_IVEC = 0x7F; // Initial vector has all 7 bits high
40
41// Payload data constants
42static const uint8_t DATA7_WIDTH = 17;
43static const uint32_t DATA7_MASK = ((1UL << DATA7_WIDTH) - 1); // 0b 0 1111 1111 1111 1111
44static const uint32_t DATA7_MSB = (1UL << (DATA7_WIDTH - 1)); // 0b 1 0000 0000 0000 0000
45
46// Converts a status to a human readable string
47static const LogString *ens210_status_to_human(int status) {
48 switch (status) {
50 return LOG_STR("I2C error - communication with ENS210 failed!");
52 return LOG_STR("CRC error");
54 return LOG_STR("Invalid data");
56 return LOG_STR("Status OK");
58 return LOG_STR("ENS210 has wrong chip ID! Is it a ENS210?");
59 default:
60 return LOG_STR("Unknown");
61 }
62}
63
64// Compute the CRC-7 of 'value' (should only have 17 bits)
65// https://en.wikipedia.org/wiki/Cyclic_redundancy_check#Computation
66static uint32_t crc7(uint32_t value) {
67 // Setup polynomial
68 uint32_t polynomial = CRC7_POLY;
69 // Align polynomial with data
70 polynomial = polynomial << (DATA7_WIDTH - CRC7_WIDTH - 1);
71 // Loop variable (indicates which bit to test, start with highest)
72 uint32_t bit = DATA7_MSB;
73 // Make room for CRC value
74 value = value << CRC7_WIDTH;
75 bit = bit << CRC7_WIDTH;
76 polynomial = polynomial << CRC7_WIDTH;
77 // Insert initial vector
78 value |= CRC7_IVEC;
79 // Apply division until all bits done
80 while (bit & (DATA7_MASK << CRC7_WIDTH)) {
81 if (bit & value)
82 value ^= polynomial;
83 bit >>= 1;
84 polynomial >>= 1;
85 }
86 return value;
87}
88
90 ESP_LOGCONFIG(TAG, "Setting up ENS210...");
91 uint8_t data[2];
92 uint16_t part_id = 0;
93 // Reset
94 if (!this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80)) {
95 this->write_byte(ENS210_REGISTER_SYS_CTRL, 0x80);
96 this->error_code_ = ENS210_STATUS_I2C_ERROR;
97 this->mark_failed();
98 return;
99 }
100 // Wait to boot after reset
101 delay(ENS210_BOOTING_MS);
102 // Must disable low power to read PART_ID
103 if (!set_low_power_(false)) {
104 // Try to go back to default mode (low power enabled)
105 set_low_power_(true);
106 this->error_code_ = ENS210_STATUS_I2C_ERROR;
107 this->mark_failed();
108 return;
109 }
110 // Read the PART_ID
111 if (!this->read_bytes(ENS210_REGISTER_PART_ID, data, 2)) {
112 // Try to go back to default mode (low power enabled)
113 set_low_power_(true);
114 this->error_code_ = ENS210_STATUS_I2C_ERROR;
115 this->mark_failed();
116 return;
117 }
118 // Pack bytes into partid
119 part_id = data[1] * 256U + data[0] * 1U;
120 // Check expected part id of the ENS210
121 if (part_id != ENS210_PART_ID) {
122 this->error_code_ = ENS210_WRONG_CHIP_ID;
123 this->mark_failed();
124 }
125 // Set default power mode (low power enabled)
126 set_low_power_(true);
127}
128
130 ESP_LOGCONFIG(TAG, "ENS210:");
131 LOG_I2C_DEVICE(this);
132 if (this->is_failed()) {
133 ESP_LOGE(TAG, "%s", LOG_STR_ARG(ens210_status_to_human(this->error_code_)));
134 }
135 LOG_UPDATE_INTERVAL(this);
136 LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
137 LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
138}
139
141
143 // Execute a single measurement
144 if (!this->write_byte(ENS210_REGISTER_SENS_RUN, 0x00)) {
145 ESP_LOGE(TAG, "Starting single measurement failed!");
146 this->status_set_warning();
147 return;
148 }
149 // Trigger measurement
150 if (!this->write_byte(ENS210_REGISTER_SENS_START, 0x03)) {
151 ESP_LOGE(TAG, "Trigger of measurement failed!");
152 this->status_set_warning();
153 return;
154 }
155 // Wait for measurement to complete
156 this->set_timeout("data", uint32_t(ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS), [this]() {
157 int temperature_data, temperature_status, humidity_data, humidity_status;
158 uint8_t data[6];
159 uint32_t h_val_data, t_val_data;
160 // Set default status for early bail out
161 temperature_status = ENS210_STATUS_I2C_ERROR;
162 humidity_status = ENS210_STATUS_I2C_ERROR;
163
164 // Read T_VAL and H_VAL
165 if (!this->read_bytes(ENS210_REGISTER_T_VAL, data, 6)) {
166 ESP_LOGE(TAG, "Communication with ENS210 failed!");
167 this->status_set_warning();
168 return;
169 }
170 // Pack bytes for humidity
171 h_val_data = (uint32_t) ((uint32_t) data[5] << 16 | (uint32_t) data[4] << 8 | (uint32_t) data[3]);
172 // Extract humidity data and update the status
173 extract_measurement_(h_val_data, &humidity_data, &humidity_status);
174
175 if (humidity_status == ENS210_STATUS_OK) {
176 if (this->humidity_sensor_ != nullptr) {
177 float humidity = (humidity_data & 0xFFFF) / 512.0;
178 this->humidity_sensor_->publish_state(humidity);
179 }
180 } else {
181 ESP_LOGW(TAG, "Humidity status failure: %s", LOG_STR_ARG(ens210_status_to_human(humidity_status)));
182 this->status_set_warning();
183 return;
184 }
185 // Pack bytes for temperature
186 t_val_data = (uint32_t) ((uint32_t) data[2] << 16 | (uint32_t) data[1] << 8 | (uint32_t) data[0]);
187 // Extract temperature data and update the status
188 extract_measurement_(t_val_data, &temperature_data, &temperature_status);
189
190 if (temperature_status == ENS210_STATUS_OK) {
191 if (this->temperature_sensor_ != nullptr) {
192 // Temperature in Celsius
193 float temperature = (temperature_data & 0xFFFF) / 64.0 - 27315L / 100.0;
194 this->temperature_sensor_->publish_state(temperature);
195 }
196 } else {
197 ESP_LOGW(TAG, "Temperature status failure: %s", LOG_STR_ARG(ens210_status_to_human(temperature_status)));
198 }
199 });
200}
201
202// Extracts measurement 'data' and 'status' from a 'val' obtained from measurement.
203void ENS210Component::extract_measurement_(uint32_t val, int *data, int *status) {
204 *data = (val >> 0) & 0xffff;
205 int valid = (val >> 16) & 0x1;
206 uint32_t crc = (val >> 17) & 0x7f;
207 uint32_t payload = (val >> 0) & 0x1ffff;
208 // Check CRC
209 uint8_t crc_ok = crc7(payload) == crc;
210
211 if (!crc_ok) {
213 } else if (!valid) {
215 } else {
217 }
218}
219
220// Sets ENS210 to low (true) or high (false) power. Returns false on I2C problems.
222 uint8_t low_power_cmd = enable ? 0x01 : 0x00;
223 ESP_LOGD(TAG, "Enable low power: %s", enable ? "true" : "false");
224 bool result = this->write_byte(ENS210_REGISTER_SYS_CTRL, low_power_cmd);
225 delay(ENS210_BOOTING_MS);
226 return result;
227}
228
229} // namespace ens210
230} // namespace esphome
uint8_t status
Definition bl0942.h:8
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message="unspecified")
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.cpp:72
float get_setup_priority() const override
Definition ens210.cpp:140
void extract_measurement_(uint32_t val, int *data, int *status)
Definition ens210.cpp:203
sensor::Sensor * humidity_sensor_
Definition ens210.h:35
bool set_low_power_(bool enable)
Definition ens210.cpp:221
enum esphome::ens210::ENS210Component::ErrorCode ENS210_STATUS_OK
sensor::Sensor * temperature_sensor_
Definition ens210.h:34
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition i2c.h:266
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:216
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
mopeka_std_values val[4]
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint16_t temperature
Definition sun_gtil2.cpp:12