Add air quality history charts
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
|
||||
Local external component for **ESPHome 2026.10.2, ESP32 with ESP-IDF**. No remote
|
||||
libraries, standard `web_server`, entity enumeration, SSE, OTA, configuration,
|
||||
authentication, or history. Only the three explicitly referenced sensor states
|
||||
are published. Other device services are not secured by this component. The
|
||||
or authentication. Only the three explicitly referenced sensor states and their
|
||||
10-hour air-quality history are published. Other device services are not secured by this component. The
|
||||
optional mDNS hostname override affects the device-wide mDNS responder.
|
||||
|
||||
## Integration (in the parent configuration)
|
||||
@@ -61,16 +61,36 @@ uses its native mDNS library; it does not initialize a second responder.
|
||||
* Exact `GET /api/air-quality`: JSON with **exactly** `co2`, `temperature`,
|
||||
`humidity`, each a finite number or `null`. Example:
|
||||
`{"co2":850,"temperature":22.5,"humidity":46}`.
|
||||
* Exact `GET /api/air-quality/history`: JSON containing `duration_seconds: 36000`
|
||||
and `samples`, ordered oldest first. Each sample is
|
||||
`[age_seconds, co2, temperature, humidity]`, with nonnegative ages relative to
|
||||
the response snapshot and finite values or `null`. Only samples within the
|
||||
last 10 hours are returned; there are at most 601 samples.
|
||||
* Non-GET methods on those paths: 405 with `Allow: GET`.
|
||||
* All other paths: 404, including `/events`, `/update`, `/config`, general sensor
|
||||
APIs, trailing slashes, encoded aliases, and query-string variants. No body
|
||||
parsing or upload handlers. HTTP parser errors can still be rejected by IDF
|
||||
before application routing.
|
||||
|
||||
The browser polls five seconds after each completed request (4-second timeout),
|
||||
never concurrently. Missing measurements show dashes. Network/JSON errors clear
|
||||
old measurements and display an offline message. CO₂ is orange **only above**
|
||||
1200 ppm and red **only above** 1600 ppm; text accompanies the colors.
|
||||
The browser refreshes live readings approximately every five seconds and history
|
||||
approximately every minute, using one sequential request loop and a separate
|
||||
4-second timeout for each request. Missing live measurements show dashes. A live
|
||||
request failure clears the live cards; a history failure retains previously
|
||||
loaded history with a stale/unavailable warning, without clearing healthy live
|
||||
cards. CO₂ is orange **only above** 1200 ppm and red **only above** 1600 ppm;
|
||||
text accompanies the colors.
|
||||
|
||||
Three separate responsive charts show CO₂ (ppm), temperature (°C), and relative
|
||||
humidity (%) on a fixed −10 h to now axis, with independent automatic Y scales.
|
||||
Missing values and timestamp gaps over 90 seconds break the traces. Partial
|
||||
history is not stretched across the time axis or extrapolated to the present.
|
||||
CO₂ guides at 1200 and 1600 ppm appear when within the chart's value range.
|
||||
|
||||
The device records one sample every 60 seconds, independently of browser, VPN,
|
||||
or Home Assistant connections. History is kept only in RAM and starts empty
|
||||
following reboot; firmware updates therefore also clear it. There are no flash
|
||||
writes for history. Delayed sampling is represented by actual timestamps, not
|
||||
fabricated catch-up samples.
|
||||
|
||||
## Isolation, security, and resource use
|
||||
|
||||
@@ -119,7 +139,13 @@ IDF's own timeouts for receiving headers or sending responses.
|
||||
A fixed 1-second `PollingComponent` serializes only the three configured sensors
|
||||
on the main loop. A mutex protects replacement and copying of the cached JSON;
|
||||
HTTP sends happen after releasing it. The HTTP task never reads live sensors or
|
||||
the entity registry. Uninitialized, missing, NaN and infinite states become
|
||||
the entity registry. The history ring uses 601 compact 16-byte samples (9,616
|
||||
bytes), plus small bookkeeping fields. Each history request allocates a bounded
|
||||
heap snapshot of approximately the same size, then streams JSON in chunks of at
|
||||
most 512 bytes; the ring is not copied onto the small HTTP task stack and no
|
||||
whole-response JSON string is allocated. Allocation failure returns 503, and
|
||||
send failures close the connection. Sample ages use rollover-safe uptime
|
||||
arithmetic and old samples are pruned on the main loop. Uninitialized, missing, NaN and infinite states become
|
||||
`null`. Sensor expiry stays with the parent configuration's existing filters
|
||||
(e.g. `timeout: 30s`); this component adds no independent sample-age policy.
|
||||
|
||||
@@ -128,7 +154,9 @@ the entity registry. Uninitialized, missing, NaN and infinite states become
|
||||
APIs were inspected against installed ESPHome 2026.10.2 and local ESP-IDF headers.
|
||||
Host checks cover schema/socket registration and codegen, C++ routing/serialization
|
||||
with API stand-ins (including send/shutdown/close ordering and close-queue
|
||||
failures), and the embedded JavaScript (threshold boundaries, missing
|
||||
and offline states, timeout and sequential polling). These are not a firmware or
|
||||
failures, history retention/wraparound, uptime rollover, allocation failures, and
|
||||
chunked-send failures), and the embedded JavaScript (threshold boundaries,
|
||||
missing and offline states, chart bounds/gaps, timeout and sequential polling).
|
||||
These are not a firmware or
|
||||
on-device integration test. The parent integration must build and check both
|
||||
listeners on hardware, including captive portal operation while Wi-Fi is down.
|
||||
|
||||
Reference in New Issue
Block a user