From c03faf2d9a19fb6da7e5422a10e89aeb032c42df Mon Sep 17 00:00:00 2001 From: Jas Strong Date: Sat, 10 Jan 2026 11:40:14 -0800 Subject: [PATCH] [aqi] Fix precision loss for low PM concentration values (#13120) Co-authored-by: jas --- .../components/aqi/abstract_aqi_calculator.h | 2 +- esphome/components/aqi/aqi_calculator.h | 34 +++++++++++-------- esphome/components/aqi/aqi_sensor.cpp | 3 +- esphome/components/aqi/caqi_calculator.h | 30 +++++++++------- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/esphome/components/aqi/abstract_aqi_calculator.h b/esphome/components/aqi/abstract_aqi_calculator.h index 7836c76cdc..299962fa17 100644 --- a/esphome/components/aqi/abstract_aqi_calculator.h +++ b/esphome/components/aqi/abstract_aqi_calculator.h @@ -6,7 +6,7 @@ namespace esphome::aqi { class AbstractAQICalculator { public: - virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0; + virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0; }; } // namespace esphome::aqi diff --git a/esphome/components/aqi/aqi_calculator.h b/esphome/components/aqi/aqi_calculator.h index 35dc35a44a..993504c1e9 100644 --- a/esphome/components/aqi/aqi_calculator.h +++ b/esphome/components/aqi/aqi_calculator.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include "abstract_aqi_calculator.h" // https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf @@ -9,11 +10,11 @@ namespace esphome::aqi { class AQICalculator : public AbstractAQICalculator { public: - uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); - int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); + uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override { + float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); - return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; + return static_cast(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index)); } protected: @@ -21,25 +22,28 @@ class AQICalculator : public AbstractAQICalculator { static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; - static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}}; + static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f}, + {35.5f, 55.4f}, {55.5f, 125.4f}, + {125.5f, 225.4f}, {225.5f, std::numeric_limits::max()}}; - static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254}, - {255, 354}, {355, 424}, {425, INT_MAX}}; + static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f}, + {155.0f, 254.0f}, {255.0f, 354.0f}, + {355.0f, 424.0f}, {425.0f, std::numeric_limits::max()}}; - static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + static float calculate_index(float value, const float array[NUM_LEVELS][2]) { int grid_index = get_grid_index(value, array); if (grid_index == -1) { - return -1; + return -1.0f; } - int aqi_lo = INDEX_GRID[grid_index][0]; - int aqi_hi = INDEX_GRID[grid_index][1]; - int conc_lo = array[grid_index][0]; - int conc_hi = array[grid_index][1]; + float aqi_lo = INDEX_GRID[grid_index][0]; + float aqi_hi = INDEX_GRID[grid_index][1]; + float conc_lo = array[grid_index][0]; + float conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + static int get_grid_index(float value, const float array[NUM_LEVELS][2]) { for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i; diff --git a/esphome/components/aqi/aqi_sensor.cpp b/esphome/components/aqi/aqi_sensor.cpp index cdc9f35ba6..2d8a780cc7 100644 --- a/esphome/components/aqi/aqi_sensor.cpp +++ b/esphome/components/aqi/aqi_sensor.cpp @@ -44,8 +44,7 @@ void AQISensor::calculate_aqi_() { return; } - uint16_t aqi = - calculator->get_aqi(static_cast(this->pm_2_5_value_), static_cast(this->pm_10_0_value_)); + uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_); this->publish_state(aqi); } diff --git a/esphome/components/aqi/caqi_calculator.h b/esphome/components/aqi/caqi_calculator.h index 9906c179f6..d2ec4bb98f 100644 --- a/esphome/components/aqi/caqi_calculator.h +++ b/esphome/components/aqi/caqi_calculator.h @@ -1,16 +1,18 @@ #pragma once +#include +#include #include "abstract_aqi_calculator.h" namespace esphome::aqi { class CAQICalculator : public AbstractAQICalculator { public: - uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override { - int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); - int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); + uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override { + float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID); + float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID); - return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index; + return static_cast(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index)); } protected: @@ -18,25 +20,27 @@ class CAQICalculator : public AbstractAQICalculator { static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}}; - static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}}; + static constexpr float PM2_5_GRID[NUM_LEVELS][2] = { + {0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits::max()}}; - static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}}; + static constexpr float PM10_0_GRID[NUM_LEVELS][2] = { + {0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits::max()}}; - static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) { + static float calculate_index(float value, const float array[NUM_LEVELS][2]) { int grid_index = get_grid_index(value, array); if (grid_index == -1) { - return -1; + return -1.0f; } - int aqi_lo = INDEX_GRID[grid_index][0]; - int aqi_hi = INDEX_GRID[grid_index][1]; - int conc_lo = array[grid_index][0]; - int conc_hi = array[grid_index][1]; + float aqi_lo = INDEX_GRID[grid_index][0]; + float aqi_hi = INDEX_GRID[grid_index][1]; + float conc_lo = array[grid_index][0]; + float conc_hi = array[grid_index][1]; return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo; } - static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) { + static int get_grid_index(float value, const float array[NUM_LEVELS][2]) { for (int i = 0; i < NUM_LEVELS; i++) { if (value >= array[i][0] && value <= array[i][1]) { return i;