Compare commits

..

4 Commits
3.4.1 ... 3.5.0

Author SHA1 Message Date
Samuel Siburian
72bf812235 Merge pull request #350 from airgradienthq/feat/pm-ext-measures
Post extra PM values to server based on remote configuration
2025-11-24 11:01:38 +07:00
samuelbles07
2c37ab9895 Post measures through cellular payload based on extendedPmMeasures config 2025-11-23 19:08:40 +07:00
samuelbles07
565a7fa9fd Build CE payload format for extended measures 2025-11-23 18:16:26 +07:00
samuelbles07
9e07b67951 New configuration extendedPmMeasures 2025-11-22 12:53:24 +07:00
6 changed files with 117 additions and 5 deletions

View File

@@ -1416,18 +1416,19 @@ void postUsingCellular(bool forcePost) {
// Build payload include all measurements from queue // Build payload include all measurements from queue
std::string payload; std::string payload;
bool extendPmMeasures = configuration.isExtendedPmMeasuresEnabled();
payload += std::to_string(CELLULAR_MEASUREMENT_INTERVAL / 1000); // Convert to seconds payload += std::to_string(CELLULAR_MEASUREMENT_INTERVAL / 1000); // Convert to seconds
for (int i = 0; i < queueSize; i++) { for (int i = 0; i < queueSize; i++) {
auto mc = measurementCycleQueue.at(i); auto mc = measurementCycleQueue.at(i);
payload += ","; payload += ",";
payload += measurements.buildMeasuresPayload(mc); payload += measurements.buildMeasuresPayload(mc, extendPmMeasures);
} }
// Release before actually post measures that might takes too long // Release before actually post measures that might takes too long
xSemaphoreGive(mutexMeasurementCycleQueue); xSemaphoreGive(mutexMeasurementCycleQueue);
// Attempt to send // Attempt to send
if (agClient->httpPostMeasures(payload) == false) { if (agClient->httpPostMeasures(payload, extendPmMeasures) == false) {
// Consider network has a problem, retry in next schedule // Consider network has a problem, retry in next schedule
Serial.println("Post measures failed, retry in next schedule"); Serial.println("Post measures failed, retry in next schedule");
return; return;

View File

@@ -60,6 +60,7 @@ JSON_PROP_DEF(monitorDisplayCompensatedValues);
JSON_PROP_DEF(corrections); JSON_PROP_DEF(corrections);
JSON_PROP_DEF(atmp); JSON_PROP_DEF(atmp);
JSON_PROP_DEF(rhum); JSON_PROP_DEF(rhum);
JSON_PROP_DEF(extendedPmMeasures);
#define jprop_model_default "" #define jprop_model_default ""
#define jprop_country_default "TH" #define jprop_country_default "TH"
@@ -78,6 +79,7 @@ JSON_PROP_DEF(rhum);
#define jprop_displayBrightness_default 100 #define jprop_displayBrightness_default 100
#define jprop_offlineMode_default false #define jprop_offlineMode_default false
#define jprop_monitorDisplayCompensatedValues_default false #define jprop_monitorDisplayCompensatedValues_default false
#define jprop_extendedPmMeasures_default false
JSONVar jconfig; JSONVar jconfig;
@@ -400,6 +402,7 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_model] = jprop_model_default; jconfig[jprop_model] = jprop_model_default;
jconfig[jprop_offlineMode] = jprop_offlineMode_default; jconfig[jprop_offlineMode] = jprop_offlineMode_default;
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default; jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
jconfig[jprop_extendedPmMeasures] = jprop_extendedPmMeasures_default;
// PM2.5 default correction // PM2.5 default correction
pmCorrection.algorithm = COR_ALGO_PM_NONE; pmCorrection.algorithm = COR_ALGO_PM_NONE;
@@ -940,6 +943,26 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
if (JSON.typeof_(root[jprop_extendedPmMeasures]) == "boolean") {
bool value = root[jprop_extendedPmMeasures];
bool oldValue = jconfig[jprop_extendedPmMeasures];
if (value != oldValue) {
changed = true;
configLogInfo(String(jprop_extendedPmMeasures),
String(oldValue ? "true" : "false"),
String(value ? "true" : "false"));
jconfig[jprop_extendedPmMeasures] = value;
}
} else {
if (jsonTypeInvalid(root[jprop_extendedPmMeasures], "boolean")) {
failedMessage = jsonTypeInvalidMessage(
String(jprop_extendedPmMeasures), "boolean");
jsonInvalid();
return false;
}
}
// PM2.5 Corrections // PM2.5 Corrections
if (updatePmCorrection(root)) { if (updatePmCorrection(root)) {
changed = true; changed = true;
@@ -1002,6 +1025,11 @@ bool Configuration::isTemperatureUnitInF(void) {
return (unit == "f"); return (unit == "f");
} }
bool Configuration::isExtendedPmMeasuresEnabled(void) {
return jconfig[jprop_extendedPmMeasures];
}
/** /**
* @brief Country name, it's short name ex: TH = Thailand * @brief Country name, it's short name ex: TH = Thailand
* *
@@ -1368,6 +1396,18 @@ void Configuration::toConfig(const char *buf) {
logInfo("toConfig: disableCloudConnection changed"); logInfo("toConfig: disableCloudConnection changed");
} }
/** validate extendedPmMeasures configuration */
if (JSON.typeof_(jconfig[jprop_extendedPmMeasures]) != "boolean") {
isConfigFieldInvalid = true;
} else {
isConfigFieldInvalid = false;
}
if (isConfigFieldInvalid) {
jconfig[jprop_extendedPmMeasures] = jprop_extendedPmMeasures_default;
changed = true;
logInfo("toConfig: extendedPmMeasures changed");
}
/** validate configuration control */ /** validate configuration control */
if (JSON.typeof_(jprop_configurationControl) != "string") { if (JSON.typeof_(jprop_configurationControl) != "string") {
isConfigFieldInvalid = true; isConfigFieldInvalid = true;

View File

@@ -79,6 +79,7 @@ public:
String toString(void); String toString(void);
String toString(AgFirmwareMode fwMode); String toString(AgFirmwareMode fwMode);
bool isTemperatureUnitInF(void); bool isTemperatureUnitInF(void);
bool isExtendedPmMeasuresEnabled(void);
String getCountry(void); String getCountry(void);
bool isPmStandardInUSAQI(void); bool isPmStandardInUSAQI(void);
int getCO2CalibrationAbcDays(void); int getCO2CalibrationAbcDays(void);

View File

@@ -885,7 +885,7 @@ Measurements::Measures Measurements::getMeasures() {
return mc; return mc;
} }
std::string Measurements::buildMeasuresPayload(Measures &mc) { std::string Measurements::buildMeasuresPayload(Measures &mc, bool extendedPmMeasures) {
std::ostringstream oss; std::ostringstream oss;
// CO2 // CO2
@@ -984,6 +984,76 @@ std::string Measurements::buildMeasuresPayload(Measures &mc) {
oss << mc.signal; oss << mc.signal;
} }
if (extendedPmMeasures) {
oss << ",,,,,,,,"; // Add placeholder for MAX payload (BMS & O3/NO2)
/// PM 0.5 particle count
if (utils::isValidPm03Count(mc.pm_05_pc[0]) && utils::isValidPm03Count(mc.pm_05_pc[1])) {
oss << std::round((mc.pm_05_pc[0] + mc.pm_05_pc[1]) / 2.0f);
} else if (utils::isValidPm03Count(mc.pm_05_pc[0])) {
oss << std::round(mc.pm_05_pc[0]);
} else if (utils::isValidPm03Count(mc.pm_05_pc[1])) {
oss << std::round(mc.pm_05_pc[1]);
}
oss << ",";
/// PM 1.0 particle count
if (utils::isValidPm03Count(mc.pm_01_pc[0]) && utils::isValidPm03Count(mc.pm_01_pc[1])) {
oss << std::round((mc.pm_01_pc[0] + mc.pm_01_pc[1]) / 2.0f);
} else if (utils::isValidPm03Count(mc.pm_01_pc[0])) {
oss << std::round(mc.pm_01_pc[0]);
} else if (utils::isValidPm03Count(mc.pm_01_pc[1])) {
oss << std::round(mc.pm_01_pc[1]);
}
oss << ",";
/// PM 2.5 particle count
if (utils::isValidPm03Count(mc.pm_25_pc[0]) && utils::isValidPm03Count(mc.pm_25_pc[1])) {
oss << std::round((mc.pm_25_pc[0] + mc.pm_25_pc[1]) / 2.0f);
} else if (utils::isValidPm03Count(mc.pm_25_pc[0])) {
oss << std::round(mc.pm_25_pc[0]);
} else if (utils::isValidPm03Count(mc.pm_25_pc[1])) {
oss << std::round(mc.pm_25_pc[1]);
}
oss << ",";
/// PM 5.0 particle count
if (utils::isValidPm03Count(mc.pm_5_pc[0]) && utils::isValidPm03Count(mc.pm_5_pc[1])) {
oss << std::round((mc.pm_5_pc[0] + mc.pm_5_pc[1]) / 2.0f);
} else if (utils::isValidPm03Count(mc.pm_5_pc[0])) {
oss << std::round(mc.pm_5_pc[0]);
} else if (utils::isValidPm03Count(mc.pm_5_pc[1])) {
oss << std::round(mc.pm_5_pc[1]);
}
oss << ",";
/// PM 10 particle count
if (utils::isValidPm03Count(mc.pm_10_pc[0]) && utils::isValidPm03Count(mc.pm_10_pc[1])) {
oss << std::round((mc.pm_10_pc[0] + mc.pm_10_pc[1]) / 2.0f);
} else if (utils::isValidPm03Count(mc.pm_10_pc[0])) {
oss << std::round(mc.pm_10_pc[0]);
} else if (utils::isValidPm03Count(mc.pm_10_pc[1])) {
oss << std::round(mc.pm_10_pc[1]);
}
oss << ",";
/// PM2.5 standard particle
if (utils::isValidPm(mc.pm_25_sp[0]) && utils::isValidPm(mc.pm_25_sp[1])) {
float pm10 = (mc.pm_25_sp[0] + mc.pm_25_sp[1]) / 2.0f;
oss << std::round(pm10 * 10);
} else if (utils::isValidPm(mc.pm_25_sp[0])) {
oss << std::round(mc.pm_25_sp[0] * 10);
} else if (utils::isValidPm(mc.pm_25_sp[1])) {
oss << std::round(mc.pm_25_sp[1] * 10);
}
}
return oss.str(); return oss.str();
} }

View File

@@ -184,7 +184,7 @@ public:
Measures getMeasures(); Measures getMeasures();
std::string buildMeasuresPayload(Measures &measures); std::string buildMeasuresPayload(Measures &mc, bool extendedPmMeasures);
/** /**
* Set to true if want to debug every update value * Set to true if want to debug every update value