# Sloth Clock: Slow and Steady Wins the Morning, an Offline AI Alarm That Rewards You for Sleeping In 🦥

> Source: <https://dev.to/abhinavkrishna3211/sloth-clock-slow-and-steady-wins-the-morning-an-offline-ai-alarm-that-rewards-you-for-sleeping-in-5bl7>
> Published: 2026-10-26 06:38:29+00:00

*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19)*

**Sloth Clock: the alarm clock that rewards you for sleeping badly.** 🦥

Every alarm clock on Earth punishes laziness. It rings at the same minute whether you slept like a log or stared at the ceiling until 3 a.m. Then it gives you a snooze button, so you can chop the last half hour into nine-minute pieces of broken, useless sleep.

Sloth Clock flips that. It **watches how you actually slept**, and if the night was rough, it **lets you stay in bed longer**, up to 30 extra minutes, decided once and up front. Then it wakes you the way a sloth would want to be woken: *slowly*. A ten-minute sunrise creeps across the LED matrix, a soft chime follows, and **Gemma 3 1B, running on the clock itself**, greets you as a sleepy, kind sloth and tells you *why* you got the lie-in.

06:45. Good morning, dear. Your sleep was a bit restless, so I've given you a longer rest.

That is a real message, written by the model on the board.

It is a small **physical AI agent**: it **senses** (radar), **decides** (night score + hard-coded health rules), and **acts** (light, sound, words), all on one board, offline.

Sleeping in is the easiest feature in the world to get wrong, so the sloth has rules it cannot break:

These limits are hard-coded in one small file, not settings. *Future-sleepy-me does not get a vote.*

Because of the "not two days in a row" rule, the most the sloth can give in a normal week is **4 lie-ins × 30 min = 2 hours** of extra sleep, calculated straight from the rules. It never gives you a late morning.

The theme isn't just the name. Every part of the clock is deliberately slow:

| Part | How it is slow and steady | 
|---|---|
| 🌅 Wake-up | A **10-minute sunrise** over 8 brightness steps instead of a blaring beep | 
| 🧠 The model | Gemma writes **one sentence a day** ,**45 minutes before you wake** , at a calm ~4.4 tokens/s. It has all the time in the world. | 
| 📟 The card | A **no-glow e-paper card** that refreshes only when something changes, and uses no power to keep showing it | 
| 🪜 Your to-do list | **One tiny step** on a job you keep postponing, sized by how well you slept. Bad night? The step gets smaller. | 

*Sloth Clock on the nightstand: UNO Q with the LED-matrix sunrise, the radar facing the bed, and the e-paper card.*

*Live page served by the UNO Q: breathing, heart rate and movement graphs, plus the night score with every term shown.*

*The last decision: the final alarm, the rules that fired, the full decision record and Gemma's message.*

You shouldn't have to sleep eight hours to see an alarm clock work (that would be a little *too* on-theme). **Demo mode** lets you lie down for a few minutes. When the nap ends, the clock scores it, makes its decision, and wakes you right away with a one-minute sunrise. Demo data goes into a separate database, so it never mixes with your real nights.

A bedside sloth that wakes you gently and moves the jobs you keep putting off forward, one tiny step a day.

Everything runs offline on an Arduino UNO Q.

Work in progress. Measured results…

| Path | What lives there | 
|---|---|
| `app/sketch/sketch.ino` | STM32 real-time side: radar line parsing, 10 s rows, LED-matrix sunrise, chime, button | 
| `app/python/` | Linux side: SQLite, night score, alarm decision, Gemma wake message, validator, web API | 
| `app/assets/` | The web page (plain HTML, CSS and JS, hand-drawn canvas charts, no frameworks) | 
| `firmware/radar_forwarder/` | Tiny sketch on the radar kit's XIAO ESP32C6 | 
| `firmware/epaper_card/` | The e-paper card firmware | 
| `tests/` | Decision, validator and night-score tests, plus the on-board Gemma message test | 

MIT licensed.

| Component | Role | 
|---|---|
| **Arduino UNO Q (4 GB)** | The whole brain: STM32U585 MCU + Qualcomm QRB2210 Linux on one board | 
| **Seeed MR60BHA2 60 GHz mmWave kit** (XIAO ESP32C6 inside) | Breathing, heart rate, presence and movement, contact-free | 
| **XIAO ePaper Display Board EE04 + 7.5" 800×480 mono panel** | The no-glow bedside card, with 3 buttons | 
| **INMP441 I2S microphone** | Rain detection from sound *features* only, never recorded audio | 
| **PAM8403 amplifier + small speaker** | The soft chime | 
| **Push button** | Dismiss the alarm | 

*Everything that goes into a Sloth Clock. All of it runs on USB power: low voltage, no mains, nothing that heats up or moves.*

*KiCad schematic: radar UART into the UNO Q, the INMP441 on the STM32's SAI pins (SCK = PB10, WS = D10, SD = A4), the PAM8403 on a PWM pin, and the dismiss button.*

