ESPHome  2024.12.2
senseair.cpp
Go to the documentation of this file.
1 #include "senseair.h"
2 #include "esphome/core/helpers.h"
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace senseair {
7 
8 static const char *const TAG = "senseair";
9 static const uint8_t SENSEAIR_REQUEST_LENGTH = 8;
10 static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13;
11 static const uint8_t SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH = 7;
12 static const uint8_t SENSEAIR_CAL_RESULT_RESPONSE_LENGTH = 7;
13 static const uint8_t SENSEAIR_COMMAND_GET_PPM_STATUS[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6};
14 static const uint8_t SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[] = {0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xC5};
15 static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL[] = {0xFE, 0x06, 0x00, 0x01, 0x7C, 0x06, 0x6C, 0xC7};
16 static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT[] = {0xFE, 0x03, 0x00, 0x00, 0x00, 0x01, 0x90, 0x05};
17 static const uint8_t SENSEAIR_COMMAND_ABC_ENABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0xB4, 0xAC, 0x74}; // 180 hours
18 static const uint8_t SENSEAIR_COMMAND_ABC_DISABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0x00, 0xAC, 0x03};
19 static const uint8_t SENSEAIR_COMMAND_ABC_GET_PERIOD[] = {0xFE, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xA1, 0xC3};
20 
22  uint8_t response[SENSEAIR_PPM_STATUS_RESPONSE_LENGTH];
23  if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM_STATUS, response, SENSEAIR_PPM_STATUS_RESPONSE_LENGTH)) {
24  ESP_LOGW(TAG, "Reading data from SenseAir failed!");
25  this->status_set_warning();
26  return;
27  }
28 
29  if (response[0] != 0xFE || response[1] != 0x04) {
30  ESP_LOGW(TAG, "Invalid preamble from SenseAir! %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x",
31  response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7],
32  response[8], response[9], response[10], response[11], response[12]);
33 
34  this->status_set_warning();
35  while (this->available()) {
36  uint8_t b;
37  if (this->read_byte(&b)) {
38  ESP_LOGV(TAG, " ... %02x", b);
39  } else {
40  ESP_LOGV(TAG, " ... nothing read");
41  }
42  }
43  return;
44  }
45 
46  uint16_t calc_checksum = crc16(response, 11);
47  uint16_t resp_checksum = (uint16_t(response[12]) << 8) | response[11];
48  if (resp_checksum != calc_checksum) {
49  ESP_LOGW(TAG, "SenseAir checksum doesn't match: 0x%02X!=0x%02X", resp_checksum, calc_checksum);
50  this->status_set_warning();
51  return;
52  }
53 
54  this->status_clear_warning();
55  const uint8_t length = response[2];
56  const uint16_t status = (uint16_t(response[3]) << 8) | response[4];
57  const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]);
58 
59  ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status);
60  if (this->co2_sensor_ != nullptr)
61  this->co2_sensor_->publish_state(ppm);
62 }
63 
65  // Responses are just echoes but must be read to clear the buffer
66  ESP_LOGD(TAG, "SenseAir Starting background calibration");
67  uint8_t command_length = sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER) / sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[0]);
68  uint8_t response[command_length];
69  this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, response, command_length);
70  this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, response, command_length);
71 }
72 
74  ESP_LOGD(TAG, "SenseAir Requesting background calibration result");
75  uint8_t response[SENSEAIR_CAL_RESULT_RESPONSE_LENGTH];
76  if (!this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT, response,
77  SENSEAIR_CAL_RESULT_RESPONSE_LENGTH)) {
78  ESP_LOGE(TAG, "Requesting background calibration result from SenseAir failed!");
79  return;
80  }
81 
82  if (response[0] != 0xFE || response[1] != 0x03) {
83  ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2],
84  response[3], response[4], response[5], response[6]);
85  return;
86  }
87 
88  // Check if 5th bit (register CI6) is set
89  ESP_LOGI(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)", (response[4] & 0b100000) != 0 ? "OK" : "NOT_OK",
90  response[0], response[1], response[2], response[3], response[4], response[5], response[6]);
91 }
92 
94  // Response is just an echo but must be read to clear the buffer
95  ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration");
96  uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_ENABLE) / sizeof(SENSEAIR_COMMAND_ABC_ENABLE[0]);
97  uint8_t response[command_length];
98  this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, response, command_length);
99 }
100 
102  // Response is just an echo but must be read to clear the buffer
103  ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration");
104  uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_DISABLE) / sizeof(SENSEAIR_COMMAND_ABC_DISABLE[0]);
105  uint8_t response[command_length];
106  this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, response, command_length);
107 }
108 
110  ESP_LOGD(TAG, "SenseAir Requesting ABC period");
111  uint8_t response[SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH];
112  if (!this->senseair_write_command_(SENSEAIR_COMMAND_ABC_GET_PERIOD, response, SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH)) {
113  ESP_LOGE(TAG, "Requesting ABC period from SenseAir failed!");
114  return;
115  }
116 
117  if (response[0] != 0xFE || response[1] != 0x03) {
118  ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2],
119  response[3], response[4], response[5], response[6]);
120  return;
121  }
122 
123  const uint16_t hours = (uint16_t(response[3]) << 8) | response[4];
124  ESP_LOGD(TAG, "SenseAir Read ABC Period: %u hours", hours);
125 }
126 
127 bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
128  // Verify we have somewhere to store the response
129  if (response == nullptr) {
130  return false;
131  }
132  // Write wake up byte required by some S8 sensor models
133  this->write_byte(0);
134  this->flush();
135  delay(5);
136  this->write_array(command, SENSEAIR_REQUEST_LENGTH);
137 
138  bool ret = this->read_array(response, response_length);
139  this->flush();
140  return ret;
141 }
142 
144  ESP_LOGCONFIG(TAG, "SenseAir:");
145  LOG_SENSOR(" ", "CO2", this->co2_sensor_);
146  this->check_uart_settings(9600);
147 }
148 
149 } // namespace senseair
150 } // namespace esphome
optional< std::array< uint8_t, N > > read_array()
Definition: uart.h:33
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void write_byte(uint8_t data)
Definition: uart.h:19
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
uint16_t crc16(const uint8_t *data, uint16_t len, uint16_t crc, uint16_t reverse_poly, bool refin, bool refout)
Calculate a CRC-16 checksum of data with size len.
Definition: helpers.cpp:111
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition: uart.cpp:13
bool read_byte(uint8_t *data)
Definition: uart.h:29
void status_clear_warning()
Definition: component.cpp:166
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length)
Definition: senseair.cpp:127
uint8_t status
Definition: bl0942.h:74
uint16_t length
Definition: tt21100.cpp:12
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26