├── examples ├── sds011-esp32 │ ├── .gitignore │ ├── README.md │ ├── platformio.ini │ └── src │ │ └── main.cpp ├── softwareserial │ └── softwareserial.ino └── mqtt │ └── mqtt.ino ├── doc ├── correction.xlsx ├── Laser_Dust_Sensor_Control_Protocol_V1.3.pdf └── SDS011_laser_PM2.5_sensor_specification-V1.3.pdf ├── library.properties ├── library.json ├── keywords.txt ├── LICENSE ├── src ├── SDS011.h └── SDS011.cpp └── README.md /examples/sds011-esp32/.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | -------------------------------------------------------------------------------- /doc/correction.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertmelis/SDS011/HEAD/doc/correction.xlsx -------------------------------------------------------------------------------- /doc/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertmelis/SDS011/HEAD/doc/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf -------------------------------------------------------------------------------- /doc/SDS011_laser_PM2.5_sensor_specification-V1.3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bertmelis/SDS011/HEAD/doc/SDS011_laser_PM2.5_sensor_specification-V1.3.pdf -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SDS011 2 | version=1.0.0 3 | author=Bert Melis 4 | maintainer=Bert Melis 5 | sentence=Non blocking SDS011 sensor library for ESP8266 6 | paragraph= 7 | category=Sensors 8 | url=https://github.com/bertmelis/SDS011 9 | architectures=esp8266 10 | -------------------------------------------------------------------------------- /examples/sds011-esp32/README.md: -------------------------------------------------------------------------------- 1 | The ESP32 [has three serial ports](https://quadmeup.com/arduino-esp32-and-3-hardware-serial-ports/). This example shows how to connect the SDS011 to a port that is different from the default one, so that we can still print to the default serial port and see what's going on: 2 | 3 | ```command 4 | $ platformio run --target upload && platformio device monitor --port /dev/cu.SLAB_USBtoUART --baud 115200 5 | ``` 6 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SDS011", 3 | "version": "1.0.0", 4 | "keywords": "SDS011, dust, sensor, Arduino, ESP8266", 5 | "description": "Non blocking SDS011 sensor library for ESP8266", 6 | "homepage": "https://github.com/bertmelis/SDS011", 7 | "license": "MIT", 8 | "authors": { 9 | "name": "Bert Melis", 10 | "url": "https://github.com/bertmelis", 11 | "maintainer": true 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/bertmelis/SDS011.git", 16 | "branch": "master" 17 | }, 18 | "frameworks": "arduino", 19 | "platforms": [ 20 | "espressif8266" 21 | ] 22 | } -------------------------------------------------------------------------------- /examples/sds011-esp32/platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32 13 | 14 | [env:esp32] 15 | platform = espressif32 16 | board = esp32doit-devkit-v1 17 | framework = arduino 18 | lib_extra_dirs = ../.. # only required during development 19 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Datatypes (KEYWORD1) 3 | ####################################### 4 | 5 | SDS011 KEYWORD1 6 | 7 | ####################################### 8 | # Methods and Functions (KEYWORD2) 9 | ####################################### 10 | 11 | setup KEYWORD2 12 | onData KEYWORD2 13 | onResponse KEYWORD2 14 | onError KEYWORD2 15 | setReportMode KEYWORD2 16 | setWorkingMode KEYWORD2 17 | setWorkingPeriod KEYWORD2 18 | queryData KEYWORD2 19 | loop KEYWORD2 20 | 21 | ####################################### 22 | # Constants (LITERAL1) 23 | ####################################### 24 | 25 | #yourLITERAL LITERAL1 26 | -------------------------------------------------------------------------------- /examples/softwareserial/softwareserial.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | SoftwareSerial swSerial(D4, D3); 6 | 7 | SDS011 sds011; 8 | 9 | void onSensorData(float pm25Value, float pm10Value) { 10 | Serial.printf("pm2.5: %.1f\n", pm25Value); 11 | Serial.printf("pm10: %.1f\n", pm10Value); 12 | }; 13 | 14 | void onSensorResponse(uint8_t command, uint8_t set, uint8_t result){ 15 | Serial.printf("command %d - set %d: %d\n", command, set, result); 16 | }; 17 | 18 | void onSensorError(int8_t error){ 19 | Serial.printf("error: %d\n", error); 20 | }; 21 | 22 | void setup() { 23 | Serial.begin(74880); 24 | 25 | sds011.setup(&swSerial); 26 | sds011.onData(onSensorData); 27 | sds011.onResponse(onSensorResponse); 28 | sds011.onError(onSensorError); 29 | sds011.setWorkingPeriod(5); 30 | } 31 | 32 | void loop() { 33 | sds011.loop(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/sds011-esp32/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | SDS011 sds011; 5 | 6 | void onSensoirData(float pm25Value, float pm10Value) { 7 | Serial.println("Data arrived: PM2.5 = " + String(pm25Value, 1) + " μg/m³; PM10 = " + String(pm10Value, 1) + " μg/m³"); 8 | }; 9 | 10 | void onSensorResponse(uint8_t command, uint8_t set, uint8_t result) { 11 | Serial.println("Response to command 0x" + String(command, HEX) + " received: 0x" + String(result, HEX)); 12 | }; 13 | 14 | void onSensorError(int8_t error) { 15 | Serial.println("Error occurred: 0x" + String(error, HEX)); 16 | }; 17 | 18 | void setup() { 19 | Serial.begin(115200); 20 | sds011.setup(&Serial1, 14, 12); // Rx on GPIO14; Tx on GPIO12 21 | 22 | sds011.onData(onSensorData); 23 | 24 | sds011.onResponse(onSensorResponse); 25 | 26 | sds011.onError(onSensorError); 27 | 28 | sds011.setWorkingPeriod(1); 29 | Serial.println("Data should appear on the default serial port in less than a minute"); 30 | } 31 | 32 | void loop() { 33 | sds011.loop(); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/mqtt/mqtt.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | const char SSID[] = "My_WiFi"; 8 | const char PASS[] = "My_Pass"; 9 | const IPAddress BROKER = {192, 168, 1, 10}; 10 | 11 | SDS011 sds011; 12 | espMqttClient mqttClient; 13 | Ticker mqttReconnectTimer; 14 | 15 | WiFiEventHandler wifiConnectHandler; 16 | WiFiEventHandler wifiDisconnectHandler; 17 | Ticker wifiReconnectTimer; 18 | 19 | bool connected = false; 20 | 21 | void connectToWifi() { 22 | WiFi.begin(SSID, PASS); 23 | } 24 | 25 | void onWifiConnect(const WiFiEventStationModeGotIP& event) { 26 | connectToMqtt(); 27 | } 28 | 29 | void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { 30 | mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi 31 | wifiReconnectTimer.once(2, connectToWifi); 32 | } 33 | 34 | void connectToMqtt() { 35 | if (!mqttClient.connect()) { 36 | mqttReconnectTimer.once(2, connectToMqtt); 37 | } 38 | 39 | void onMqttConnected(bool sessionPresent) { 40 | connected = true; 41 | } 42 | 43 | void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) { 44 | connected = false; 45 | if (WiFi.isConnected()) { 46 | mqttReconnectTimer.once(2, connectToMqtt); 47 | } 48 | } 49 | 50 | void onSensorData(float pm25Value, float pm10Value) { 51 | if (connected) { 52 | mqttClient.publish("/SENSOR/PM2_5", 1, false, String(pm25Value, 1).c_str()); 53 | mqttClient.publish("/SENSOR/PM10", 1, false, String(pm10Value, 1).c_str()); 54 | } 55 | } 56 | 57 | void onSensorResponse(uint8_t command, uint8_t set, uint8_t result) { 58 | // log to MQTT 59 | } 60 | 61 | void onSensorError(int8_t error){ 62 | // error happened 63 | // -1: CRC error 64 | } 65 | 66 | void setup() { 67 | wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); 68 | wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); 69 | 70 | mqttClient.onConnect(onMqttConnected); 71 | mqttClient.onDisconnect(onMqttDisconnect); 72 | mqttClient.setServer(BROKER, 1883); 73 | 74 | sds011.setup(&Serial); 75 | sds011.onData(onSensorData); 76 | sds011.onResponse(onSensorResponse); 77 | sds011.onError(onSensorResponse); 78 | sds011.setWorkingPeriod(5); 79 | 80 | connectToWifi(); 81 | } 82 | 83 | void loop() { 84 | sds011.loop(); 85 | } 86 | -------------------------------------------------------------------------------- /src/SDS011.h: -------------------------------------------------------------------------------- 1 | /* SDS011 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #pragma once 27 | 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | typedef std::function onDataHandler; // pm2.5, pm10 34 | typedef std::function onResponseHandler; 35 | typedef std::function onErrorHandler; 36 | 37 | class SDS011 { 38 | public: 39 | SDS011(); 40 | ~SDS011(); 41 | void setup(HardwareSerial* serial); 42 | #ifdef ESP32 43 | void setup(HardwareSerial* serial, uint8_t rx_pin, uint8_t tx_pin); 44 | #endif 45 | #ifdef ESP8266 46 | void setup(SoftwareSerial* serial); 47 | #endif 48 | void onData(onDataHandler handler); 49 | void onResponse(onResponseHandler handler); 50 | void onError(onErrorHandler handler); // -1: CRC error 51 | void setReportMode(bool mode); // false: active, true: query 52 | void setWorkingMode(bool mode); // false: sleep, true, work 53 | void setWorkingPeriod(uint8_t period); // period in minutes: work 30 seconds, sleep period*60 - 30 seconds 54 | void queryData(); 55 | void loop(); 56 | 57 | private: 58 | uint8_t _getCRC(uint8_t buff[]); 59 | bool _checkCRC(uint8_t buff[], uint8_t crc); 60 | Stream* _serial; 61 | onDataHandler _onData; 62 | onResponseHandler _onResponse; 63 | onErrorHandler _onError; 64 | uint8_t _rxBuff[10]; 65 | uint8_t _txBuff[19]; 66 | 67 | public: 68 | float correct(float raw, float humidity, float factor, float exp); 69 | enum Correction { 70 | AMSTERDAM, 71 | AMERSFOORT, 72 | VENLO 73 | }; 74 | float correct(float raw, float humidity, Correction correction); 75 | }; 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SDS011 2 | 3 | SDS011 particle matter sensor library for the Arduino framework for ESP8266 and ESP32. 4 | 5 | This is yet another SDS011 library, this time completely non blocking. It does come with a `loop()`-method to poll the serial port. 6 | 7 | ## Installation 8 | 9 | * For Arduino IDE: see [the Arduino Guide](https://www.arduino.cc/en/Guide/Libraries#toc4) 10 | * For Platformio: see the [Platfomio guide](http://docs.platformio.org/en/latest/projectconf/section_env_library.html) 11 | 12 | ## Usage 13 | 14 | ESP8266 only has one full UART port so you may want to use SoftwareSerial on this platform. 15 | 16 | To do something useful you can combine this lib with MQTT (like [espMqttClient](https://github.com/bertmelis/espMqttClient)) as in the example below. 17 | 18 | ```C++ 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | const char SSID[] = "My_WiFi"; 25 | const char PASS[] = "My_Pass"; 26 | const IPAddress BROKER = {192, 168, 1, 10}; 27 | 28 | SDS011 sds011; 29 | espMqttClient mqttClient; 30 | Ticker mqttReconnectTimer; 31 | 32 | WiFiEventHandler wifiConnectHandler; 33 | WiFiEventHandler wifiDisconnectHandler; 34 | Ticker wifiReconnectTimer; 35 | 36 | bool connected = false; 37 | 38 | void connectToWifi() { 39 | WiFi.begin(SSID, PASS); 40 | } 41 | 42 | void onWifiConnect(const WiFiEventStationModeGotIP& event) { 43 | connectToMqtt(); 44 | } 45 | 46 | void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { 47 | mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi 48 | wifiReconnectTimer.once(2, connectToWifi); 49 | } 50 | 51 | void connectToMqtt() { 52 | if (!mqttClient.connect()) { 53 | mqttReconnectTimer.once(2, connectToMqtt); 54 | } 55 | 56 | void onMqttConnected(bool sessionPresent) { 57 | connected = true; 58 | } 59 | 60 | void onMqttDisconnect(espMqttClientTypes::DisconnectReason reason) { 61 | connected = false; 62 | if (WiFi.isConnected()) { 63 | mqttReconnectTimer.once(2, connectToMqtt); 64 | } 65 | } 66 | 67 | void onSensorData(float pm25Value, float pm10Value) { 68 | if (connected) { 69 | mqttClient.publish("/SENSOR/PM2_5", 1, false, String(pm25Value, 1).c_str()); 70 | mqttClient.publish("/SENSOR/PM10", 1, false, String(pm10Value, 1).c_str()); 71 | } 72 | } 73 | 74 | void onSensorResponse(uint8_t command, uint8_t set, uint8_t result) { 75 | // log to MQTT 76 | } 77 | 78 | void onSensorError(int8_t error){ 79 | // error happened 80 | // -1: CRC error 81 | } 82 | 83 | void setup() { 84 | wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); 85 | wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); 86 | 87 | mqttClient.onConnect(onMqttConnected); 88 | mqttClient.onDisconnect(onMqttDisconnect); 89 | mqttClient.setServer(BROKER, 1883); 90 | 91 | sds011.setup(&Serial); 92 | sds011.onData(onSensorData); 93 | sds011.onResponse(onSensorResponse); 94 | sds011.onError(onSensorResponse); 95 | sds011.setWorkingPeriod(5); 96 | 97 | connectToWifi(); 98 | } 99 | 100 | void loop() { 101 | sds011.loop(); 102 | } 103 | ``` 104 | 105 | ## To Do 106 | 107 | - adjust readings based on humidity 108 | - possible timeout after sending a command 109 | - implement missing commands 110 | - multiple sensors (apparently the firmware supports this. However, they implemented hardware using 1-1 UART) ??? 111 | 112 | ## Issues, improvements? 113 | 114 | Please create a ticket or pull request. 115 | -------------------------------------------------------------------------------- /src/SDS011.cpp: -------------------------------------------------------------------------------- 1 | /* SDS011 2 | 3 | Copyright 2018 Bert Melis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | */ 25 | 26 | #include 27 | 28 | #include // for pow() 29 | 30 | SDS011::SDS011() : 31 | _serial(nullptr), 32 | _onData(nullptr), 33 | _onResponse(nullptr), 34 | _onError(nullptr), 35 | _rxBuff{0x00}, 36 | _txBuff{0x00} { 37 | } 38 | 39 | SDS011::~SDS011() { 40 | // currently nothing to do 41 | } 42 | 43 | void SDS011::setup(HardwareSerial* serial) { 44 | _serial = serial; 45 | static_cast(_serial)->begin(9600, SERIAL_8N1); 46 | } 47 | 48 | #ifdef ESP8266 49 | void SDS011::setup(SoftwareSerial* serial) { 50 | _serial = serial; 51 | static_cast(_serial)->begin(9600, SWSERIAL_8N1); 52 | } 53 | #endif 54 | 55 | #ifdef ESP32 56 | void SDS011::setup(HardwareSerial* serial, uint8_t rx_pin, uint8_t tx_pin) { 57 | _serial = serial; 58 | static_cast(_serial)->begin(9600, SERIAL_8N1, rx_pin, tx_pin); 59 | } 60 | #endif 61 | 62 | void SDS011::onData(onDataHandler handler) { 63 | _onData = handler; 64 | } 65 | 66 | void SDS011::onResponse(onResponseHandler handler) { 67 | _onResponse = handler; 68 | } 69 | 70 | void SDS011::onError(onErrorHandler handler) { 71 | _onError = handler; 72 | } 73 | 74 | void SDS011::setReportMode(bool mode) { 75 | memset(_txBuff, 0, sizeof(_txBuff)); 76 | _txBuff[0] = 0xAA; 77 | _txBuff[1] = 0xB4; 78 | _txBuff[2] = 0x02; 79 | _txBuff[3] = 0x01; // set query mode 80 | _txBuff[4] = (mode) ? 0x01 : 0x00; // mode 81 | _txBuff[15] = 0xFF; 82 | _txBuff[16] = 0xFF; 83 | _txBuff[17] = _getCRC(_txBuff); 84 | _txBuff[18] = 0xAB; 85 | _serial->write(_txBuff, sizeof(_txBuff)); 86 | } 87 | 88 | void SDS011::setWorkingMode(bool mode) { 89 | memset(_txBuff, 0, sizeof(_txBuff)); 90 | _txBuff[0] = 0xAA; 91 | _txBuff[1] = 0xB4; 92 | _txBuff[2] = 0x06; 93 | _txBuff[3] = 0x01; // set working mode 94 | _txBuff[4] = (mode) ? 0x01 : 0x00; // mode 95 | _txBuff[15] = 0xFF; 96 | _txBuff[16] = 0xFF; 97 | _txBuff[17] = _getCRC(_txBuff); 98 | _txBuff[18] = 0xAB; 99 | _serial->write(_txBuff, sizeof(_txBuff)); 100 | } 101 | 102 | void SDS011::setWorkingPeriod(uint8_t period) { 103 | memset(_txBuff, 0, sizeof(_txBuff)); 104 | _txBuff[0] = 0xAA; 105 | _txBuff[1] = 0xB4; 106 | _txBuff[2] = 0x08; 107 | _txBuff[3] = 0x01; // set working mode 108 | _txBuff[4] = period; 109 | _txBuff[15] = 0xFF; 110 | _txBuff[16] = 0xFF; 111 | _txBuff[17] = _getCRC(_txBuff); 112 | _txBuff[18] = 0xAB; 113 | _serial->write(_txBuff, sizeof(_txBuff)); 114 | } 115 | 116 | void SDS011::queryData() { 117 | memset(_txBuff, 0, sizeof(_txBuff)); 118 | _txBuff[0] = 0xAA; 119 | _txBuff[1] = 0xB4; 120 | _txBuff[2] = 0x04; 121 | _txBuff[15] = 0xFF; 122 | _txBuff[16] = 0xFF; 123 | _txBuff[17] = _getCRC(_txBuff); 124 | _txBuff[18] = 0xAB; 125 | _serial->write(_txBuff, sizeof(_txBuff)); 126 | } 127 | 128 | void SDS011::loop() { 129 | static uint8_t index = 0; 130 | if (_serial->available()) { // fill rxBuffer 131 | _rxBuff[index] = _serial->read(); 132 | if (_rxBuff[0] == 0xAA) { // advance if HEAD is received 133 | ++index; 134 | } 135 | } 136 | if (_rxBuff[9] == 0xAB) { // process when TAIL is received 137 | if (!_checkCRC(_rxBuff, _rxBuff[8])) { // 1. check CRC 138 | if (_onError) _onError(-1); // 2. signal error on fail 139 | } else { 140 | // 2. check message type 141 | if (_rxBuff[1] == 0xC5) { // 3. response or data? 142 | if (_onResponse) _onResponse(_rxBuff[2], _rxBuff[3], _rxBuff[4]); // 4. signal response 143 | } else { 144 | uint16_t pm2_5_raw = (_rxBuff[3] << 8) + _rxBuff[2]; 145 | float pm2_5 = pm2_5_raw / 10.0; 146 | uint16_t pm10_raw = (_rxBuff[5] << 8) + _rxBuff[4]; 147 | float pm10 = pm10_raw / 10.0; 148 | if (_onData) _onData(pm2_5, pm10); 149 | } 150 | } 151 | // 4. reset 152 | memset(_rxBuff, 0x00, sizeof(_rxBuff)); 153 | index = 0; 154 | } 155 | } 156 | 157 | uint8_t SDS011::_getCRC(uint8_t buff[]) { 158 | uint8_t crc = 0; 159 | for (uint8_t i = 2; i < 17; ++i) { 160 | crc += buff[i]; 161 | } 162 | return crc; 163 | } 164 | 165 | bool SDS011::_checkCRC(uint8_t buff[], uint8_t crc) { 166 | uint8_t crc_calc = 0; 167 | for (uint8_t i = 2; i < 8; ++i) { 168 | crc_calc += buff[i]; 169 | } 170 | return crc == crc_calc; 171 | } 172 | 173 | float SDS011::correct(float pm2_5, float humidity, float factor, float exp) { 174 | if (humidity == 100 || factor == 0) return 0; 175 | float pm2_5_corr = pm2_5 / pow(factor * (100 - humidity), exp); 176 | return pm2_5_corr; 177 | } 178 | 179 | float SDS011::correct(float pm2_5, float humidity, Correction correction) { 180 | float factor = 0.0; 181 | float exponent = 0.0; 182 | switch (correction) { 183 | case AMSTERDAM: 184 | factor = 2.3; 185 | exponent = -0.38; 186 | break; 187 | case AMERSFOORT: 188 | factor = 3.4; 189 | exponent = -0.4; 190 | break; 191 | case VENLO: 192 | factor = 3.9; 193 | exponent = -0.43; 194 | break; 195 | } 196 | return correct(pm2_5, humidity, factor, exponent); 197 | } 198 | --------------------------------------------------------------------------------