ESPHome 2025.5.0
Loading...
Searching...
No Matches
ens160_base.cpp
Go to the documentation of this file.
1// ENS160 sensor with I2C interface from ScioSense
2//
3// Datasheet: https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-7-ENS160-Datasheet.pdf
4//
5// Implementation based on:
6// https://github.com/sciosense/ENS160_driver
7
8#include "ens160_base.h"
9#include "esphome/core/log.h"
10#include "esphome/core/hal.h"
11
12namespace esphome {
13namespace ens160_base {
14
15static const char *const TAG = "ens160";
16
17static const uint8_t ENS160_BOOTING = 10;
18
19static const uint16_t ENS160_PART_ID = 0x0160;
20
21static const uint8_t ENS160_REG_PART_ID = 0x00;
22static const uint8_t ENS160_REG_OPMODE = 0x10;
23static const uint8_t ENS160_REG_CONFIG = 0x11;
24static const uint8_t ENS160_REG_COMMAND = 0x12;
25static const uint8_t ENS160_REG_TEMP_IN = 0x13;
26static const uint8_t ENS160_REG_DATA_STATUS = 0x20;
27static const uint8_t ENS160_REG_DATA_AQI = 0x21;
28static const uint8_t ENS160_REG_DATA_TVOC = 0x22;
29static const uint8_t ENS160_REG_DATA_ECO2 = 0x24;
30
31static const uint8_t ENS160_REG_GPR_READ_0 = 0x48;
32static const uint8_t ENS160_REG_GPR_READ_4 = ENS160_REG_GPR_READ_0 + 4;
33
34static const uint8_t ENS160_COMMAND_NOP = 0x00;
35static const uint8_t ENS160_COMMAND_CLRGPR = 0xCC;
36static const uint8_t ENS160_COMMAND_GET_APPVER = 0x0E;
37
38static const uint8_t ENS160_OPMODE_RESET = 0xF0;
39static const uint8_t ENS160_OPMODE_IDLE = 0x01;
40static const uint8_t ENS160_OPMODE_STD = 0x02;
41
42static const uint8_t ENS160_DATA_STATUS_STATAS = 0x80;
43static const uint8_t ENS160_DATA_STATUS_STATER = 0x40;
44static const uint8_t ENS160_DATA_STATUS_VALIDITY = 0x0C;
45static const uint8_t ENS160_DATA_STATUS_NEWDAT = 0x02;
46static const uint8_t ENS160_DATA_STATUS_NEWGPR = 0x01;
47
48// helps remove reserved bits in aqi data register
49static const uint8_t ENS160_DATA_AQI = 0x07;
50
52 ESP_LOGCONFIG(TAG, "Setting up ENS160...");
53
54 // check part_id
55 uint16_t part_id;
56 if (!this->read_bytes(ENS160_REG_PART_ID, reinterpret_cast<uint8_t *>(&part_id), 2)) {
57 this->error_code_ = COMMUNICATION_FAILED;
58 this->mark_failed();
59 return;
60 }
61 if (part_id != ENS160_PART_ID) {
62 this->error_code_ = INVALID_ID;
63 this->mark_failed();
64 return;
65 }
66
67 // set mode to reset
68 if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_RESET)) {
69 this->error_code_ = WRITE_FAILED;
70 this->mark_failed();
71 return;
72 }
73 delay(ENS160_BOOTING);
74
75 // check status
76 uint8_t status_value;
77 if (!this->read_byte(ENS160_REG_DATA_STATUS, &status_value)) {
78 this->error_code_ = READ_FAILED;
79 this->mark_failed();
80 return;
81 }
82 this->validity_flag_ = static_cast<ValidityFlag>((ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
83
84 if (this->validity_flag_ == INVALID_OUTPUT) {
85 this->error_code_ = VALIDITY_INVALID;
86 this->mark_failed();
87 return;
88 }
89
90 // set mode to idle
91 if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_IDLE)) {
92 this->error_code_ = WRITE_FAILED;
93 this->mark_failed();
94 return;
95 }
96 // clear command
97 if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_NOP)) {
98 this->error_code_ = WRITE_FAILED;
99 this->mark_failed();
100 return;
101 }
102 if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR)) {
103 this->error_code_ = WRITE_FAILED;
104 this->mark_failed();
105 return;
106 }
107
108 // read firmware version
109 if (!this->write_byte(ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER)) {
110 this->error_code_ = WRITE_FAILED;
111 this->mark_failed();
112 return;
113 }
114 uint8_t version_data[3];
115 if (!this->read_bytes(ENS160_REG_GPR_READ_4, version_data, 3)) {
116 this->error_code_ = READ_FAILED;
117 this->mark_failed();
118 return;
119 }
120 this->firmware_ver_major_ = version_data[0];
121 this->firmware_ver_minor_ = version_data[1];
122 this->firmware_ver_build_ = version_data[2];
123
124 // set mode to standard
125 if (!this->write_byte(ENS160_REG_OPMODE, ENS160_OPMODE_STD)) {
126 this->error_code_ = WRITE_FAILED;
127 this->mark_failed();
128 return;
129 }
130
131 // read opmode and check standard mode is achieved before finishing Setup
132 uint8_t op_mode;
133 if (!this->read_byte(ENS160_REG_OPMODE, &op_mode)) {
134 this->error_code_ = READ_FAILED;
135 this->mark_failed();
136 return;
137 }
138
139 if (op_mode != ENS160_OPMODE_STD) {
140 this->error_code_ = STD_OPMODE_FAILED;
141 this->mark_failed();
142 return;
143 }
144}
145
147 uint8_t status_value, data_ready;
148
149 if (!this->read_byte(ENS160_REG_DATA_STATUS, &status_value)) {
150 ESP_LOGW(TAG, "Error reading status register");
151 this->status_set_warning();
152 return;
153 }
154
155 // verbose status logging
156 ESP_LOGV(TAG, "Status: ENS160 STATAS bit 0x%x",
157 (ENS160_DATA_STATUS_STATAS & (status_value)) == ENS160_DATA_STATUS_STATAS);
158 ESP_LOGV(TAG, "Status: ENS160 STATER bit 0x%x",
159 (ENS160_DATA_STATUS_STATER & (status_value)) == ENS160_DATA_STATUS_STATER);
160 ESP_LOGV(TAG, "Status: ENS160 VALIDITY FLAG 0x%02x", (ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
161 ESP_LOGV(TAG, "Status: ENS160 NEWDAT bit 0x%x",
162 (ENS160_DATA_STATUS_NEWDAT & (status_value)) == ENS160_DATA_STATUS_NEWDAT);
163 ESP_LOGV(TAG, "Status: ENS160 NEWGPR bit 0x%x",
164 (ENS160_DATA_STATUS_NEWGPR & (status_value)) == ENS160_DATA_STATUS_NEWGPR);
165
166 data_ready = ENS160_DATA_STATUS_NEWDAT & status_value;
167 this->validity_flag_ = static_cast<ValidityFlag>((ENS160_DATA_STATUS_VALIDITY & status_value) >> 2);
168
169 switch (validity_flag_) {
170 case NORMAL_OPERATION:
171 if (data_ready != ENS160_DATA_STATUS_NEWDAT) {
172 ESP_LOGD(TAG, "ENS160 readings unavailable - Normal Operation but readings not ready");
173 return;
174 }
175 break;
176 case INITIAL_STARTUP:
177 if (!this->initial_startup_) {
178 this->initial_startup_ = true;
179 ESP_LOGI(TAG, "ENS160 readings unavailable - 1 hour startup required after first power on");
180 }
181 return;
182 case WARMING_UP:
183 if (!this->warming_up_) {
184 this->warming_up_ = true;
185 ESP_LOGI(TAG, "ENS160 readings not available yet - Warming up requires 3 minutes");
186 this->send_env_data_();
187 }
188 return;
189 case INVALID_OUTPUT:
190 ESP_LOGE(TAG, "ENS160 Invalid Status - No valid output");
191 this->status_set_warning();
192 return;
193 }
194
195 // read new data
196 uint16_t data_eco2;
197 if (!this->read_bytes(ENS160_REG_DATA_ECO2, reinterpret_cast<uint8_t *>(&data_eco2), 2)) {
198 ESP_LOGW(TAG, "Error reading eCO2 data register");
199 this->status_set_warning();
200 return;
201 }
202 if (this->co2_ != nullptr) {
203 this->co2_->publish_state(data_eco2);
204 }
205
206 uint16_t data_tvoc;
207 if (!this->read_bytes(ENS160_REG_DATA_TVOC, reinterpret_cast<uint8_t *>(&data_tvoc), 2)) {
208 ESP_LOGW(TAG, "Error reading TVOC data register");
209 this->status_set_warning();
210 return;
211 }
212 if (this->tvoc_ != nullptr) {
213 this->tvoc_->publish_state(data_tvoc);
214 }
215
216 uint8_t data_aqi;
217 if (!this->read_byte(ENS160_REG_DATA_AQI, &data_aqi)) {
218 ESP_LOGW(TAG, "Error reading AQI data register");
219 this->status_set_warning();
220 return;
221 }
222 if (this->aqi_ != nullptr) {
223 // remove reserved bits, just in case they are used in future
224 data_aqi = ENS160_DATA_AQI & data_aqi;
225
226 this->aqi_->publish_state(data_aqi);
227 }
228
229 this->status_clear_warning();
230
231 // set temperature and humidity compensation data
232 this->send_env_data_();
233}
234
236 if (this->temperature_ == nullptr && this->humidity_ == nullptr)
237 return;
238
239 float temperature = NAN;
240 if (this->temperature_ != nullptr)
241 temperature = this->temperature_->state;
242
243 if (std::isnan(temperature) || temperature < -40.0f || temperature > 85.0f) {
244 ESP_LOGW(TAG, "Invalid external temperature - compensation values not updated");
245 return;
246 } else {
247 ESP_LOGV(TAG, "External temperature compensation: %.1f°C", temperature);
248 }
249
250 float humidity = NAN;
251 if (this->humidity_ != nullptr)
252 humidity = this->humidity_->state;
253
254 if (std::isnan(humidity) || humidity < 0.0f || humidity > 100.0f) {
255 ESP_LOGW(TAG, "Invalid external humidity - compensation values not updated");
256 return;
257 } else {
258 ESP_LOGV(TAG, "External humidity compensation: %.1f%%", humidity);
259 }
260
261 uint16_t t = (uint16_t) ((temperature + 273.15f) * 64.0f);
262 uint16_t h = (uint16_t) (humidity * 512.0f);
263
264 uint8_t data[4];
265 data[0] = t & 0xff;
266 data[1] = (t >> 8) & 0xff;
267 data[2] = h & 0xff;
268 data[3] = (h >> 8) & 0xff;
269
270 if (!this->write_bytes(ENS160_REG_TEMP_IN, data, 4)) {
271 ESP_LOGE(TAG, "Error writing compensation values");
272 this->status_set_warning();
273 return;
274 }
275}
276
278 ESP_LOGCONFIG(TAG, "ENS160:");
279
280 switch (this->error_code_) {
282 ESP_LOGE(TAG, "Communication failed! Is the sensor connected?");
283 break;
284 case READ_FAILED:
285 ESP_LOGE(TAG, "Error reading from register");
286 break;
287 case WRITE_FAILED:
288 ESP_LOGE(TAG, "Error writing to register");
289 break;
290 case INVALID_ID:
291 ESP_LOGE(TAG, "Sensor reported an invalid ID. Is this a ENS160?");
292 break;
293 case VALIDITY_INVALID:
294 ESP_LOGE(TAG, "Invalid Device Status - No valid output");
295 break;
297 ESP_LOGE(TAG, "Device failed to achieve Standard Operating Mode");
298 break;
299 case NONE:
300 ESP_LOGD(TAG, "Setup successful");
301 break;
302 }
303 ESP_LOGI(TAG, "Firmware Version: %d.%d.%d", this->firmware_ver_major_, this->firmware_ver_minor_,
304 this->firmware_ver_build_);
305
306 LOG_UPDATE_INTERVAL(this);
307 LOG_SENSOR(" ", "CO2 Sensor:", this->co2_);
308 LOG_SENSOR(" ", "TVOC Sensor:", this->tvoc_);
309 LOG_SENSOR(" ", "AQI Sensor:", this->aqi_);
310
311 if (this->temperature_ != nullptr && this->humidity_ != nullptr) {
312 LOG_SENSOR(" ", " Temperature Compensation:", this->temperature_);
313 LOG_SENSOR(" ", " Humidity Compensation:", this->humidity_);
314 } else {
315 ESP_LOGCONFIG(TAG, " Compensation: Not configured");
316 }
317}
318
319} // namespace ens160_base
320} // namespace esphome
uint8_t h
Definition bl0906.h:2
virtual void mark_failed()
Mark this component as failed.
void status_set_warning(const char *message="unspecified")
void status_clear_warning()
virtual bool write_byte(uint8_t a_register, uint8_t data)=0
virtual bool read_byte(uint8_t a_register, uint8_t *data)=0
enum esphome::ens160_base::ENS160Component::ErrorCode NONE
virtual bool write_bytes(uint8_t a_register, uint8_t *data, size_t len)=0
enum esphome::ens160_base::ENS160Component::ValidityFlag validity_flag_
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len)=0
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:131
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:28
uint16_t temperature
Definition sun_gtil2.cpp:12