// CO2 monitor — small (10-port) board, CO2 sensor (SCD40), Screen, Light Grid.
// The screen shows the ppm with a gauge; the grid is green while the air is fine, amber above 1000 ppm, red above 1500.
// Nothing to press. The sensor needs about a minute after power-on before its first reading.
#include <Arduino.h>
#include "drivers/i2c_software.h"
#include "modules/air_quality/scd40.h"
#include "modules/display/st7735_tft.h"
#include "modules/led/neopixel.h"

ST7735_TFT    screen(SCR_SCLK, SCR_CS, SCR_MOSI, SCR_DC);
SoftwareI2C   bus(CO2_SDA, CO2_SCL);
SCD40         co2(&bus);
NeoPixelGrid  grid(GRID_A, GRID_B);

static const uint16_t AMBER_ABOVE = 1000, RED_ABOVE = 1500, GAUGE_MAX = 2000;
static uint16_t rgb(uint8_t r, uint8_t g, uint8_t b) { return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); }

enum Air { FINE, STUFFY, BAD, WAITING };
Air airOf(uint16_t ppm) { return ppm == 0 ? WAITING : ppm > RED_ABOVE ? BAD : ppm > AMBER_ABOVE ? STUFFY : FINE; }
uint16_t inkOf(Air a) { return a == BAD ? rgb(255, 70, 60) : a == STUFFY ? rgb(255, 180, 40) : a == FINE ? rgb(90, 210, 110) : rgb(120, 120, 120); }

uint16_t lastPpm = 0; bool sensorOk = false; uint32_t lastDraw = 0, bootAt = 0; bool pulse = false;

void lights(Air a) {
    if (a == BAD)         grid.setAll(pulse ? 255 : 120, 20, 10);   // red, pulsing
    else if (a == STUFFY) grid.setAll(255, 150, 20);
    else if (a == FINE)   grid.setAll(30, 200, 60);
    else                  grid.setAll(pulse ? 40 : 10, pulse ? 40 : 10, pulse ? 40 : 10);
    grid.show();
}

void redraw() {
    Air a = airOf(lastPpm); uint16_t ink = inkOf(a);
    screen.fillScreen(ST7735_TFT::COLOR_BLACK);
    // gauge: a bar along the bottom, 400..2000 ppm, with the two thresholds marked
    const int gx = 8, gy = 66, gw = 144, gh = 8;
    screen.drawRoundRect(gx, gy, gw, gh, 2, ST7735_TFT::COLOR_WHITE);
    int fill = lastPpm ? constrain((int)((gw - 4) * (float)(lastPpm - 400) / (GAUGE_MAX - 400)), 0, gw - 4) : 0;
    if (fill > 0) screen.fillRoundRect(gx + 2, gy + 2, fill, gh - 4, 1, ink);
    for (uint16_t t : { AMBER_ABOVE, RED_ABOVE }) { int x = gx + 2 + (gw - 4) * (t - 400) / (GAUGE_MAX - 400); screen.drawLine(x, gy - 3, x, gy + gh + 2, rgb(150, 150, 150)); }
    screen.setTextSize(1); screen.setTextColor(rgb(120, 120, 120)); screen.setCursor(gx, 50); screen.print("400"); screen.setCursor(gx + gw - 24, 50); screen.print("2000");
    // the number
    screen.setTextColor(ink); screen.setTextSize(4); screen.setCursor(12, 8);
    if (lastPpm) screen.print(lastPpm); else screen.print("----");
    screen.setTextSize(1); screen.setTextColor(ST7735_TFT::COLOR_WHITE); screen.setCursor(118, 30); screen.print("ppm");
    screen.setCursor(118, 10); screen.setTextColor(ink);
    screen.print(a == BAD ? "open a" : a == STUFFY ? "stuffy" : a == FINE ? "fresh" : sensorOk ? "warming" : "no sensor");
    if (a == BAD) { screen.setCursor(118, 20); screen.print("window"); }
    screen.display();
    lights(a);
}

void setup() {
    Serial.begin(115200); delay(200); Serial.println("[co2] boot");
    grid.begin(); grid.clear(); grid.show();
    screen.begin(); screen.setRotation(1);
    screen.fillScreen(ST7735_TFT::COLOR_BLACK); screen.setTextColor(ST7735_TFT::COLOR_WHITE); screen.setTextSize(2); screen.setCursor(20, 30); screen.print("CO2 monitor"); screen.display();
    bus.begin();
    sensorOk = co2.begin();
    Serial.printf("[co2] CO2 sensor: %s\n", sensorOk ? "found" : "NOT found");
    bootAt = millis();
    redraw();
}

void loop() {
    uint32_t now = millis();
    if (now - lastDraw > 1000) {
        lastDraw = now; pulse = !pulse;
        if (sensorOk && co2.isReady()) {
            uint16_t ppm = co2.readCO2();
            if (ppm != lastPpm) { lastPpm = ppm; Serial.printf("[co2] %u ppm  %.1f C  %.0f %%RH\n", ppm, co2.readTemperature(), co2.readHumidity()); }
        } else if (!sensorOk && now - bootAt > 5000) {
            sensorOk = co2.begin();        // keep looking for the sensor if it was plugged in late
            bootAt = now;
        }
        redraw();
    }
    delay(10);
}
