#pragma once #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/web_server_base/web_server_base.h" #include #include #include namespace esphome::air_quality_web { // Separate IDF listener: do not use WebServerBase or its global instance/headers. // The stock begin() has a shared control port and generic POST body processing. class AirQualityServer : public AsyncWebServer { public: AirQualityServer() : AsyncWebServer(8080) {} void set_port(uint16_t port) { this->port_ = port; } esp_err_t start(esp_err_t (*handler)(httpd_req_t *), void *context); }; class AirQualityWeb : public PollingComponent { public: AirQualityWeb() : PollingComponent(1000) {} void set_port(uint16_t port) { this->server_.set_port(port); } void set_mdns_hostname(const std::string &hostname) { this->mdns_hostname_ = hostname; } void set_co2_sensor(sensor::Sensor *value) { this->co2_ = value; } void set_temperature_sensor(sensor::Sensor *value) { this->temperature_ = value; } void set_humidity_sensor(sensor::Sensor *value) { this->humidity_ = value; } void setup() override; void update() override; void on_shutdown() override { this->server_.end(); } float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION - 1.0f; } protected: static esp_err_t handle_request_(httpd_req_t *request); std::string snapshot_(); esp_err_t send_history_(httpd_req_t *request); static constexpr uint32_t HISTORY_DURATION_MS = 36000000; static constexpr uint32_t HISTORY_INTERVAL_MS = 60000; static constexpr size_t HISTORY_CAPACITY = 601; struct HistorySample { uint32_t timestamp; float co2; float temperature; float humidity; }; static_assert(sizeof(HistorySample) == 16, "History samples must remain compact"); struct HistorySnapshot { uint32_t timestamp; size_t count; HistorySample samples[HISTORY_CAPACITY]; }; AirQualityServer server_; std::string mdns_hostname_; sensor::Sensor *co2_{nullptr}; sensor::Sensor *temperature_{nullptr}; sensor::Sensor *humidity_{nullptr}; Mutex snapshot_mutex_; HistorySample history_[HISTORY_CAPACITY]{}; size_t history_head_{0}; size_t history_count_{0}; uint32_t last_sample_ms_{0}; bool has_sample_{false}; std::string json_{"{\"co2\":null,\"temperature\":null,\"humidity\":null}"}; }; } // namespace esphome::air_quality_web