ESPHome  2024.3.1
sds011.cpp
Go to the documentation of this file.
1 #include "sds011.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace sds011 {
6 
7 static const char *const TAG = "sds011";
8 
9 static const uint8_t SDS011_MSG_REQUEST_LENGTH = 19;
10 static const uint8_t SDS011_MSG_RESPONSE_LENGTH = 10;
11 static const uint8_t SDS011_DATA_REQUEST_LENGTH = 15;
12 static const uint8_t SDS011_DATA_RESPONSE_LENGTH = 6;
13 static const uint8_t SDS011_MSG_HEAD = 0xaa;
14 static const uint8_t SDS011_MSG_TAIL = 0xab;
15 static const uint8_t SDS011_COMMAND_ID_REQUEST = 0xb4;
16 static const uint8_t SDS011_COMMAND_ID_RESPONSE = 0xc5;
17 static const uint8_t SDS011_COMMAND_ID_DATA = 0xc0;
18 static const uint8_t SDS011_COMMAND_REPORT_MODE = 0x02;
19 static const uint8_t SDS011_COMMAND_QUERY_DATA = 0x04;
20 static const uint8_t SDS011_COMMAND_SET_DEVICE_ID = 0x05;
21 static const uint8_t SDS011_COMMAND_SLEEP = 0x06;
22 static const uint8_t SDS011_COMMAND_FIRMWARE = 0x07;
23 static const uint8_t SDS011_COMMAND_PERIOD = 0x08;
24 static const uint8_t SDS011_GET_MODE = 0x00;
25 static const uint8_t SDS011_SET_MODE = 0x01;
26 static const uint8_t SDS011_MODE_REPORT_ACTIVE = 0x00;
27 static const uint8_t SDS011_MODE_REPORT_QUERY = 0x01;
28 static const uint8_t SDS011_MODE_SLEEP = 0x00;
29 static const uint8_t SDS011_MODE_WORK = 0x01;
30 
32  if (this->rx_mode_only_) {
33  // In RX-only mode we do not setup the sensor, it is assumed to be setup
34  // already
35  return;
36  }
37  uint8_t command_data[SDS011_DATA_REQUEST_LENGTH] = {0};
38  command_data[0] = SDS011_COMMAND_REPORT_MODE;
39  command_data[1] = SDS011_SET_MODE;
40  command_data[2] = SDS011_MODE_REPORT_ACTIVE;
41  command_data[13] = 0xff;
42  command_data[14] = 0xff;
43  this->sds011_write_command_(command_data);
44 
45  command_data[0] = SDS011_COMMAND_PERIOD;
46  command_data[1] = SDS011_SET_MODE;
47  command_data[2] = this->update_interval_min_;
48  command_data[13] = 0xff;
49  command_data[14] = 0xff;
50  this->sds011_write_command_(command_data);
51 }
52 
53 void SDS011Component::set_working_state(bool working_state) {
54  if (this->rx_mode_only_) {
55  // In RX-only mode we do not setup the sensor, it is assumed to be setup
56  // already
57  return;
58  }
59  uint8_t command_data[SDS011_DATA_REQUEST_LENGTH] = {0};
60  command_data[0] = SDS011_COMMAND_SLEEP;
61  command_data[1] = SDS011_SET_MODE;
62  command_data[2] = working_state ? SDS011_MODE_WORK : SDS011_MODE_SLEEP;
63  command_data[13] = 0xff;
64  command_data[14] = 0xff;
65  this->sds011_write_command_(command_data);
66 }
67 
69  ESP_LOGCONFIG(TAG, "SDS011:");
70  ESP_LOGCONFIG(TAG, " Update Interval: %u min", this->update_interval_min_);
71  ESP_LOGCONFIG(TAG, " RX-only mode: %s", ONOFF(this->rx_mode_only_));
72  LOG_SENSOR(" ", "PM2.5", this->pm_2_5_sensor_);
73  LOG_SENSOR(" ", "PM10.0", this->pm_10_0_sensor_);
74  this->check_uart_settings(9600);
75 }
76 
78  const uint32_t now = millis();
79  if ((now - this->last_transmission_ >= 500) && this->data_index_) {
80  // last transmission too long ago. Reset RX index.
81  ESP_LOGV(TAG, "Last transmission too long ago. Reset RX index.");
82  this->data_index_ = 0;
83  }
84 
85  if (this->available() == 0) {
86  return;
87  }
88 
89  this->last_transmission_ = now;
90  while (this->available() != 0) {
91  this->read_byte(&this->data_[this->data_index_]);
92  auto check = this->check_byte_();
93  if (!check.has_value()) {
94  // finished
95  this->parse_data_();
96  this->data_index_ = 0;
97  } else if (!*check) {
98  // wrong data
99  ESP_LOGV(TAG, "Byte %i of received data frame is invalid.", this->data_index_);
100  this->data_index_ = 0;
101  } else {
102  // next byte
103  this->data_index_++;
104  }
105  }
106 }
107 
109 
110 void SDS011Component::set_rx_mode_only(bool rx_mode_only) { this->rx_mode_only_ = rx_mode_only; }
111 
112 void SDS011Component::sds011_write_command_(const uint8_t *command_data) {
113  this->write_byte(SDS011_MSG_HEAD);
114  this->write_byte(SDS011_COMMAND_ID_REQUEST);
115  this->write_array(command_data, SDS011_DATA_REQUEST_LENGTH);
116  this->write_byte(sds011_checksum_(command_data, SDS011_DATA_REQUEST_LENGTH));
117  this->write_byte(SDS011_MSG_TAIL);
118 }
119 
120 uint8_t SDS011Component::sds011_checksum_(const uint8_t *command_data, uint8_t length) const {
121  uint8_t sum = 0;
122  for (uint8_t i = 0; i < length; i++) {
123  sum += command_data[i];
124  }
125  return sum;
126 }
127 
129  uint8_t index = this->data_index_;
130  uint8_t byte = this->data_[index];
131 
132  if (index == 0) {
133  return byte == SDS011_MSG_HEAD;
134  }
135 
136  if (index == 1) {
137  return byte == SDS011_COMMAND_ID_DATA;
138  }
139 
140  if ((index >= 2) && (index <= 7)) {
141  return true;
142  }
143 
144  if (index == 8) {
145  // checksum is without checksum bytes
146  uint8_t checksum = sds011_checksum_(this->data_ + 2, SDS011_DATA_RESPONSE_LENGTH);
147  if (checksum != byte) {
148  ESP_LOGW(TAG, "SDS011 Checksum doesn't match: 0x%02X!=0x%02X", byte, checksum);
149  return false;
150  }
151  return true;
152  }
153 
154  if (index == 9) {
155  if (byte != SDS011_MSG_TAIL) {
156  return false;
157  }
158  }
159 
160  return {};
161 }
162 
164  this->status_clear_warning();
165  const float pm_2_5_concentration = this->get_16_bit_uint_(2) / 10.0f;
166  const float pm_10_0_concentration = this->get_16_bit_uint_(4) / 10.0f;
167 
168  ESP_LOGD(TAG, "Got PM2.5 Concentration: %.1f µg/m³, PM10.0 Concentration: %.1f µg/m³", pm_2_5_concentration,
169  pm_10_0_concentration);
170  if (pm_2_5_concentration <= 0 && pm_10_0_concentration <= 0) {
171  // not yet any valid data
172  return;
173  }
174  if (this->pm_2_5_sensor_ != nullptr) {
175  this->pm_2_5_sensor_->publish_state(pm_2_5_concentration);
176  }
177  if (this->pm_10_0_sensor_ != nullptr) {
178  this->pm_10_0_sensor_->publish_state(pm_10_0_concentration);
179  }
180 }
181 
182 uint16_t SDS011Component::get_16_bit_uint_(uint8_t start_index) const {
183  return (uint16_t(this->data_[start_index + 1]) << 8) | uint16_t(this->data_[start_index]);
184 }
185 void SDS011Component::set_update_interval_min(uint8_t update_interval_min) {
186  this->update_interval_min_ = update_interval_min;
187 }
188 
189 } // namespace sds011
190 } // namespace esphome
uint8_t sds011_checksum_(const uint8_t *command_data, uint8_t length) const
Definition: sds011.cpp:120
void set_working_state(bool working_state)
Definition: sds011.cpp:53
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
sensor::Sensor * pm_10_0_sensor_
Definition: sds011.h:38
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void write_byte(uint8_t data)
Definition: uart.h:19
float get_setup_priority() const override
Definition: sds011.cpp:108
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
sensor::Sensor * pm_2_5_sensor_
Definition: sds011.h:37
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
void set_update_interval_min(uint8_t update_interval_min)
Definition: sds011.cpp:185
uint16_t get_16_bit_uint_(uint8_t start_index) const
Definition: sds011.cpp:182
bool read_byte(uint8_t *data)
Definition: uart.h:29
void status_clear_warning()
Definition: component.cpp:161
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void dump_config() override
Definition: sds011.cpp:68
uint8_t checksum
Definition: bl0939.h:35
uint16_t length
Definition: tt21100.cpp:12
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
optional< bool > check_byte_() const
Definition: sds011.cpp:128
void set_rx_mode_only(bool rx_mode_only)
Manually set the rx-only mode. Defaults to false.
Definition: sds011.cpp:110
void sds011_write_command_(const uint8_t *command)
Definition: sds011.cpp:112