ESPHome  2024.11.1
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 
13 namespace esphome {
14 namespace ens210 {
15 
16 static const char *const TAG = "ens210";
17 
18 // ENS210 chip constants
19 static const uint8_t ENS210_BOOTING_MS = 2; // Booting time in ms (also after reset, or going to high power)
20 static const uint8_t ENS210_SINGLE_MEASURMENT_CONVERSION_TIME_MS =
21  130; // Conversion time in ms for single shot T/H measurement
22 static const uint16_t ENS210_PART_ID = 0x0210; // The expected part id of the ENS210
23 
24 // Addresses of the ENS210 registers
25 static const uint8_t ENS210_REGISTER_PART_ID = 0x00;
26 static const uint8_t ENS210_REGISTER_UID = 0x04;
27 static const uint8_t ENS210_REGISTER_SYS_CTRL = 0x10;
28 static const uint8_t ENS210_REGISTER_SYS_STAT = 0x11;
29 static const uint8_t ENS210_REGISTER_SENS_RUN = 0x21;
30 static const uint8_t ENS210_REGISTER_SENS_START = 0x22;
31 static const uint8_t ENS210_REGISTER_SENS_STOP = 0x23;
32 static const uint8_t ENS210_REGISTER_SENS_STAT = 0x24;
33 static const uint8_t ENS210_REGISTER_T_VAL = 0x30;
34 static const uint8_t ENS210_REGISTER_H_VAL = 0x33;
35 
36 // CRC-7 constants
37 static const uint8_t CRC7_WIDTH = 7; // A 7 bits CRC has polynomial of 7th order, which has 8 terms
38 static const uint8_t CRC7_POLY = 0x89; // The 8 coefficients of the polynomial
39 static const uint8_t CRC7_IVEC = 0x7F; // Initial vector has all 7 bits high
40 
41 // Payload data constants
42 static const uint8_t DATA7_WIDTH = 17;
43 static const uint32_t DATA7_MASK = ((1UL << DATA7_WIDTH) - 1); // 0b 0 1111 1111 1111 1111
44 static 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
47 static 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
66 static 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.
203 void 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) {
212  *status = ENS210_STATUS_CRC_ERROR;
213  } else if (!valid) {
214  *status = ENS210_STATUS_INVALID;
215  } else {
216  *status = ENS210_STATUS_OK;
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
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
bool is_failed() const
Definition: component.cpp:143
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:69
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:212
mopeka_std_values val[4]
float get_setup_priority() const override
Definition: ens210.cpp:140
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint16_t temperature
Definition: sun_gtil2.cpp:26
sensor::Sensor * humidity_sensor_
Definition: ens210.h:35
sensor::Sensor * temperature_sensor_
Definition: ens210.h:34
bool set_low_power_(bool enable)
Definition: ens210.cpp:221
uint8_t status
Definition: bl0942.h:74
enum esphome::ens210::ENS210Component::ErrorCode ENS210_STATUS_OK
bool write_byte(uint8_t a_register, uint8_t data, bool stop=true)
Definition: i2c.h:262
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void extract_measurement_(uint32_t val, int *data, int *status)
Definition: ens210.cpp:203
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26