System Architecture

Status: 🟢 Stable


Two clearly separated subsystems

The system is split into two independent parts that communicate asynchronously:

  1. The weather station — a low-power field device. Measures, stores, sleeps.
  2. The backend — a home server that ingests, stores, and visualizes data, and runs predictive models.

These two parts are never directly connected. The phone acts as a physical data mule — an asynchronous gateway.


Data flow overview

flowchart TB
    subgraph Station["🌦️ Weather Station (ESP32)"]
        S1[Sensors] --> MCU[ESP32]
        MCU --> SD[SD Card]
        MCU --> AP[Wi-Fi AP<br/>SERVER mode]
    end

    subgraph Phone["📱 Phone Gateway"]
        DL[Download logs<br/>via browser / script]
        UL[Upload to server<br/>over home Wi-Fi]
        DL --> UL
    end

    subgraph Backend["🖥️ SBC Server"]
        API[REST API<br/>FastAPI]
        DB[Database<br/>TinyDB / InfluxDB]
        DASH[Dashboard<br/>TBD]
        ML[ML Models<br/>RF + LSTM]
        API --> DB --> DASH
        DB --> ML
    end

    AP -->|"User walks up with phone"| DL
    UL -->|"Home Wi-Fi"| API

There is no always-on internet connection at the station. The station operates fully autonomously. Data is collected on the SD card and pulled manually (or on a schedule) by the phone.


Station — operating principle

The station follows a wake-do-sleep cycle. The ESP32 spends the vast majority of its life in deep sleep, consuming microamps.

sequenceDiagram
    participant RTC as DS3231 RTC
    participant ESP as ESP32
    participant SD as SD Card

    RTC->>ESP: Alarm interrupt (every N minutes)
    ESP->>ESP: Boot & evaluate FSM state
    ESP->>RTC: Read timestamp
    ESP->>ESP: Read all sensors
    ESP->>SD: Append CSV row
    ESP->>RTC: Set next alarm (now + interval)
    ESP->>ESP: Enter deep sleep

Key principle: no code runs during deep sleep. Every wake-up is a fresh boot.


Station — finite state machine (FSM)

At every boot, the firmware evaluates the system state and executes exactly one role:

stateDiagram-v2
    [*] --> BOOT

    BOOT --> CHECK_WAKEUP : esp_sleep_get_wakeup_cause()

    CHECK_WAKEUP --> INIT_RTC : Power-on (UNDEFINED) AND RTC invalid
    CHECK_WAKEUP --> READ_SWITCH : Power-on (UNDEFINED) AND RTC valid
    CHECK_WAKEUP --> DATA_LOGGER : EXT0 — RTC alarm interrupt
    CHECK_WAKEUP --> SERVER : EXT1 — switch set to SERVER

    READ_SWITCH --> SERVER : Mode switch = SERVER
    READ_SWITCH --> DATA_LOGGER : Mode switch = MEASURE

    SERVER --> INIT_RTC : Long button press (NTP sync)
    INIT_RTC --> SERVER : NTP sync successful

    DATA_LOGGER --> DEEP_SLEEP : Measurements written
    SERVER --> DEEP_SLEEP : Mode switch → MEASURE

    DEEP_SLEEP --> [*]

At every boot, esp_sleep_get_wakeup_cause() is called first. Three wakeup causes are handled:

  • UNDEFINED (power-on): RTC validity is checked — if never set, go to INIT_RTC; if already valid, sample the mode switch (READ_SWITCH).
  • EXT0 (RTC alarm interrupt): go directly to DATA_LOGGER.
  • EXT1 (mode switch GPIO): fires only when the switch is moved to the SERVER position — device was already initialised, go directly to SERVER. The return to MEASURE is handled in software while SERVER is active (no wakeup needed).

This requires the mode switch to be wired to an EXT1-capable GPIO and registered as a wakeup source, which is what disambiguates a switch-flip from a fresh power-on. States are mutually exclusive. The FSM is deterministic and fully observable via the status LED — see the Firmware — Status LED section for the full LED behaviour table.


Phone gateway — operating principle

The phone acts as a store-and-forward relay:

  1. User walks to the station with their phone
  2. Phone connects to the station’s Wi-Fi AP (SERVER mode)
  3. User downloads the latest CSV log file(s) via the browser
  4. When back on home Wi-Fi, the user opens the server web UI and uploads the file directly from a browser form

This approach requires zero infrastructure at the station location and no app or script on the phone — only a browser. The station can be in a garden, a field, or a rooftop — anywhere.


Backend — server role

The SBC is the home server. It is always on (or scheduled) and provides:

  • Data ingestion — REST endpoint that accepts uploaded CSV batches
  • Storage — time-series database (TinyDB prototype → InfluxDB production)
  • Visualization — web dashboard with historical data and derived metrics (Grafana, Plotly Dash, or similar — TBD)
  • Inference — runs trained ML models to produce watering recommendations and short-term weather forecasts

The backend is fully independent of the station. It can operate, process, and display data even if the station has been offline for days.


Key design invariants

These do not change across phases:

Invariant Value
Time authority DS3231 RTC
Primary storage SD card (append-only CSV)
Station-server link Asynchronous (phone relay)
Data format YYYY-MM-DD HH:MM:SS,fields...
Firmware model Wake-do-sleep FSM
Internet at station Never required
Note

Future consideration: direct upload from the station (Wi-Fi or cellular modem) could bypass the phone relay entirely, eliminating the manual data-mule step. Worth keeping in mind once the project reaches sufficient maturity.