Skip to content

Commit 96fb237

Browse files
committed
Merge main into dashboard design branch
2 parents 61ff23c + 2d3b6c4 commit 96fb237

13 files changed

Lines changed: 2015 additions & 996 deletions

File tree

config.example.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,10 @@
3737
"i2cSDA": 21,
3838
"i2cSCL": 22,
3939
"i2cFrequency": 100000
40+
},
41+
"cloudDetection": {
42+
"clearSkyThreshold": -13.0,
43+
"cloudyThreshold": -3.0,
44+
"humidityCorrection": 0.75
4045
}
4146
}

include/Config.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ namespace SQM
9292
uint32_t i2cFrequency;
9393
};
9494

95+
struct CloudDetectionConfig
96+
{
97+
float clearSkyThreshold; // °C, corrected delta below which sky is clear (default: -13.0)
98+
float cloudyThreshold; // °C, corrected delta above which sky is overcast (default: -3.0)
99+
float humidityCorrection; // k1 factor for humidity correction (default: 0.75)
100+
};
101+
95102
struct Config
96103
{
97104
WiFiConfig wifi;
@@ -102,6 +109,7 @@ namespace SQM
102109
GPSConfig gps;
103110
RainConfig rain;
104111
SensorConfig sensor;
112+
CloudDetectionConfig cloudDetection;
105113
std::string deviceName;
106114
std::string timezone;
107115
TimeSource primaryTimeSource; // Primary time source

include/calculations/CloudDetection.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ namespace SQM
4444
* @param relativeHumidity Relative humidity (0-100%), default 53%
4545
* @return CloudMetrics containing cloud cover estimation
4646
*/
47-
static CloudMetrics calculate(float skyTemp, float ambientTemp, float relativeHumidity = 53.0f);
47+
static CloudMetrics calculate(
48+
float skyTemp,
49+
float ambientTemp,
50+
float relativeHumidity = 53.0f,
51+
float clearSkyThreshold = CLEAR_SKY_THRESHOLD,
52+
float cloudyThreshold = CLOUDY_THRESHOLD,
53+
float humidityCorrection = HUMIDITY_CORRECTION_FACTOR
54+
);
4855

4956
/**
5057
* Apply humidity correction to temperature delta
@@ -57,9 +64,10 @@ namespace SQM
5764
*
5865
* @param tempDelta Raw temperature difference (sky - ambient)
5966
* @param humidity Relative humidity (0-100%)
67+
* @param correctionFactor k1 humidity correction factor
6068
* @return Humidity-corrected temperature delta
6169
*/
62-
static float applyHumidityCorrection(float tempDelta, float humidity);
70+
static float applyHumidityCorrection(float tempDelta, float humidity, float correctionFactor = HUMIDITY_CORRECTION_FACTOR);
6371

6472
/**
6573
* Classify cloud condition from corrected temperature delta
@@ -72,9 +80,11 @@ namespace SQM
7280
* Errs on the side of caution for astronomical observations.
7381
*
7482
* @param correctedDelta Humidity-corrected temperature delta
83+
* @param clearThreshold corrected delta below which sky is clear
84+
* @param cloudyThreshold corrected delta above which sky is overcast
7585
* @return Cloud condition classification
7686
*/
77-
static CloudCondition classifyCondition(float correctedDelta);
87+
static CloudCondition classifyCondition(float correctedDelta, float clearThreshold = CLEAR_SKY_THRESHOLD, float cloudyThreshold = CLOUDY_THRESHOLD);
7888

7989
/**
8090
* Estimate cloud cover percentage
@@ -83,9 +93,11 @@ namespace SQM
8393
* More sophisticated than binary classification.
8494
*
8595
* @param correctedDelta Humidity-corrected temperature delta
96+
* @param clearThreshold corrected delta below which sky is clear
97+
* @param cloudyThreshold corrected delta above which sky is overcast
8698
* @return Estimated cloud cover (0-100%)
8799
*/
88-
static float estimateCloudCover(float correctedDelta);
100+
static float estimateCloudCover(float correctedDelta, float clearThreshold = CLEAR_SKY_THRESHOLD, float cloudyThreshold = CLOUDY_THRESHOLD);
89101

90102
/**
91103
* Get human-readable description of cloud condition

src/Config.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ namespace SQM
291291
cfg.primaryTimeSource = TimeSource::NTP;
292292
cfg.secondaryTimeSource = TimeSource::GPS;
293293

294+
cfg.cloudDetection.clearSkyThreshold = -13.0f;
295+
cfg.cloudDetection.cloudyThreshold = -3.0f;
296+
cfg.cloudDetection.humidityCorrection = 0.75f;
297+
294298
return cfg;
295299
}
296300

@@ -365,6 +369,11 @@ namespace SQM
365369
sensor["i2cSCL"] = this->sensor.i2cSCL;
366370
sensor["i2cFrequency"] = this->sensor.i2cFrequency;
367371

372+
JsonObject cloudDetection = doc.createNestedObject("cloudDetection");
373+
cloudDetection["clearSkyThreshold"] = this->cloudDetection.clearSkyThreshold;
374+
cloudDetection["cloudyThreshold"] = this->cloudDetection.cloudyThreshold;
375+
cloudDetection["humidityCorrection"] = this->cloudDetection.humidityCorrection;
376+
368377
std::string output;
369378
serializeJson(doc, output);
370379
return output;
@@ -514,6 +523,11 @@ namespace SQM
514523
return setError(error, "I2C frequency is invalid");
515524
}
516525

526+
if (cloudDetection.clearSkyThreshold >= cloudDetection.cloudyThreshold)
527+
{
528+
return setError(error, "Cloud detection: clear-sky threshold must be less than cloudy threshold");
529+
}
530+
517531
return true;
518532
}
519533

@@ -672,6 +686,17 @@ namespace SQM
672686

673687
normalizeTimeSources(cfg);
674688

689+
JsonObject cloudDetectionObj = doc["cloudDetection"];
690+
if (!cloudDetectionObj.isNull())
691+
{
692+
if (cloudDetectionObj.containsKey("clearSkyThreshold"))
693+
cfg.cloudDetection.clearSkyThreshold = cloudDetectionObj["clearSkyThreshold"] | -13.0f;
694+
if (cloudDetectionObj.containsKey("cloudyThreshold"))
695+
cfg.cloudDetection.cloudyThreshold = cloudDetectionObj["cloudyThreshold"] | -3.0f;
696+
if (cloudDetectionObj.containsKey("humidityCorrection"))
697+
cfg.cloudDetection.humidityCorrection = cloudDetectionObj["humidityCorrection"] | 0.75f;
698+
}
699+
675700
std::string validationError;
676701
if (!cfg.validate(&validationError))
677702
{

src/WebServer.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1096,10 +1096,14 @@ namespace SQM
10961096
// Use BME280 humidity if available, otherwise default to 53%
10971097
bool usingHumidityFallback = bmeReading.status != SensorStatus::OK;
10981098
float humidity = usingHumidityFallback ? 53.0f : bmeReading.humidity;
1099+
const Config &cfg = getConfigCallback();
10991100
CloudMetrics cloudMetrics = CloudDetection::calculate(
11001101
mlxReading.objectTemp,
11011102
mlxReading.ambientTemp,
1102-
humidity);
1103+
humidity,
1104+
cfg.cloudDetection.clearSkyThreshold,
1105+
cfg.cloudDetection.cloudyThreshold,
1106+
cfg.cloudDetection.humidityCorrection);
11031107

11041108
JsonObject cloud = doc.createNestedObject("cloudConditions");
11051109
cloud["temperatureDelta"] = cloudMetrics.temperatureDelta;

src/calculations/CloudDetection.cpp

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
namespace SQM
55
{
66

7-
CloudMetrics CloudDetection::calculate(float skyTemp, float ambientTemp, float relativeHumidity)
7+
CloudMetrics CloudDetection::calculate(float skyTemp, float ambientTemp, float relativeHumidity,
8+
float clearSkyThreshold, float cloudyThreshold, float humidityCorrection)
89
{
910
CloudMetrics metrics;
1011

@@ -13,39 +14,39 @@ namespace SQM
1314

1415
// Apply humidity correction
1516
// High humidity makes the sky appear warmer, reducing the delta
16-
metrics.correctedDelta = applyHumidityCorrection(metrics.temperatureDelta, relativeHumidity);
17+
metrics.correctedDelta = applyHumidityCorrection(metrics.temperatureDelta, relativeHumidity, humidityCorrection);
1718

1819
// Estimate cloud cover percentage
19-
metrics.cloudCoverPercent = estimateCloudCover(metrics.correctedDelta);
20+
metrics.cloudCoverPercent = estimateCloudCover(metrics.correctedDelta, clearSkyThreshold, cloudyThreshold);
2021

2122
// Classify condition
22-
metrics.condition = classifyCondition(metrics.correctedDelta);
23+
metrics.condition = classifyCondition(metrics.correctedDelta, clearSkyThreshold, cloudyThreshold);
2324
metrics.description = getConditionDescription(metrics.condition);
2425

2526
return metrics;
2627
}
2728

28-
float CloudDetection::applyHumidityCorrection(float tempDelta, float humidity)
29+
float CloudDetection::applyHumidityCorrection(float tempDelta, float humidity, float correctionFactor)
2930
{
3031
// Clamp humidity to valid range
3132
humidity = std::max(0.0f, std::min(100.0f, humidity));
3233

3334
// AAG CloudWatcher correction formula
3435
// Humidity increases the apparent sky temperature, reducing the delta
35-
float correction = (HUMIDITY_CORRECTION_FACTOR / 100.0f) * humidity;
36+
float correction = (correctionFactor / 100.0f) * humidity;
3637

3738
return tempDelta - correction;
3839
}
3940

40-
CloudCondition CloudDetection::classifyCondition(float correctedDelta)
41+
CloudCondition CloudDetection::classifyCondition(float correctedDelta, float clearThreshold, float cloudyThreshold)
4142
{
4243
// Conservative thresholds for astronomical observations (zenith sensor)
4344
// Errs on the side of caution - better to overestimate cloud cover
44-
if (correctedDelta < CLEAR_SKY_THRESHOLD)
45+
if (correctedDelta < clearThreshold)
4546
{
4647
return CloudCondition::CLEAR;
4748
}
48-
else if (correctedDelta < CLOUDY_THRESHOLD)
49+
else if (correctedDelta < cloudyThreshold)
4950
{
5051
return CloudCondition::CLOUDY;
5152
}
@@ -55,27 +56,23 @@ namespace SQM
5556
}
5657
}
5758

58-
float CloudDetection::estimateCloudCover(float correctedDelta)
59+
float CloudDetection::estimateCloudCover(float correctedDelta, float clearThreshold, float cloudyThreshold)
5960
{
60-
// Linear interpolation between clear and overcast
61-
// Conservative thresholds for zenith-pointing sensors:
62-
// Clear sky (< -13°C) = 0%
63-
// Overcast (≥ -3°C) = 100%
64-
// 10°C range for fine-grained detection
61+
// Linear interpolation between clear and overcast thresholds
6562

66-
if (correctedDelta < CLEAR_SKY_THRESHOLD)
63+
if (correctedDelta < clearThreshold)
6764
{
6865
return 0.0f; // Truly clear
6966
}
70-
else if (correctedDelta >= CLOUDY_THRESHOLD)
67+
else if (correctedDelta >= cloudyThreshold)
7168
{
7269
return 100.0f; // Overcast
7370
}
7471
else
7572
{
7673
// Linear interpolation in the cloudy range
77-
float range = CLOUDY_THRESHOLD - CLEAR_SKY_THRESHOLD; // 10°C range
78-
float position = correctedDelta - CLEAR_SKY_THRESHOLD;
74+
float range = cloudyThreshold - clearThreshold;
75+
float position = correctedDelta - clearThreshold;
7976
float percent = (position / range) * 100.0f;
8077

8178
return std::max(0.0f, std::min(100.0f, percent));

0 commit comments

Comments
 (0)