diff --git a/platformio.ini b/platformio.ini index 0382d0bf1..4542e11d9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -99,6 +99,8 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ ; platform = https://github.com/pioarduino/platform-espressif32#develop lib_ignore = WiFiNINA, WiFi101, OneWire monitor_filters = esp32_exception_decoder, time +debug_tool = esp-builtin +debug_init_break = tbreak ws_uart_drv_pm25aqi ; Common build environment for ESP8266 platform [common:esp8266] @@ -220,7 +222,7 @@ extends = common:esp32 board = adafruit_feather_esp32s3 build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S3 -DBOARD_HAS_PSRAM ;set partition to tinyuf2-partitions-4MB.csv as of idf 5.1 -board_build.partitions = tinyuf2-partitions-4MB.csv +board_build.partitions = tinyuf2-partitions-4MB-noota.csv extra_scripts = pre:rename_usb_config.py ; Adafruit Feather ESP32-S3 NO PSRAM diff --git a/pm25.cpp.txt b/pm25.cpp.txt new file mode 100644 index 000000000..a0976c353 --- /dev/null +++ b/pm25.cpp.txt @@ -0,0 +1,341 @@ +/*! + * @file Adafruit_PM25AQI.cpp + * + * @mainpage Adafruit PM2.5 air quality sensor driver + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's PM2.5 AQI driver for the + * Arduino platform. It is designed specifically to work with the + * Adafruit PM2.5 Air quality sensors: http://www.adafruit.com/products/4632 + * + * This library also works with the Cubic PM1006 UART Air Quality Sensor. + * + * These sensors use I2C or UART to communicate. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing + * products from Adafruit! + * + * + * @section author Author + * Written by Ladyada for Adafruit Industries. + * Modified by Brent Rubell for Adafruit Industries for use with Cubic PM1006 + * Air Quality Sensor. + * + * @section license License + * BSD license, all text here must be included in any redistribution. + * + */ +// #include +#include "Adafruit_PM25AQI.h" +#include + +/*! + * @brief Instantiates a new PM25AQI class + */ +Adafruit_PM25AQI::Adafruit_PM25AQI() {} + +/*! + * @brief Setups the hardware and detects a valid PMSA003I. Initializes I2C. + * @param theWire + * Optional pointer to I2C interface, otherwise use Wire + * @return True if PMSA003I found on I2C, False if something went wrong! + */ +bool Adafruit_PM25AQI::begin_I2C(TwoWire *theWire) { + if (!i2c_dev) { + i2c_dev = new Adafruit_I2CDevice(PMSA003I_I2CADDR_DEFAULT, theWire); + } + + if (!i2c_dev->begin()) { + return false; + } + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param theSerial + * Pointer to Stream (HardwareSerial/SoftwareSerial) interface + * @return True + */ +bool Adafruit_PM25AQI::begin_UART(Stream *theSerial) { + serial_dev = theSerial; + + return true; +} + +/*! + * @brief Setups the hardware and detects a valid UART PM2.5 + * @param data + * Pointer to PM25_AQI_Data that will be filled by read()ing + * @return True on successful read, false if timed out or bad data + */ +bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) { + uint8_t buffer[32]; + size_t bufLen = sizeof(buffer); + uint16_t sum = 0; + uint8_t csum = 0; + bool is_pm1006 = false; + + if (!data) { + return false; + } + + if (i2c_dev) { // ok using i2c? + if (!i2c_dev->read(buffer, 32)) { + return false; + } + } else if (serial_dev) { // ok using uart + if (!serial_dev->available()) { + Serial.println("PM25: Serial data unavailable"); + return false; + } + + int skipped = 0; + while ((skipped < 32) && (serial_dev->peek() != 0x42) && + (serial_dev->peek() != 0x16)) { + serial_dev->read(); + skipped++; + if (!serial_dev->available()) { + Serial.println("PM25: Serial data unavailable part way through"); + return false; + } + } + + // Check for the start character in the stream for both sensors + if ((serial_dev->peek() != 0x42) && (serial_dev->peek() != 0x16)) { + Serial.println("PM25: Serial peek failed"); + serial_dev->read(); + return false; + } + + // Are we using the Cubic PM1006 sensor? + if (serial_dev->peek() == 0x16) { + is_pm1006 = true; // Set flag to indicate we are using the PM1006 + bufLen = + 20; // Reduce buffer read length to 20 bytes. Last 12 bytes ignored. + } + + // Are there enough bytes to read from? + if (serial_dev->available() < bufLen) { + Serial.println("PM25: Serial data too short"); + return false; + } + + // Read all available bytes from the serial stream + serial_dev->readBytes(buffer, bufLen); + } else { + return false; + } + Serial.println("PM25: Serial data fetch done"); + + // Validate start byte is correct if using Adafruit PM sensors + if ((!is_pm1006 && (buffer[0] != 0x42 || buffer[1] != 0x4d))) { + Serial.println("PM25: Serial data start incorrect (not pm1006)"); + + return false; + } + + // Validate start header is correct if using Cubic PM1006 sensor + if (is_pm1006 && + (buffer[0] != 0x16 || buffer[1] != 0x11 || buffer[2] != 0x0B)) { + Serial.println("PM25: Serial data start incorrect (pm1006)"); + + return false; + } + + // Calculate checksum + if (!is_pm1006) { + for (uint8_t i = 0; i < 30; i++) { + sum += buffer[i]; + } + } else { + for (uint8_t i = 0; i < bufLen; i++) { + csum += buffer[i]; + } + } + + // Since header and checksum are OK, parse data from the buffer + if (!is_pm1006) { + // The data comes in endian'd, this solves it so it works on all platforms + uint16_t buffer_u16[15]; + for (uint8_t i = 0; i < 15; i++) { + buffer_u16[i] = buffer[2 + i * 2 + 1]; + buffer_u16[i] += (buffer[2 + i * 2] << 8); + } + // put it into a nice struct :) + memcpy((void *)data, (void *)buffer_u16, 30); + } else { + // Cubic PM1006 sensor only produces a pm25_env reading + data->pm25_env = (buffer[5] << 8) | buffer[6]; + data->checksum = sum; + } + + // Validate checksum + if ((is_pm1006 && csum != 0) || (!is_pm1006 && sum != data->checksum)) { + Serial.println("PM25: Serial data checksum incorrect"); + return false; + } + + Serial.println("PM25: Serial data checks complete, about to calculate AQIs"); + + // convert concentration to AQI + data->aqi_pm25_us = pm25_aqi_us(data->pm25_env); + data->aqi_pm25_china = pm25_aqi_china(data->pm25_env); + data->aqi_pm100_us = pm100_aqi_us(data->pm100_env); + data->aqi_pm100_china = pm100_aqi_china(data->pm100_env); + Serial.println("PM25: Calculated AQIs, returning TRUE for read()"); + + // success! + return true; +} + +/*! + * @brief Get AQI of PM2.5 in US standard + * @param concentration + * the environmental concentration of pm2.5 in ug/m3 + * @return AQI number. 0 to 500 for valid calculation. 99999 for out of range. + */ +uint16_t Adafruit_PM25AQI::pm25_aqi_us(float concentration) { + float c; + float AQI; + c = (floor(10 * concentration)) / 10; + if (c < 0) + AQI = 0; + else if (c >= 0 && c < 12.1f) { + AQI = linear(50, 0, 12, 0, c); + } else if (c >= 12.1f && c < 35.5f) { + AQI = linear(100, 51, 35.4f, 12.1f, c); + } else if (c >= 35.5f && c < 55.5f) { + AQI = linear(150, 101, 55.4f, 35.5f, c); + } else if (c >= 55.5f && c < 150.5f) { + AQI = linear(200, 151, 150.4f, 55.5f, c); + } else if (c >= 150.5f && c < 250.5f) { + AQI = linear(300, 201, 250.4f, 150.5f, c); + } else if (c >= 250.5f && c < 350.5f) { + AQI = linear(400, 301, 350.4f, 250.5f, c); + } else if (c >= 350.5f && c < 500.5f) { + AQI = linear(500, 401, 500.4f, 350.5f, c); + } else { + AQI = 99999; // + } + return round(AQI); +} + +/*! + * @brief Get AQI of PM10 in US standard + * @param concentration + * the environmental concentration of pm10 in ug/m3 + * @return AQI number. 0 to 500 for valid calculation. 99999 for out of range. + */ +uint16_t Adafruit_PM25AQI::pm100_aqi_us(float concentration) { + float c; + float AQI; + c = concentration; + if (c < 0) + AQI = 0; + else if (c < 55) { + AQI = linear(50, 0, 55, 0, c); + } else if (c < 155) { + AQI = linear(100, 51, 155, 55, c); + } else if (c < 255) { + AQI = linear(150, 101, 255, 155, c); + } else if (c < 355) { + AQI = linear(200, 151, 355, 255, c); + } else if (c < 425) { + AQI = linear(300, 201, 425, 355, c); + } else if (c < 505) { + AQI = linear(400, 301, 505, 425, c); + } else if (c < 605) { + AQI = linear(500, 401, 605, 505, c); + } else { + AQI = 99999; // + } + return round(AQI); +} + +/*! + * @brief Get AQI of PM2.5 in China standard + * @param concentration + * the environmental concentration of pm2.5 in ug/m3 + * @return AQI number. 0 to 500 for valid calculation. 99999 for out of range. + */ +uint16_t Adafruit_PM25AQI::pm25_aqi_china(float concentration) { + float c; + float AQI; + c = concentration; + if (c < 0) + AQI = 0; + else if (c <= 35) { + AQI = linear(50, 0, 35, 0, c); + } else if (c <= 75) { + AQI = linear(100, 51, 75, 35, c); + } else if (c <= 115) { + AQI = linear(150, 101, 115, 75, c); + } else if (c <= 150) { + AQI = linear(200, 151, 150, 115, c); + } else if (c <= 250) { + AQI = linear(300, 201, 250, 150, c); + } else if (c <= 350) { + AQI = linear(400, 301, 350, 250, c); + } else if (c <= 500) { + AQI = linear(500, 401, 500, 350, c); + } else { + AQI = 99999; // + } + return round(AQI); +} + +/*! + * @brief Get AQI of PM10 in China standard + * @param concentration + * the environmental concentration of pm10 in ug/m3 + * @return AQI number. 0 to 500 for valid calculation. 99999 for out of range. + */ +uint16_t Adafruit_PM25AQI::pm100_aqi_china(float concentration) { + float c; + float AQI; + c = concentration; + if (c < 0) + AQI = 0; + else if (c <= 50) { + AQI = linear(50, 0, 50, 0, c); + } else if (c <= 150) { + AQI = linear(100, 51, 150, 50, c); + } else if (c <= 250) { + AQI = linear(150, 101, 250, 150, c); + } else if (c <= 350) { + AQI = linear(200, 151, 350, 250, c); + } else if (c <= 420) { + AQI = linear(300, 201, 420, 350, c); + } else if (c <= 500) { + AQI = linear(400, 301, 500, 420, c); + } else if (c <= 600) { + AQI = linear(500, 401, 600, 500, c); + } else { + AQI = 99999; // + } + return round(AQI); +} + +/*! + * @brief Linearly map a concentration value to its AQI level + * @param aqi_high max aqi of the calculating range + * @param aqi_low min aqi of the calculating range + * @param conc_high max concentration value (ug/m3) of the calculating range + * @param conc_low min concentration value (ug/m3) of the calculating range + * @param concentration + * the concentration value to be calculated + * @return Calculated AQI value + */ +float Adafruit_PM25AQI::linear(uint16_t aqi_high, uint16_t aqi_low, + float conc_high, float conc_low, + float concentration) { + float f; + f = ((concentration - conc_low) / (conc_high - conc_low)) * + (aqi_high - aqi_low) + + aqi_low; + return f; +} \ No newline at end of file diff --git a/src/components/uart/drivers/ws_uart_drv_pm25aqi.h b/src/components/uart/drivers/ws_uart_drv_pm25aqi.h index 4eb4c0c92..100104bde 100644 --- a/src/components/uart/drivers/ws_uart_drv_pm25aqi.h +++ b/src/components/uart/drivers/ws_uart_drv_pm25aqi.h @@ -102,35 +102,43 @@ class ws_uart_drv_pm25aqi : public ws_uart_drv { */ /*******************************************************************************/ bool read_data() override { - Serial.println("[UART, PM25] Reading data..."); + WS_DEBUG_PRINTLN("[UART, PM25] Reading data..."); // Attempt to read the PM2.5 Sensor - if (!_aqi->read(&_data)) { - Serial.println("[UART, PM25] Data not available."); - delay(500); - return false; + bool result; + RETRY_FUNCTION_UNTIL_TIMEOUT(_aqi->read, bool, result, + // lambda function to check if data is available + [](bool res) { + WS_DEBUG_PRINT("UART IN TIMED CHECK, RES: "); WS_DEBUG_PRINTLN(res); + return res==true; } + , 5000, 250, &_data); + WS_DEBUG_PRINT("RESULT VAR: "); WS_DEBUG_PRINTLN(result); + + if (!result) { + WS_DEBUG_PRINTLN("[UART, PM25] Data not available."); + return result; } - Serial.println("[UART, PM25] Read data OK"); - Serial.println(); - Serial.println(F("---------------------------------------")); - Serial.println(F("Concentration Units (standard)")); - Serial.println(F("---------------------------------------")); - Serial.print(F("PM 1.0: ")); - Serial.print(_data.pm10_standard); - Serial.print(F("\t\tPM 2.5: ")); - Serial.print(_data.pm25_standard); - Serial.print(F("\t\tPM 10: ")); - Serial.println(_data.pm100_standard); - Serial.println(F("Concentration Units (environmental)")); - Serial.println(F("---------------------------------------")); - Serial.print(F("PM 1.0: ")); - Serial.print(_data.pm10_env); - Serial.print(F("\t\tPM 2.5: ")); - Serial.print(_data.pm25_env); - Serial.print(F("\t\tPM 10: ")); - Serial.println(_data.pm100_env); - Serial.println(F("---------------------------------------")); - - return true; + WS_DEBUG_PRINTLN("[UART, PM25] Read data OK"); + WS_DEBUG_PRINTLN(); + WS_DEBUG_PRINTLN(F("---------------------------------------")); + WS_DEBUG_PRINTLN(F("Concentration Units (standard)")); + WS_DEBUG_PRINTLN(F("---------------------------------------")); + WS_DEBUG_PRINT(F("PM 1.0: ")); + WS_DEBUG_PRINT(_data.pm10_standard); + WS_DEBUG_PRINT(F("\t\tPM 2.5: ")); + WS_DEBUG_PRINT(_data.pm25_standard); + WS_DEBUG_PRINT(F("\t\tPM 10: ")); + WS_DEBUG_PRINTLN(_data.pm100_standard); + WS_DEBUG_PRINTLN(F("Concentration Units (environmental)")); + WS_DEBUG_PRINTLN(F("---------------------------------------")); + WS_DEBUG_PRINT(F("PM 1.0: ")); + WS_DEBUG_PRINT(_data.pm10_env); + WS_DEBUG_PRINT(F("\t\tPM 2.5: ")); + WS_DEBUG_PRINT(_data.pm25_env); + WS_DEBUG_PRINT(F("\t\tPM 10: ")); + WS_DEBUG_PRINTLN(_data.pm100_env); + WS_DEBUG_PRINTLN(F("---------------------------------------")); + + return result; } /*******************************************************************************/ @@ -187,7 +195,7 @@ class ws_uart_drv_pm25aqi : public ws_uart_drv { pb_ostream_from_buffer(mqttBuffer, sizeof(mqttBuffer)); if (!ws_pb_encode(&ostream, wippersnapper_signal_v1_UARTResponse_fields, &msgUARTResponse)) { - Serial.println("[ERROR, UART]: Unable to encode device response!"); + WS_DEBUG_PRINTLN("[ERROR, UART]: Unable to encode device response!"); return; } @@ -195,9 +203,9 @@ class ws_uart_drv_pm25aqi : public ws_uart_drv { size_t msgSz; pb_get_encoded_size(&msgSz, wippersnapper_signal_v1_UARTResponse_fields, &msgUARTResponse); - Serial.print("[UART] Publishing event to IO.."); + WS_DEBUG_PRINT("[UART] Publishing event to IO.."); mqttClient->publish(uartTopic, mqttBuffer, msgSz, 1); - Serial.println("Published!"); + WS_DEBUG_PRINTLN("Published!"); setPrvPollTime(millis()); }