My submission for the Hacktoberfest Open-Source AI Challenge, Week 3 (Slow and Steady). Built this week, open-weight model at its core, runs offline.
Every maintenance team has a dashboard everyone agreed to check each morning. Nobody checks it.
Most mornings that is fine, because most mornings nothing moved. The problem is the one morning a bearing is quietly cooking itself toward a seizure. That is also the morning nobody is looking.
So for International Sloth Day I fixed it the lazy way. Not with a bigger dashboard. With a sloth that reads the night shift for me and hands me two lines.
Meet Edge Sensor Sloth
I build industrial IoT gateways: a little ESP32 box on a factory floor that reads a pump or a motor over Modbus and ships the numbers out over MQTT. By the end of a shift that is a few thousand rows of temperature, current, vibration, power, flow. Nobody wants to read that. Including me.
Edge Sensor Sloth reads it instead and gives back a digest a human actually acts on:
That is real output, from gpt-oss-120b, an open-weight model, on a day of telemetry with two faults buried in it: a slow bearing-wear drift, and a thermal blip that already recovered. The sloth put the one that matters first, and told me the blip was nothing. That is the whole job, and it is the job I never did.
Laziness is a feature, not a bug, when the alternative is a human squinting at five trend lines before coffee.
Two moving parts
flowchart LR
A[Sensor CSV] --> B[detect.py: EWMA z-score + drift]
B -->|a few numbers, not raw data| C[digest.py]
C -->|OpenAI-compatible| D{{Open-weight model: gpt-oss / Gemma / Qwen}}
D --> E[Two-line morning digest]
C -.no endpoint?.-> F[Deterministic fallback]
F --> E
A lazy detector. Pure numpy, no training, no model to download, runs on the gateway itself. An EWMA baseline with a robust z-score catches spikes; a long-window level check catches slow drift that a short baseline would quietly track and hide.
The part I am quietly proud of is three lines: a spike that already recovered gets its urgency down-weighted, so a live bearing-wear drift always outranks a thermal blip from three hours ago that fixed itself. That is the difference between a digest that says "here is what to do today" and one that just lists everything that twitched.
An open-weight model as the voice. The detector hands over a handful of numbers (vibration 3.36, +27%, drift). An open-weight LLM turns that into "possible bearing wear" in words you read at 7am without a stats degree. That translation, numbers into a decision, is the part that was always missing.
Why open innovation is the whole point here
This is not open-for-the-badge. The project genuinely does not work as a closed-API product:
- The plant's data never leaves the plant. Factory telemetry leaks production rates, downtime, process secrets. The detector runs locally and only ever hands the model a few anonymised numbers, never the raw feed. And because the model is open-weight, even those numbers can stay on-prem. You cannot promise a customer "your data never leaves your machine" on top of someone else's API. Here it is the default.
- It works where the internet does not. Plant rooms are not famous for reliable WAN. An open-weight model behind an OpenAI-compatible endpoint means the brain can be a laptop in the same room. A bearing does not stop wearing out when the connection drops, so the thing watching it cannot either.
- Swap the brain, keep the pipeline. gpt-oss today, a Gemma fine-tuned on your own failure history tomorrow, Qwen when it gets better. Open weights make that a one-line change instead of a vendor migration.
- It costs nothing to leave running. For a tool whose whole value is "always quietly watching," per-call pricing is the thing that gets it switched off.
That is the lazy payoff and the open payoff in one sentence: set it up once, offline, free, and it does the shift you dreaded, forever.
Reproduce the real output offline in 30 seconds
No cloud, no key. An open-weight model on your own machine via Ollama:
git clone https://github.com/GifariKemal/edge-sensor-sloth
cd edge-sensor-sloth && pip install -r requirements.txt
ollama pull gemma2 # or gpt-oss, qwen2.5 ...
export SLOTH_BASE_URL=http://localhost:11434/v1
export SLOTH_MODEL=gemma2
python sloth.py --make-sample
python sloth.py samples/plant.csv # two lines, from a model on YOUR laptop
Or skip the model entirely and watch the deterministic fallback still write you a line:
python sloth.py samples/plant.csv --no-llm
Bring your own CSV: a timestamp column and any numeric sensors, and the morning chore is gone.
Building it to stay lazy
- The detector does not learn. I considered a tabular foundation model, then remembered the theme. A robust z-score plus a drift check is ten lines of numpy and good enough to tell "bearing wear" from "sensor noise." Laziness is the feature: nothing to retrain when a site changes, just two thresholds to turn.
- Reasoning models needed room to think. gpt-oss is a reasoning model over a streaming endpoint. My first calls came back empty: it spent its whole token budget reasoning and got cut off before the two lines. The fix was boring (more headroom, and a stream parser that takes the answer but falls back to the reasoning channel if that is all that arrived). Boring is what you want in the thing that pages you at 3am.
- It degrades instead of failing. No endpoint, no internet, no problem: a deterministic template still hands you a usable line. The sloth never leaves you staring at a traceback at 7am.
Prize categories
- Overall: open-weight AI at the core, offline-capable, data stays local.
-
Best Use of Gemma:
SLOTH_MODEL=gemma2behind a local Ollama runs the whole thing on your laptop, fully offline. The pipeline is model-agnostic by design; Gemma is a one-line swap, shown above. -
Best Use of ElevenLabs: the sloth can read the digest to you out loud (
--speak), because the laziest report is the one you do not even have to read.
Repo: https://github.com/GifariKemal/edge-sensor-sloth
Set it up once. It does the shift you dreaded. Forever.
The sloth approves.

Top comments (0)