Inspiration

Most consumer air quality monitors do one thing: they trip a threshold and light up a red LED. They can't tell you what is happening, how bad it is, or what to do about it. A gas spike from cooking bacon and a gas spike from a slow gas leak look identical to a plain threshold alarm.

We wanted to build a edge device that notices something is wrong locally, in milliseconds, with zero cloud dependency, and then reaches out to a large language model to reason about why it's wrong and what a person should actually do. Qwen Cloud's speed and free tier made that second half possible on a hackathon budget, and Track 5 (EdgeAgent) was a perfect fit for what we'd already been prototyping with dual BME688 sensors sitting on a desk.

What it does

Aeris is a dual-sensor environmental monitor built on a Raspberry Pi Pico 2W that:

  • Detects anomalies entirely on-device, in under 100ms, using two BME688 gas/temperature/humidity/pressure sensors. Running two sensors side by side lets Aeris catch things a single sensor can't: if one sensor spikes and the other doesn't, that's a localised event (e.g. someone opening a window near one sensor), not a room-wide hazard.
  • Escalates to Qwen Cloud for diagnosis when an anomaly fires and WiFi is available. Instead of a blinking light, the operator gets a plain-English explanation — likely cause, severity, and a suggested action — generated from the actual sensor deltas between the two sensors.
  • Displays live status on an onboard SSD1306 OLED, with a scrollable menu for sensor readings, trend analysis, settings, and cloud status, navigable with two physical buttons.
  • Degrades gracefully offline. If WiFi drops mid-anomaly, the event is timestamped and queued to LittleFS rather than dropped, and syncs automatically once connectivity returns.
  • Feeds a live web dashboard (hosted on GitHub Pages) that polls a Flask backend running on Alibaba Cloud Function Compute, showing real-time sensor values, severity-coded alerts, and a chat box for asking Qwen follow-up questions about the current environment.

How we built it

Edge firmware (C++ / PlatformIO, RP2350): The Pico 2W runs dual Bosch BSEC2 sensor fusion instances in Ultra-Low-Power (ULP) mode, so the gas heater only cycles roughly every few minutes while temperature and humidity keep sampling every ~3 seconds. An AnomalyDetector module compares live readings against auto-tuned baselines (gas spike, temperature spike, humidity spike, and cross-sensor mismatch), and a TrendAnalyser module does least-squares slope fitting on recent samples to project how many minutes remain before a threshold will be crossed, so the dashboard can warn before a hard anomaly actually fires.

Offline-first queueing: An OfflineManager persists any detected anomaly to LittleFS immediately, independent of network state, so nothing is lost to a dropped connection. A separate CloudTaskQueue in RAM handles the actual Qwen API calls with basic rate-limiting between attempts.

Cloud diagnosis (Qwen / DashScope): When WiFi is up, the firmware builds a structured prompt containing both sensors' readings and the delta between them, and sends it to qwen-turbo via DashScope's OpenAI-compatible endpoint, asking for a diagnosis under 40 words plus one concrete action.

Serverless backend (Alibaba Function Compute, Flask): A lightweight Flask app receives sensor payloads and heartbeats from the Pico, exposes a /api/latest endpoint for the dashboard, and handles CORS so the GitHub Pages frontend can poll it directly.

Dashboard (GitHub Pages): A single static page polls the backend every 3 seconds, color-codes the current severity (normal / moderate / high / critical), shows trend icons for rising/falling gas and temperature, and includes a small chat interface to ask Qwen ad-hoc questions about the current reading.

Challenges we ran into

A one-character bug broke WiFi for days. For a long stretch, the Pico's WiFi simply refused to come up — WiFi.status() always returned "no shield present," regardless of anything in our connection logic. The root cause turned out to be board = rpipico2 instead of board = rpipico2w in platformio.ini: the wrong board definition meant the CYW43439 wireless chip was never initialised in the first place. No amount of firmware debugging would have fixed it, because the problem was one line in a build config file. Lesson learned: verify the board definition first, before touching any connectivity code, on this platform.

Stale BSEC2 state silently broke sensor subscriptions. Switching sample rates (e.g. from ULP to LP mode) without deleting the sensor's persisted .state file causes BSEC2 to reject the new subscription with a sample-rate-mismatch error, and it isn't obvious from the symptom alone. We now gate state-file deletion behind an explicit version marker so rate changes don't require manual intervention.

Credential loading had an all-or-nothing bug. Our initial secrets-loading logic used a single "compiled credentials available" flag; if any one credential (WiFi, Qwen, or Alibaba key) was missing at compile time, it would silently fall back to stale cached values for everything, including credentials that were actually available. We split this into independent flags per subsystem so a missing Qwen key, for example, doesn't also block a perfectly good WiFi SSID from loading.

Serverless statelessness fights live telemetry. Alibaba Function Compute containers spin up and down independently, so a POST from the Pico and a subsequent GET from the dashboard aren't guaranteed to land on the same instance — a plain Python global for "latest reading" can silently reset. We're aware this is a real limitation of the current backend (see "What's next") and designed around it as far as time allowed for this submission.

Uploading directly from the Pico wasn't viable. We originally intended the Pico to sign and upload anomaly logs to Alibaba OSS directly, but computing the required request signature and managing the TLS handshake on a memory-constrained microcontroller caused timeouts and instability. We pivoted to a proxy pattern: the Pico sends a lightweight, unsigned JSON payload to the Function Compute endpoint, which is responsible for any further persistence — a pragmatic tradeoff between "technically most impressive" and "actually reliable within the hackathon timeline."

Accomplishments that we're proud of

  • Achieving differential anomaly detection in sub-100ms on highly constrained edge hardware, with zero dependency on the network for safety-critical evaluations.
  • Building a genuinely useful dual-sensor comparison (not just averaging two readings) that can distinguish a localised event from a room-wide one.
  • An offline queue that actually round-trips: anomalies detected with no WiFi present are still logged, timestamped, and synced automatically the moment connectivity returns, with no manual recovery step.
  • A trend-prediction layer that turns "an anomaly just happened" into "an anomaly is about to happen," using nothing more exotic than least-squares slope fitting on sensor history.
  • Being honest with ourselves about the seams. Several pieces of this system are deliberately built as stubs in the architecture diagram rather than dressed up as finished.

What we learned

-The BSEC2 fusion library is fussy about persisted state: Its serialized state encodes strict assumptions about the current sample rate, leading to cryptic errors if not managed perfectly.

  • "It compiles" isn't the same as "it's wired up": We found that our trend-analysis sampling function was defined and tested but never actually called from the main loop. It’s a stark reminder to trace features end-to-end, not just unit-by-unit.
  • Serverless is not "just a server": Holding state in a plain global works fine in local testing but breaks under real autoscaling.
  • LLMs are surprisingly good zero-shot interpreters of structured sensor data: We didn't have to train a custom ML model on the edge. Feeding Qwen a well-structured telemetry payload was enough to extract highly specific, actionable diagnostics.

What's next for Aeris

  • Physical actuation. Trigger a local fan, vent, or alarm automatically when a CRITICAL severity anomaly is confirmed, closing the loop from detection to physical response.
  • Circadian baselines. Learn time-of-day patterns so a normal 7am cooking spike isn't treated the same as an identical spike at 3am, reducing false positives without lowering sensitivity to real hazards.
Share this project:

Updates