Whee Lil' Bin

Whee Lil' Bin is a bin-day indicator: a Raspberry Pi Zero 2 W in a printed wedge that reads a council's collection calendar overnight and lights one of two miniature wheelie bins on its lid — green for general waste, blue for recycling. Python scrapes the calendar, Docker keeps it running, and two GPIO pins do the talking. This page is the finished object, annotated.

Which bins go out tonight?

A client came to us with a small, real, weekly problem. Two bins, alternating weeks, and a council calendar that lives behind a form you have to fill in with your postcode and pick your address from a list. Nobody remembers. Everybody guesses. Half the street guesses wrong. The ask was not an app. It was a thing that simply knows, and says so without being asked.

Board
Pi Zero 2 W
Enclosure
3 printed parts
Output
2 lamps
Wakes
00:05 daily
Requests
1 a day
Whee Lil' Bin is a bin-day indicator: a Raspberry Pi Zero 2 W in a printed wedge that reads a council's collection calendar overnight and lights one of two miniature wheelie bins on its lid. Green for general waste, blue for recycling. Python scrapes the calendar, Docker keeps it running, and two GPIO pins do the talking.

Three printed parts, one wedge.

Shell, base and tray, pulled apart along the axis they go together on. The tray is drawn clear of the base because it slides in from behind rather than stacking under it.

  1. The shell The only part anyone sees. Every cutout is in this piece.
  2. The base 8 mm of floor, with the back plate rising off it. Split from the shell so both print flat, with no supports inside.
  3. The tray Holds the power bank upright on three printed fingers, against a back wall. It lifts out with the cell still in it.

Every hole is a decision.

The lid holds the whole interface, and there is nothing on it that does not earn its cutout.

  1. A plate off the back A flat plate stands off the back of the lid with two screw holes in it. In service the wedge simply stands on a surface, and the holes have not been used.
  2. A wedge, so the face looks at you 49 mm tall at the back, 28 mm at the front. That taper turns a lid you would have to lean over into a face that looks back at you.
  3. Two holes for two lamps An LED under each, a translucent toy wheelie bin over each. The bins are the display.
  4. A switch for the screen The screen is the detail, not the message, and a lit one in a hallway at night is a nuisance. This kills it without touching the lamps.
  5. A window cut to the screen Sized to the OLED's glass, not its breakout board, so none of the electronics shows.

inside the wedge

Board
Raspberry Pi Zero 2 W
OS
Debian 12
Runtime
Docker, arm64
Lamp pins
GPIO 20, 26
Bin lamps
2 — green, blue
Screen
OLED, I²C
Power
USB, 5 V
Survives
Power cuts

the errand

The council knows. It just will not say.

There is no feed and no API. The collection calendar is a web form: post a postcode, get a list of addresses back, each with a UPRN — the Unique Property Reference Number that Britain quietly assigns to every letterbox in the country. Post the UPRN and a month, and a page of HTML comes back with the collections drawn on a calendar. So the errand is: find the house, then read the month. Twice, if the next collection falls past the end of it.

tidied_web_scraper.py Finding the house. Endpoints and address redacted.
class BinScraper:
    def get_uprn(self):
        form_data = {'Postcode': self.postcode}
        response = requests.post(
            COUNCIL_FORMS + '/addresslist', data=form_data
        )

        if response.status_code == 200:
            addresses = response.json()['Model']['PostcodeAddresses']

            for address in addresses:
                if self.address_lines[0] in address['Address']:
                    self.uprn_value = address['UPRN']
                    return self.uprn_value
            print('UPRN not found.')

        return None

the reading

Two letters is the whole data model.

The calendar comes back as a grid of day cells. Each cell that has a collection carries the words for it, so the parse is honest and short: walk the days, look at the text, and mark it H for household waste or R for recycling. Everything the device does after this is a consequence of one of those two letters. The screen in the lid spells the answer out for anyone who wants the date as well as the colour.

tidied_web_scraper.py Reading the month.
days = soup.find_all('div', class_='cal-day-inmonth')

for day in days:
    day_no = day.find('span', class_='day-no')
    event_container = day.find('div', class_='events-list')

    if day_no and event_container:
        event_type = None
        event_text = event_container.get_text().strip().lower()
        if 'household waste' in event_text:
            event_type = 'H'
        elif 'recycling' in event_text:
            event_type = 'R'

        if event_type:
            events.append({
                'date': day_no['data-cal-date'],
                'day_name': day_name.text,
                'event_type': event_type,
            })
Source
Council calendar
Lookup
Postcode → UPRN
Parsed with
BeautifulSoup 4.12
Data model
H or R

the watchman

A Pi Zero 2 W that never sleeps.

The whole thing runs on a Raspberry Pi Zero 2 W in a container, so the machine can be reflashed, moved or rebuilt without anyone having to remember which Python packages it needed. The image is built for arm64 because that is what the board is, and a systemd unit starts it at boot so the device comes back on its own after a power cut. It wakes just after midnight, reads the month, decides, and goes quiet again. One HTTP request a day, to be the thing in the room that knows.

Dockerfile Built for the board it actually runs on.
# An official Python base for ARM (Raspberry Pi)
FROM arm64v8/python:3.11-slim

WORKDIR /app
COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

CMD ["python", "wheelilbin.py"]

the signal

Two lamps, and nothing else.

The output is deliberately dumb, and that is the point: no screen to read, no app to open. Two LEDs sit under two toy wheelie bins on the lid. Green lights for general waste. Blue lights for recycling. Two GPIO pins is the whole of it. An earlier version had fourteen more — a pair for every day of the week, to spell out when as well as which — and it made the lid harder to read at a glance rather than easier. The date moved to the OLED behind the window, where a date belongs, and the lamps went back to answering one question.

display_leds.py The entire output layer.
recyclingLED = LED(26)
householdLED = LED(20)

lit

The bin that lights up, is the bin that goes out.

This is the whole interface. The scraper has found tomorrow's collection, the controller has turned on one of two pins, and a translucent toy bin on the lid is now glowing the colour of the bin you need to wheel to the kerb. No notification, no login, no battery to charge in the thing you are trying to remember. Just a light in the room, on the nights it matters.

the vessel

Modelled around the board, printed in three parts.

The enclosure went through two versions and only the second was built. It was drawn around the components rather than the other way round: the Pi's footprint set the floor, the OLED's glass set the window, and the plate at the back was added last. Splitting it into three parts is what makes it printable without supports inside the enclosure. Every part has a flat face to sit on and no overhang steeper than a Flashforge Adventurer 3 will bridge.

Version
V2, the one built
Parts
Base, tray, shell
Body
106 × 98 mm
Printer
Flashforge Adventurer 3
Material
Black PLA

in service

On the shelf, and quiet about it.

It runs off a USB power bank standing behind it, and does one thing on a schedule nobody has to think about. The best outcome for a device like this is that it becomes furniture; that after a fortnight nobody notices it, and the right bin simply goes out every week.