ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
senseair.cpp
Go to the documentation of this file.
1#include "senseair.h"
3#include "esphome/core/log.h"
4
6
7ESPHOME_LOG_TAG(TAG, "senseair");
8static const uint8_t SENSEAIR_REQUEST_LENGTH = 8;
9static const uint8_t SENSEAIR_PPM_STATUS_RESPONSE_LENGTH = 13;
10static const uint8_t SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH = 7;
11static const uint8_t SENSEAIR_CAL_RESULT_RESPONSE_LENGTH = 7;
12static const uint8_t SENSEAIR_COMMAND_GET_PPM_STATUS[] = {0xFE, 0x04, 0x00, 0x00, 0x00, 0x04, 0xE5, 0xC6};
13static const uint8_t SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[] = {0xFE, 0x06, 0x00, 0x00, 0x00, 0x00, 0x9D, 0xC5};
14static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL[] = {0xFE, 0x06, 0x00, 0x01, 0x7C, 0x06, 0x6C, 0xC7};
15static const uint8_t SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT[] = {0xFE, 0x03, 0x00, 0x00, 0x00, 0x01, 0x90, 0x05};
16static const uint8_t SENSEAIR_COMMAND_ABC_ENABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0xB4, 0xAC, 0x74}; // 180 hours
17static const uint8_t SENSEAIR_COMMAND_ABC_DISABLE[] = {0xFE, 0x06, 0x00, 0x1F, 0x00, 0x00, 0xAC, 0x03};
18static const uint8_t SENSEAIR_COMMAND_ABC_GET_PERIOD[] = {0xFE, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xA1, 0xC3};
19
21 uint8_t response[SENSEAIR_PPM_STATUS_RESPONSE_LENGTH];
22 if (!this->senseair_write_command_(SENSEAIR_COMMAND_GET_PPM_STATUS, response, SENSEAIR_PPM_STATUS_RESPONSE_LENGTH)) {
23 ESP_LOGW(TAG, "Reading data from SenseAir failed!");
24 this->status_set_warning();
25 return;
26 }
27
28 if (response[0] != 0xFE || response[1] != 0x04) {
29 ESP_LOGW(TAG, "Invalid preamble from SenseAir! %02x%02x%02x%02x %02x%02x%02x%02x %02x%02x%02x%02x %02x",
30 response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7],
31 response[8], response[9], response[10], response[11], response[12]);
32
33 this->status_set_warning();
34 while (this->available()) {
35 uint8_t b;
36 if (this->read_byte(&b)) {
37 ESP_LOGV(TAG, " ... %02x", b);
38 } else {
39 ESP_LOGV(TAG, " ... nothing read");
40 }
41 }
42 return;
43 }
44
45 uint16_t calc_checksum = crc16(response, 11);
46 uint16_t resp_checksum = encode_value_le<uint16_t>(&response[11]);
47 if (!this->status_comm_result(resp_checksum == calc_checksum)) {
48 ESP_LOGW(TAG, "SenseAir checksum doesn't match: 0x%02X!=0x%02X", resp_checksum, calc_checksum);
49 return;
50 }
51
52 const uint8_t length = response[2];
53 const uint16_t status = encode_uint16(response[3], response[4]);
54 const uint16_t ppm = encode_uint16(response[length + 1], response[length + 2]);
55
56 ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status);
57 if (ppm == 0 && (status & SenseAirStatus::OUT_OF_RANGE_ERROR) != 0) {
58 ESP_LOGD(TAG, "Discarding 0 ppm reading with out-of-range status.");
59 return;
60 }
61 if (this->co2_sensor_ != nullptr)
62 this->co2_sensor_->publish_state(ppm);
63}
64
66 // Responses are just echoes but must be read to clear the buffer
67 ESP_LOGD(TAG, "SenseAir Starting background calibration");
68 uint8_t command_length = sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER) / sizeof(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER[0]);
69 uint8_t response[command_length];
70 this->senseair_write_command_(SENSEAIR_COMMAND_CLEAR_ACK_REGISTER, response, command_length);
71 this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL, response, command_length);
72}
73
75 ESP_LOGD(TAG, "SenseAir Requesting background calibration result");
76 uint8_t response[SENSEAIR_CAL_RESULT_RESPONSE_LENGTH];
77 if (!this->senseair_write_command_(SENSEAIR_COMMAND_BACKGROUND_CAL_RESULT, response,
78 SENSEAIR_CAL_RESULT_RESPONSE_LENGTH)) {
79 ESP_LOGE(TAG, "Requesting background calibration result from SenseAir failed!");
80 return;
81 }
82
83 if (response[0] != 0xFE || response[1] != 0x03) {
84 ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2],
85 response[3], response[4], response[5], response[6]);
86 return;
87 }
88
89 // Check if 5th bit (register CI6) is set
90 ESP_LOGI(TAG, "SenseAir Result=%s (%02x%02x%02x %02x%02x %02x%02x)",
91 (response[4] & 0b100000) != 0 ? LOG_STR_LITERAL("OK") : LOG_STR_LITERAL("NOT_OK"), response[0], response[1],
92 response[2], response[3], response[4], response[5], response[6]);
93}
94
96 // Response is just an echo but must be read to clear the buffer
97 ESP_LOGD(TAG, "SenseAir Enabling automatic baseline calibration");
98 uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_ENABLE) / sizeof(SENSEAIR_COMMAND_ABC_ENABLE[0]);
99 uint8_t response[command_length];
100 this->senseair_write_command_(SENSEAIR_COMMAND_ABC_ENABLE, response, command_length);
101}
102
104 // Response is just an echo but must be read to clear the buffer
105 ESP_LOGD(TAG, "SenseAir Disabling automatic baseline calibration");
106 uint8_t command_length = sizeof(SENSEAIR_COMMAND_ABC_DISABLE) / sizeof(SENSEAIR_COMMAND_ABC_DISABLE[0]);
107 uint8_t response[command_length];
108 this->senseair_write_command_(SENSEAIR_COMMAND_ABC_DISABLE, response, command_length);
109}
110
112 ESP_LOGD(TAG, "SenseAir Requesting ABC period");
113 uint8_t response[SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH];
114 if (!this->senseair_write_command_(SENSEAIR_COMMAND_ABC_GET_PERIOD, response, SENSEAIR_ABC_PERIOD_RESPONSE_LENGTH)) {
115 ESP_LOGE(TAG, "Requesting ABC period from SenseAir failed!");
116 return;
117 }
118
119 if (response[0] != 0xFE || response[1] != 0x03) {
120 ESP_LOGE(TAG, "Invalid reply from SenseAir! %02x%02x%02x %02x%02x %02x%02x", response[0], response[1], response[2],
121 response[3], response[4], response[5], response[6]);
122 return;
123 }
124
125 const uint16_t hours = (uint16_t(response[3]) << 8) | response[4];
126 ESP_LOGD(TAG, "SenseAir Read ABC Period: %u hours", hours);
127}
128
129bool SenseAirComponent::senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length) {
130 // Verify we have somewhere to store the response
131 if (response == nullptr) {
132 return false;
133 }
134 // Write wake up byte required by some S8 sensor models
135 this->write_byte(0);
136 this->flush();
137 delay(5);
138 this->write_array(command, SENSEAIR_REQUEST_LENGTH);
139
140 bool ret = this->read_array(response, response_length);
141 this->flush();
142 return ret;
143}
144
146 ESP_LOGCONFIG(TAG, "SenseAir:");
147 LOG_SENSOR(" ", "CO2", this->co2_sensor_);
148}
149
150} // namespace esphome::senseair
uint8_t status
Definition bl0942.h:8
bool status_comm_result(bool ok)
Clears the warning when ok, else sets it with message (default "Communication failed"); returns ok.
bool senseair_write_command_(const uint8_t *command, uint8_t *response, uint8_t response_length)
Definition senseair.cpp:129
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
UARTFlushResult flush()
Definition uart.h:96
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:43
bool read_byte(uint8_t *data)
Definition uart.h:39
void write_byte(uint8_t data)
Definition uart.h:22
void write_array(const uint8_t *data, size_t len)
Definition uart.h:30
int ret
ESPHOME_LOG_TAG(TAG, "senseair")
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:95
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
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:998
void HOT delay(uint32_t ms)
Definition hal.cpp:85