@@ -12,8 +12,8 @@ globals:
1212 - id : nowcast_delay_mins
1313 type : int
1414 restore_value : false
15- # NowCast is calculated over a 12 hour period
16- initial_value : ' 720 '
15+ # NowCast is valid if we have data for 2 out of the 3 most recent hours
16+ initial_value : ' 120 '
1717 - id : pm_2_5_hourly_avg
1818 type : std::vector<float>
1919 restore_value : false
@@ -81,7 +81,7 @@ sensor:
8181 lambda : |
8282 // Insert the current value
8383 float current = id(pm_2_5_1h_avg).state;
84- if (!isnan(current)) {
84+ if (!isnan(current) && current > 0 ) {
8585 id(pm_2_5_hourly_avg).insert(id(pm_2_5_hourly_avg).begin(), current);
8686 // Truncate anything past the first 24
8787 if (id(pm_2_5_hourly_avg).size() > 24) {
@@ -107,7 +107,7 @@ sensor:
107107 lambda : |
108108 // Insert the current value
109109 float current = id(pm_10_0_1h_avg).state;
110- if (!isnan(current)) {
110+ if (!isnan(current) && current > 0 ) {
111111 id(pm_10_0_hourly_avg).insert(id(pm_10_0_hourly_avg).begin(), current);
112112 // Truncate anything past the first 24
113113 if (id(pm_10_0_hourly_avg).size() > 24) {
@@ -123,20 +123,22 @@ script:
123123 int aqi_2_5 = -1;
124124 int aqi_10_0 = -1;
125125
126- // AQI is calculated over a 24 hour minimum, but EPA says it's acceptable to
126+ // AQI is calculated over a 24 hour minimum but EPA says it's acceptable to
127127 // report at 75%, or 18 hours: https://forum.airnowtech.org/t/aqi-calculations-overview-ozone-pm2-5-and-pm10/168
128- int size_2_5 = id(pm_2_5_hourly_avg).size();
129- if (size_2_5 > 24) {
130- size_2_5 = 24;
128+ int size_2_5 = 0;
129+ float sum_2_5 = 0.0;
130+ int hourly_2_5_size = std::min(24, static_cast<int>(id(pm_2_5_hourly_avg).size()));
131+ for (int i = 0; i < hourly_2_5_size; i++) {
132+ float pm = id(pm_2_5_hourly_avg)[i];
133+ if (!isnan(pm) && pm > 0) {
134+ sum_2_5 += pm;
135+ size_2_5++;
136+ }
131137 }
132- if (size_2_5 >= 18) {
133138
134- float sum = 0.0;
135- for (int i = 0; i < size_2_5; i++) {
136- sum += id(pm_2_5_hourly_avg)[i];
137- }
139+ if (size_2_5 >= 18) {
138140
139- float pm25 = sum / (float)size_2_5;
141+ float pm25 = sum_2_5 / (float)size_2_5;
140142 if (pm25 < 12.0) {
141143 aqi_2_5 = (50.0 - 0.0) / (12.0 - 0.0) * (pm25 - 0.0) + 0.0;
142144 } else if (pm25 < 35.4) {
@@ -156,18 +158,20 @@ script:
156158 }
157159 }
158160
159- int size_10_0 = id(pm_10_0_hourly_avg).size();
160- if (size_10_0 > 24) {
161- size_10_0 = 24;
161+ int size_10_0 = 0;
162+ float sum_10_0 = 0.0;
163+ int loop_max_10 = std::min(24, static_cast<int>(id(pm_10_0_hourly_avg).size()));
164+ for (int i = 0; i < loop_max_10; i++) {
165+ float pm = id(pm_10_0_hourly_avg)[i];
166+ if (!isnan(pm) && pm > 0) {
167+ sum_10_0 += pm;
168+ size_10_0++;
169+ }
162170 }
163- if (size_10_0 >= 18) {
164171
165- float sum = 0.0;
166- for (int i = 0; i < size_10_0; i++) {
167- sum += id(pm_10_0_hourly_avg)[i];
168- }
172+ if (size_10_0 >= 18) {
169173
170- float pm10 = sum / (float)size_10_0;
174+ float pm10 = sum_10_0 / (float)size_10_0;
171175 if (pm10 < 54.0) {
172176 aqi_10_0 = (50.0 - 0.0) / (54.0 - 0.0) * (pm10 - 0.0) + 0.0;
173177 } else if (pm10 < 154.0) {
@@ -191,8 +195,7 @@ script:
191195 if (aqi_calc > 0) {
192196 id(aqi).publish_state(aqi_calc);
193197 // Just in case we're counting down, make sure we set to zero
194- id(aqi_delay_mins) = 0;
195- if (id(aqi_delay_mins) > 0) {
198+ if (isnan(id(aqi_delay_mins)) || id(aqi_delay_mins) > 0) {
196199 id(aqi_delay_mins) = 0;
197200 id(aqi_mins_remaining).publish_state(0);
198201 }
@@ -204,122 +207,148 @@ script:
204207 - lambda : |
205208 int nowcast_2_5 = -1;
206209 int nowcast_10_0 = -1;
210+
211+ int hourly_2_5_size = std::min(12, static_cast<int>(id(pm_2_5_hourly_avg).size()));
212+ int hourly_10_0_size = std::min(12, static_cast<int>(id(pm_10_0_hourly_avg).size()));
207213
208- if (id(pm_2_5_hourly_avg).size() >= 12) {
214+ // nowcast is valid if we have 2 out of the last 3 hours worth of data
215+ if (hourly_2_5_size >= 2) {
209216 // Calculate min and max
210217 float max = 0.0;
211218 float min = 31337.0; // just a random large number
212- for (int i = 0; i < 12 ; i++) {
219+ for (int i = 0; i < hourly_2_5_size ; i++) {
213220 float pm = id(pm_2_5_hourly_avg)[i];
214- if (pm < min) {
215- min = pm;
216- }
217- if (pm > max) {
218- max = pm;
221+ if (!isnan(pm) && pm > 0) {
222+ if (pm < min) {
223+ min = pm;
224+ }
225+ if (pm > max) {
226+ max = pm;
227+ }
219228 }
220229 }
221- // Calculate the weight factor
222- float range = max - min;
223- float rate = range / max;
224- float weight_factor = 1.0 - rate;
225- if (weight_factor < 0.5) {
226- weight_factor = 0.5;
227- } else if (weight_factor > 1.0) {
228- weight_factor = 1.0;
229- }
230+ if (max >= min) {
230231
231- float pm_sum = 0.0;
232- float weight_sum = 0.0;
233- for (int i = 0; i < 12; i++) {
234- float weight_pow = pow(weight_factor, i);
235- pm_sum += id(pm_2_5_hourly_avg)[i] * weight_pow;
236- weight_sum += weight_pow;
237- }
238- float pm25 = pm_sum / weight_sum;
232+ // Calculate the weight factor
233+ float range = max - min;
234+ float rate = range / max;
235+ float weight_factor = 1.0 - rate;
236+ if (weight_factor < 0.5) {
237+ weight_factor = 0.5;
238+ } else if (weight_factor > 1.0) {
239+ weight_factor = 1.0;
240+ }
239241
240- // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
241- if (pm25 < 12.0) {
242- nowcast_2_5 = (50.0 - 0.0) / (12.0 - 0.0) * (pm25 - 0.0) + 0.0;
243- } else if (pm25 < 35.4) {
244- nowcast_2_5 = (100.0 - 51.0) / (35.4 - 12.1) * (pm25 - 12.1) + 51.0;
245- } else if (pm25 < 55.4) {
246- nowcast_2_5 = (150.0 - 101.0) / (55.4 - 35.5) * (pm25 - 35.5) + 101.0;
247- } else if (pm25 < 150.4) {
248- nowcast_2_5 = (200.0 - 151.0) / (150.4 - 55.5) * (pm25 - 55.5) + 151.0;
249- } else if (pm25 < 250.4) {
250- nowcast_2_5 = (300.0 - 201.0) / (250.4 - 150.5) * (pm25 - 150.5) + 201.0;
251- } else if (pm25 < 350.4) {
252- nowcast_2_5 = (400.0 - 301.0) / (350.4 - 250.5) * (pm25 - 250.5) + 301.0;
253- } else if (pm25 < 500.4) {
254- nowcast_2_5 = (500.0 - 401.0) / (500.4 - 350.5) * (pm25 - 350.5) + 401.0;
255- } else {
256- // everything higher is just counted as 500
257- nowcast_2_5 = 500;
242+ float pm_sum = 0.0;
243+ float weight_sum = 0.0;
244+ for (int i = 0; i < hourly_2_5_size; i++) {
245+ float pm = id(pm_2_5_hourly_avg)[i];
246+ if (!isnan(pm) && pm > 0) {
247+ float weight_pow = pow(weight_factor, i);
248+ pm_sum += pm * weight_pow;
249+ weight_sum += weight_pow;
250+ }
251+ }
252+ float pm25 = pm_sum / weight_sum;
253+ pm25 = static_cast<int>(pm25 * 10) / 10.0; // pm25 is truncated to the nearest 0.1
254+
255+ // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
256+ if (pm25 < 12.0) {
257+ nowcast_2_5 = (50.0 - 0.0) / (12.0 - 0.0) * (pm25 - 0.0) + 0.0;
258+ } else if (pm25 < 35.4) {
259+ nowcast_2_5 = (100.0 - 51.0) / (35.4 - 12.1) * (pm25 - 12.1) + 51.0;
260+ } else if (pm25 < 55.4) {
261+ nowcast_2_5 = (150.0 - 101.0) / (55.4 - 35.5) * (pm25 - 35.5) + 101.0;
262+ } else if (pm25 < 150.4) {
263+ nowcast_2_5 = (200.0 - 151.0) / (150.4 - 55.5) * (pm25 - 55.5) + 151.0;
264+ } else if (pm25 < 250.4) {
265+ nowcast_2_5 = (300.0 - 201.0) / (250.4 - 150.5) * (pm25 - 150.5) + 201.0;
266+ } else if (pm25 < 350.4) {
267+ nowcast_2_5 = (400.0 - 301.0) / (350.4 - 250.5) * (pm25 - 250.5) + 301.0;
268+ } else if (pm25 < 500.4) {
269+ nowcast_2_5 = (500.0 - 401.0) / (500.4 - 350.5) * (pm25 - 350.5) + 401.0;
270+ } else {
271+ // everything higher is just counted as 500
272+ nowcast_2_5 = 500;
273+ }
274+
258275 }
259276 }
260277
261- if (id(pm_10_0_hourly_avg).size() >= 12 ) {
278+ if (hourly_10_0_size >= 2 ) {
262279 // Calculate min and max
263280 float max = 0.0;
264281 float min = 31337.0; // just a random large number
265- for (int i = 0; i < 12 ; i++) {
282+ for (int i = 0; i < hourly_10_0_size ; i++) {
266283 float pm = id(pm_10_0_hourly_avg)[i];
267- if (pm < min) {
268- min = pm;
269- }
270- if (pm > max) {
271- max = pm;
284+ if (!isnan(pm) && pm > 0) {
285+ if (pm < min) {
286+ min = pm;
287+ }
288+ if (pm > max) {
289+ max = pm;
290+ }
272291 }
273292 }
274- // Calculate the weight factor
275- float range = max - min;
276- float rate = range / max;
277- float weight_factor = 1.0 - rate;
278- if (weight_factor < 0.5) {
279- weight_factor = 0.5;
280- } else if (weight_factor > 1.0) {
281- weight_factor = 1.0;
282- }
283293
284- float pm_sum = 0.0;
285- float weight_sum = 0.0;
286- for (int i = 0; i < 12; i++) {
287- float weight_pow = pow(weight_factor, i);
288- pm_sum += id(pm_10_0_hourly_avg)[i] * weight_pow;
289- weight_sum += weight_pow;
290- }
291- float pm10 = pm_sum / weight_sum;
294+ if (max >= min) {
292295
293- // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
294- if (pm10 < 54.0) {
295- nowcast_10_0 = (50.0 - 0.0) / (54.0 - 0.0) * (pm10 - 0.0) + 0.0;
296- } else if (pm10 < 154.0) {
297- nowcast_10_0 = (100.0 - 51.0) / (154.0 - 55.0) * (pm10 - 55.0) + 51.0;
298- } else if (pm10 < 254.0) {
299- nowcast_10_0 = (150.0 - 101.0) / (254.0 - 155.0) * (pm10 - 155.0) + 101.0;
300- } else if (pm10 < 354.0) {
301- nowcast_10_0 = (200.0 - 151.0) / (354.0 - 255.0) * (pm10 - 255.0) + 151.0;
302- } else if (pm10 < 424.0) {
303- nowcast_10_0 = (300.0 - 201.0) / (424.0 - 355.0) * (pm10 - 355.0) + 201.0;
304- } else if (pm10 < 504.0) {
305- nowcast_10_0 = (400.0 - 301.0) / (504.0 - 425.0) * (pm10 - 425.0) + 301.0;
306- } else if (pm10 < 604) {
307- nowcast_10_0 = (500.0 - 401.0) / (604.0 - 505.0) * (pm10 - 505.0) + 401.0;
308- } else {
309- // everything higher is just counted as 500
310- nowcast_10_0 = 500.0;
311- }
296+ // Calculate the weight factor
297+ float range = max - min;
298+ float rate = range / max;
299+ float weight_factor = 1.0 - rate;
300+ if (weight_factor < 0.5) {
301+ weight_factor = 0.5;
302+ } else if (weight_factor > 1.0) {
303+ weight_factor = 1.0;
304+ }
312305
306+ float pm_sum = 0.0;
307+ float weight_sum = 0.0;
308+ for (int i = 0; i < hourly_10_0_size; i++) {
309+ float pm = id(pm_10_0_hourly_avg)[i];
310+ if (!isnan(pm) && pm > 0) {
311+ float weight_pow = pow(weight_factor, i);
312+ pm_sum += pm * weight_pow;
313+ weight_sum += weight_pow;
314+ }
315+ }
316+ float pm10 = pm_sum / weight_sum;
317+ pm10 = static_cast<int>(pm10); // pm10 is truncated to the nearest whole number
318+
319+ // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
320+ if (pm10 < 54.0) {
321+ nowcast_10_0 = (50.0 - 0.0) / (54.0 - 0.0) * (pm10 - 0.0) + 0.0;
322+ } else if (pm10 < 154.0) {
323+ nowcast_10_0 = (100.0 - 51.0) / (154.0 - 55.0) * (pm10 - 55.0) + 51.0;
324+ } else if (pm10 < 254.0) {
325+ nowcast_10_0 = (150.0 - 101.0) / (254.0 - 155.0) * (pm10 - 155.0) + 101.0;
326+ } else if (pm10 < 354.0) {
327+ nowcast_10_0 = (200.0 - 151.0) / (354.0 - 255.0) * (pm10 - 255.0) + 151.0;
328+ } else if (pm10 < 424.0) {
329+ nowcast_10_0 = (300.0 - 201.0) / (424.0 - 355.0) * (pm10 - 355.0) + 201.0;
330+ } else if (pm10 < 504.0) {
331+ nowcast_10_0 = (400.0 - 301.0) / (504.0 - 425.0) * (pm10 - 425.0) + 301.0;
332+ } else if (pm10 < 604) {
333+ nowcast_10_0 = (500.0 - 401.0) / (604.0 - 505.0) * (pm10 - 505.0) + 401.0;
334+ } else {
335+ // everything higher is just counted as 500
336+ nowcast_10_0 = 500.0;
337+ }
338+
339+ }
313340 }
314341
315- int nowcast_calc = std::max( nowcast_2_5, nowcast_10_0) ;
342+ int nowcast_calc = nowcast_10_0 > nowcast_2_5 ? nowcast_10_0 : nowcast_2_5 ;
316343 if (nowcast_calc > 0) {
317344 id(nowcast).publish_state(nowcast_calc);
318345 // Just in case we're counting down, make sure we set to zero
319- if (id(nowcast_delay_mins) > 0) {
346+ if (isnan(id(nowcast_delay_mins)) || id(nowcast_delay_mins) > 0) {
320347 id(nowcast_delay_mins) = 0;
321348 id(nowcast_mins_remaining).publish_state(0);
322349 }
350+ } else {
351+ id(nowcast).publish_state(std::nan(""));
323352 }
324353
325354 - id : calculate_categories
@@ -377,11 +406,11 @@ interval:
377406 - interval : 1min
378407 then :
379408 - lambda : |
380- if (id(aqi_delay_mins) > 0) {
409+ if (!isnan(id(aqi_delay_mins)) && id(aqi_delay_mins) > 0) {
381410 id(aqi_delay_mins) -= 1;
382411 id(aqi_mins_remaining).publish_state(id(aqi_delay_mins));
383412 }
384- if (id(nowcast_delay_mins) > 0) {
413+ if (!isnan(id(nowcast_delay_mins)) && id(nowcast_delay_mins) > 0) {
385414 id(nowcast_delay_mins) -= 1;
386415 id(nowcast_mins_remaining).publish_state(id(nowcast_delay_mins));
387416 }
0 commit comments