How I taught my garage camera to remember bin day
The problem
Bin day always sneaked up on me. Miss the pickup of any of the four bins and you’re stuck with overflowing waste for another two weeks — bio, packaging, paper, residual, all equally annoying when they pile up. I tried calendar reminders, smart speaker nudges, a sticky note on the kitchen counter. None of it stuck.
What I needed wasn’t another reminder. I needed a system that knew whether the bin was already out and only nagged me when it wasn’t.
The setup at a glance
- Reolink garage cam — already there, 24/7 RTSP, sees the four bins lined up against the wall
- GPT-4o vision — gets a snapshot every evening before pickup, returns which bins are visible inside
- Waste collection schedule integration for Home Assistant — knows tomorrow’s pickup type
- n8n — orchestrates the snapshot → vision → calendar → notification chain
- Telegram bot — sends the actual photo so I can verify with my own eyes
Step 1: the snapshot
The garage cam already exposes an RTSP feed. Home Assistant has it as camera.garage. The first step is just grabbing a still frame:
// In n8n Code node
const ha_token = fs.readFileSync('/path/to/ha_token','utf8').trim();
const snapshot = await fetch('http://homeassistant:8123/api/camera_proxy/camera.garage', {
headers: { 'Authorization': 'Bearer ' + ha_token }
});
const buffer = Buffer.from(await snapshot.arrayBuffer());
const b64 = buffer.toString('base64');
Step 2: don’t ask the AI to find the bins — tell it where they are
My first version was naive: I sent the full garage snapshot to GPT-4o and asked “which of the four bins do you see?”. It worked maybe 70% of the time. The model confused the recycling bin with a storage crate, hallucinated bins that weren’t there, missed the bio bin behind a bike. Unusable for an alert that has to be trustworthy.
The fix: manually calibrated bounding boxes. The bins always live in the same four spots against the back wall. I measured those spots once on a reference frame (640×480) and hardcoded them as sectors:
const SECTORS = {
A: { x:10, y:165, w:140, h:235, label: "Paper", color: "blue" },
B: { x:145, y:170, w:95, h:210, label: "Residual waste", color: "black" },
C: { x:240, y:110, w:80, h:215, label: "Bio", color: "brown" },
D: { x:315, y:120, w:110, h:250, label: "Recyclable", color: "yellow" },
};

Calibration is a one-time job: take a snapshot when all four bins are in position, measure each sector in any image editor, paste the coordinates. Five minutes of work that doubles the accuracy.
The workflow then scales each new snapshot to 640×480 (so the sectors stay valid even if the camera resolution changes) and crops one image per sector with ffmpeg piped in/out of memory — no temp files:
function ffmpeg(inputBuf, vfArg) {
return new Promise((resolve, reject) => {
const ff = spawn("ffmpeg", ["-loglevel","error","-i","pipe:0","-vf",vfArg,"-f","mjpeg","-q:v","5","pipe:1"]);
const chunks = [];
ff.stdout.on("data", c => chunks.push(c));
ff.on("close", code => code === 0 ? resolve(Buffer.concat(chunks)) : reject(code));
ff.stdin.end(inputBuf);
});
}
const baseBuf = await ffmpeg(snapshotBuffer, "scale=640:480");
const cropBuf = await ffmpeg(baseBuf, `crop=${spec.w}:${spec.h}:${spec.x}:${spec.y}`);
Each cropped image is now a tightly framed picture of exactly one slot. The GPT-4o call is correspondingly simple — instead of “find four bins in this scene”, it answers one binary question: is a bin sitting in this slot, yes or no?
const prompt = `You see a tightly cropped image of a small area in a garage.
First describe in 1-2 sentences what you ACTUALLY see (floor? wall? door? plastic bin? bicycle?). Invent NOTHING.
Then decide:
- bin_visible = TRUE only if you CLEARLY see a wheelie bin body (rectangular plastic container with wheels at the bottom and a distinct lid on top) taking up MORE than half the image
- bin_visible = FALSE if the image shows mostly door, wall, floor, shadow, bike, or other objects
- bin_visible = FALSE when in doubt
Respond as JSON only:
{"what_i_see": "concrete observation in 5-10 words", "bin_visible": true/false}`;
The four sector calls run in parallel — about 2 seconds end-to-end. The mapping back to bin types is trivial because each sector already has its label attached.
Three small choices that mattered:
- Force a “what_i_see” field before the boolean. Asking the model to describe what’s actually in the crop before deciding bin_visible suppresses hallucinations — it has to commit to “I see a door” before it can claim “bin visible: true”
- Explicit bias toward FALSE on uncertainty. A missed alert (false negative) is fine — I’ll see the bin still in the garage on my next walk-by. A wrong “bin is outside” message would erode my trust in the whole system
- temperature: 0 + max_tokens: 150. Stable, parseable, cheap — about 0.5 cents per evening check across all four sectors
Step 3: the calendar cross-check
Home Assistant pulls in the local pickup schedule via the Waste Collection Schedule integration. The relevant sensors look like this:
sensor.next_bio_pickup // "in 1 day"
sensor.next_residual_pickup // "in 10 days"
sensor.next_paper_pickup // "in 5 days"
sensor.next_recyclable_pickup // "in 3 days"
The workflow runs every evening at 20:45 and checks whether any of these sensors say “in 1 day”. If yes, that bin needs to be outside before morning.
Step 4: the photo notification
If the relevant bin is still listed in bins_visible from the vision call, Telegram fires off the alert with the actual snapshot attached:
🟤 Bin check: Bio bin (tomorrow)
⚠️ The bio bin is still in the garage!
Please take it out NOW.
📷 All four bins are still in the garage, including the bio
bin scheduled for tomorrow's pickup.
The photo is what makes this work. A text-only alert is easy to dismiss with “I’m sure I took it out”. A photo of the bin literally still sitting there is not.
What I learned
Hand-calibrated bounding boxes beat letting vision models find their own. The all-in-one prompt struggled. The per-sector binary prompt is nearly perfect — including at night with the camera in IR mode, because the model only has to answer “is there a bin-shaped object in this small frame” instead of color-identifying four bins in a wide scene.
Run it the evening before, not the morning of. My first version ran at 06:00 on pickup day. Useless — by then I was already drinking coffee and the truck was halfway down the street. The evening check gives me time to deal with it during normal household hours.
Snapshot proof beats text proof, every time. Once the alert includes a photo of the bin still in the garage, there is no debate. I get up and roll it out.
Results
Two weeks in, zero missed pickups. One genuinely satisfying “told you so” each pickup evening from a smart home that’s just doing its job.
Leave a Reply