Inspiration

We spend a third of our lives in the bedroom, with the door closed and the air slowly getting warmer, stuffier, and more humid, and we never see it. Sleep trackers measure the person. We wanted to measure the room: what is the room doing while you sleep, and what can you actually change?

What it does

Hypnos is a bedside monitor, in a 3D-printed case, that scores your sleep environment.

  • Reads estimated CO₂ (eCO₂), temperature, and humidity every minute from sensors on an Arduino UNO Q, plus light and sound from a USB webcam
  • Scores the room 0–100 against targets drawn from bedroom-air, sleep, and WHO noise research, with a plain-language reason ("Room is 6.6 °F above target")
  • Sleep mode on the device: tap the touch screen at bedtime, hold it in the morning. You get a nightly report with the score, time in sleep mode, the lowest-scoring metric, noise events, and charts for each metric, plus a calendar of every night colored by score
  • An AI assistant answers questions like "Why was my score only Fair?" using the real data, and every number it says is automatically verified against the sensor readings
  • Private by design: everything runs on the device. Webcam frames and audio are reduced to two numbers in memory and never stored. Only chat questions leave the room.

How we built it

The UNO Q has two processors, and we used both:

  • Microcontroller (STM32, Zephyr): an Arduino sketch reads the CCS811 (eCO₂/TVOC over I²C) and DHT11 (temperature/humidity), feeds the DHT11 values back into the CCS811 for compensation, drives a 3.5" SPI touch screen, and talks to Linux over the board's MessagePack-RPC router: it sends readings and asks the backend to start or end sleep mode.
  • Enclosure: we designed and 3D-printed a case that holds the UNO Q, the sensors, and the touch screen, turning a breadboard prototype into a single device that sits on a nightstand.
  • Linux side, all Rust: a layered backend (controllers → services → repositories) on Tokio + Axum:
    • a router client that registers for readings and device calls, and reconnects automatically
    • webcam adapters: a 32×24 grayscale frame at locked exposure for light, and per-minute sound levels (average and peak) from the mic
    • validation that flags warm-up, missing, and out-of-range readings
    • SQLite storage
    • pure, unit-tested scoring functions
    • a REST API plus Server-Sent Events for live updates
  • Dashboard: a React + TypeScript web UI, designed in Figma first and embedded in the backend binary, so the device needs no Node and works offline.
  • AI agent: runs on Groq (gpt-oss-120b) with tool calling. The model never touches the database or hardware; it can only call four tools (get_current, get_summary, get_night_latest, get_targets) that go through the same services as the dashboard.

Each minute's score is the average of the sub-scores available (eCO₂, temperature, and humidity, plus light and sound when the webcam is connected):

$$S_{\text{minute}} = \frac{1}{n}\sum_{i=1}^{n} S_i$$

The nightly score is the average across the sleep session, and nights with less than 60% valid data are marked incomplete instead of scored.

The grounding check

LLMs make up numbers. So after every reply, the backend extracts each number the assistant said and checks it against that turn's tool results (or the user's own question). The reply is marked ✓ verified or ⚠ unverified in the chat. On the real board, this caught the model inventing a "68 °F" setpoint, which led us to change the prompt so it points to the target range instead.

Challenges we ran into

  • Embedded Rust on a brand-new board. We planned to write the microcontroller firmware in Rust. The only community framework had no sensor support and replaced the official router, so after timeboxing it we went hybrid: a C++ sketch on the microcontroller, and everything on Linux in Rust.
  • Silent failures on the UNO Q. Serial.println printed nothing, and sketches failed silently when a library wasn't added to that specific app. The touch screen stayed white because a graphics-library member named WIDTH was hiding our own constant.
  • Our CO₂ sensor doesn't measure CO₂. The CCS811 estimates it from VOCs, so it barely reacts to breath. We label it "eCO₂ (estimated)" everywhere, and the pipeline is sensor-agnostic: a true CO₂ sensor like the SCD41 is a one-driver swap.
  • A microphone that hears itself. Calibrated against a phone sound meter, the webcam mic was accurate for loud sounds but read a silent room 6.6 dB too loud: its own electronic hiss. A single offset can't fix both, so we subtract the mic's noise as energy (quiet: 42.6 → 36.1 dB; loud: 57 → 56.9). The Linux audio system also kept resetting the mic gain to maximum, so the backend now re-sets it every minute.
  • Numbers that almost matched. 32-bit floats stored 49.8% as 49.7999992…, and scores computed from unrounded values didn't match what was displayed. We now score from the same rounded values the API shows, so the dashboard, the agent, and the grounding check all agree.

Accomplishments that we're proud of

  • Real sensor data flowing end to end: microcontroller → router → Rust → SQLite → touch screen and live browser dashboard
  • A finished-looking device: a custom 3D-printed case, not just a board on a desk
  • An AI assistant that is checked instead of trusted
  • Real nights recorded on the device, with a history calendar
  • A sound measurement calibrated against a reference meter at both quiet and loud levels
  • 119 unit and handler tests, with clean clippy

What we learned

How a dual-processor board splits work between real-time hardware and Linux; MessagePack-RPC; building a layered async Rust service; SSE streaming; tool-calling agents; that a cheap webcam can be an honest light and sound meter if you calibrate it; and that validating an AI's output is often more valuable than a better prompt.

What's next for Hypnos

  • A true CO₂ sensor (SCD41) and calibrated light thresholds
  • Embedded Rust firmware on the microcontroller
  • A closed loop: let the agent turn on a fan when CO₂ climbs, gated by a safety layer
  • Multi-night trends in the assistant
  • An optional cloud mirror with Google sign-in, one account per device (built, not deployed)

AI tools disclosure

We used Claude and Claude Code for planning, code generation, and review, and Figma's AI tools for mockups. The in-app assistant runs on Groq (openai/gpt-oss-120b).

Built With

Share this project:

Updates

posted an update —

Research behind the scoring targets

  1. Fan et al., 2022 (window/door opening): https://doi.org/10.1016/j.buildenv.2022.109630
  2. Fan et al., 2022 (ventilation and temperature): https://doi.org/10.1016/j.buildenv.2021.108666
  3. Kang et al., 2024 (1,000 ppm CO₂ affects sleep): https://doi.org/10.1016/j.buildenv.2023.111118
  4. Okamoto-Mizuno et al., 1999 (humid heat and sleep): https://doi.org/10.1093/sleep/22.6.767
  5. Yan et al., 2024 (window/door opening in summer): https://doi.org/10.1016/j.buildenv.2023.111024
  6. Arundel et al., 1986 (indoor humidity): https://doi.org/10.1289/ehp.8665351
  7. US EPA, A Brief Guide to Mold, Moisture and Your Home: https://www.epa.gov/mold/brief-guide-mold-moisture-and-your-home
  8. WHO, Guidelines for Community Noise (1999): https://iris.who.int/handle/10665/66217
  9. Mason et al., 2022 (light during sleep): https://doi.org/10.1073/pnas.2113290119

Log in or sign up for Devpost to join the conversation.

Submission history