`llama-server`` web_ui` brick.
An alarm clock is a strange mix of jobs. Part of it must be **hard real time** (the alarm *must* ring, the button *must* work). Part of it is **heavy and slow** (a language model). Most boards make you pick one. The UNO Q is **two computers on one board**, and Sloth Clock uses both for what each does best:

|  | ⚡ STM32U585 (Zephyr, real time) | 🐧 Qualcomm QRB2210 (Debian Linux) | 
|---|---|---|
| Sense | Read the radar stream, validate every line, summarise 10 s rows | Store the rows in SQLite | 
| Think |  | Compute the night score, decide the alarm with hard limits, run **Gemma 3 1B** locally | 
| Act | Drive the **built-in 13×8 LED matrix** sunrise, play the chime, read the button | Validate every message, serve the web page | 

They talk over the **Arduino Bridge** (MessagePack RPC), and every message stays tiny: one summary row of **8 integers every 10 seconds**, well under the Bridge's 256-byte limit. Over an 8-hour night that is **2,880 rows**, which is small enough to keep months of nights on the board.

The split is what makes it a *trustworthy* alarm clock:

```
MR60BHA2 radar ──UART──▶ XIAO ESP32C6 ──CSV, 1/s──▶ STM32 (validate, 10 s rows)
                                                        │ Bridge
                                                        ▼
            Linux: SQLite ▶ night score ▶ decision (hard limits) ▶ Gemma 3 1B ▶ validator
                                                        │ Bridge
                                                        ▼
                                  STM32: LED-matrix sunrise ▶ chime ▶ button
```

Every minute is checked for movement, presence and breathing regularity, then:

```
score = 100 − (restless % + 0.5 × irregular-breathing % + 10 × long awakenings)
```

Each term is a share of the minutes observed, so a 15-minute demo nap and a full night use the same scale. The web page shows every term, so you can see exactly why you got (or didn't get) your lie-in.

This was the most interesting part. A 1B model is lovely at sounding warm and terrible at facts. An alarm clock that *lies about what time it is* is worse than no alarm clock. So:

`6:45` is one time, not 6 and 45, and a range like `2:10–3:30` is never read as a minus sign)
The first version asked the model to include the time itself. It often didn't. And on two test nights where the alarm had **not moved at all**, it cheerfully announced "I've given you a little extra time". 😅 Each failure became a validator rule and a test.

Here are the results on the board, across 10 different decision records (good night, restless, rain, shifted yesterday, not enough data, and so on):

| Prompt version | ✅ Valid on first try | 🔁 Valid after retry | 📄 Fell back to template | 
|---|---|---|---|
| Model writes the time itself | 4 / 10 | 1 / 10 | 5 / 10 | 
| **Code writes the time, model writes the reason** | **8 / 10** | **2 / 10** | **0 / 10** | 

That took the share of messages written by the model from **50% to 100%**, with **zero** wrong numbers getting through.

More real messages from that run:

07:00. Good morning, dear. The rain is a bit gloomy, so I've given you a longer rest.

06:30. Good morning, dear. I've been waiting for you.

| Measured on the UNO Q | Result | 
|---|---|
| Generation speed (Gemma 3 1B, 3 threads) | **~4.4 tokens/s** | 
| Prompt processing | **6.5–7.4 tokens/s** | 
| First token, cold prompt | **2.4 s** | 
| Time per wake message | **20–38 s** | 
| Time budget before the alarm | **45 min** , so**over 70×** headroom over the slowest message | 

It is not perfect, and I'd rather say so. In one retry the model turned "you slept badly, but you already slept in yesterday" into "you've already had a restful sleep". Every number was right, but the tone was wrong. A rule-based validator can check facts; it can't fully check feelings. That's next on my list.

`lines=12, bad=1`, so the bad line was caught and counted, not crashed on` docs/measurements.md`
I used **Claude Code** as my coding assistant to write the software. Everything else was mine: the idea, choosing and wiring the hardware, the health rules, flashing and testing on the board, and every measurement in this post.

Because this device lives **in your bedroom, watching you sleep.** 🛏️

A closed model could write a slightly nicer sentence. It could not do it on a nightstand, offline, for free, without learning when you sleep.

😴 **Up to 30 minutes more sleep** on exactly the mornings you need it, and **up to 2 hours a week**, with zero risk of being late. The hard limit always wins.

🧠 **Zero decisions before coffee.** You never again bargain with a snooze button, half asleep, about whether you "can afford" five more minutes. The sloth already did the maths against your hard limit.

⌚ **Zero effort to track sleep.** No wearable to charge, no app to open, no button to press at bedtime. You just go to bed. The laziest possible sleep tracker is one you don't have to touch.

🪜 **Progress without willpower.** Tomorrow's card asks for one tiny step, never the whole job, and a smaller one after a bad night. Slow and steady.
