The basic, yet so useful bindicatior is a simple little indicator to notify you when rubbish or recycling is due. This consists of a single esp32 or esp8366 and a WS2182B LED or LED strip.
| Parts List |
|---|
| ESP32 or ESP8266 |
| NeoPixel ES2182B or any addressable LED |
| A mini bin thing |
Using the HACS integration in Home Assistant, I am able to retrieve a Calander entry of when my recycling and rubbish is due. We can use this data to trigger different colors and flash patterns to indicate what is due and when.
There are two options for this project, the first is to use WLED and use Home Assistant for all automations, this is less flexible, but WLED is pretty cool.
The other is esphome. The process is a little more manual for configuring flashing patterns etc. I will cover both in this article. Firstly, start by downloading and installing Waste Collection Schedule in HACS
All automations in this post assume you have a schedule or an entity to track your rubbish and recycling collection dates. I used Waste Collection Schedule available on HACS and supports a wide range of countries and APIs.
https://github.com/mampfes/hacs_waste_collection_schedule
Using WLED
If you choose to use WLED, firstly start by installing WLED on your esp32 or esp8266.
Once installed and connected to Wi-Fi, add it to home assistant using the WLED integration. Take note of the WLED light entity. In my case, light.bindicator.
Using the following automation, I will dim the LED to 5% when the sun is below horizon, and 100% when the sun is above horizon. The bin LED will be solid for the bin that is due for collection that week, when collection day is 1 day out, it will blink slowly. When the bin is due that day, it will blink fast. When that bin collection schedule is over, it will restart and show the next bin with a solid LED of that bin color until that bin is due for collection, then it will blink slowly when 1 day out and so on.
Below, I am using the entity sensor.all_waste_types provided by Waste Collection Schedule, this provides me all collection dates for all bins in 1 single entity.
- id: bin_wled_indicator
alias: "Bin WLED Indicator"
mode: restart
trigger:
- platform: state
entity_id:
- sensor.all_waste_types
- sun.sun
- platform: time_pattern
hours: "/1"
action:
- variables:
s: "{{ states('sensor.all_waste_types')|lower|trim }}"
bin_type: >-
{% if 'recycle' in s %}recycle
{% elif 'rubbish' in s %}rubbish
{% else %}unknown{% endif %}
days: >-
{% set d = s | regex_findall_index('(\\d+)', 0) %}
{% if d is not none %}{{ d|int }}{% else %}999{% endif %}
rgb: >-
{% if bin_type == 'recycle' %}[0,255,0]
{% elif bin_type == 'rubbish' %}[255,210,0]
{% else %}none{% endif %}
brightness: "{{ 255 if is_state('sun.sun','above_horizon') else 13 }}"
- choose:
- conditions: "{{ rgb != 'none' and days == 0 }}"
sequence:
- service: select.select_option
target: { entity_id: select.bindicator_palette }
data: { option: "Default" }
- service: light.turn_on
target: { entity_id: light.bindicator }
data:
effect: Solid
rgb_color: "{{ rgb }}"
brightness: "{{ brightness }}"
- delay: "00:00:00.3"
- service: number.set_value
target: { entity_id: number.bindicator_effect_speed }
data: { value: 255 }
- delay: "00:00:00.3"
- service: light.turn_on
target: { entity_id: light.bindicator }
data:
effect: Dissolve
brightness: "{{ brightness }}"
- conditions: "{{ rgb != 'none' and days == 1 }}"
sequence:
- service: select.select_option
target: { entity_id: select.bindicator_palette }
data: { option: "Default" }
- service: light.turn_on
target: { entity_id: light.bindicator }
data:
effect: Solid
rgb_color: "{{ rgb }}"
brightness: "{{ brightness }}"
- delay: "00:00:00.3"
- service: number.set_value
target: { entity_id: number.bindicator_effect_speed }
data: { value: 220 }
- delay: "00:00:00.3"
- service: light.turn_on
target: { entity_id: light.bindicator }
data:
effect: Dissolve
brightness: "{{ brightness }}"
- conditions: "{{ rgb != 'none' and days > 1 and days < 999 }}"
sequence:
- service: select.select_option
target: { entity_id: select.bindicator_palette }
data: { option: "Default" }
- service: light.turn_on
target: { entity_id: light.bindicator }
data:
effect: Solid
rgb_color: "{{ rgb }}"
brightness: "{{ brightness }}"
default:
- condition: template
value_template: "{{ bin_type == 'unknown' }}"
- service: light.turn_off
target: { entity_id: light.bindicator }
Using esphome
Using the esp8266, use the following code. Here we use the nexopixelbus platform. This includes 3 lambda effects.
light:
- platform: neopixelbus
id: bindicator
name: "bindicator"
pin: GPIO2
num_leds: 1
type: GRB # WS2812s are usually GRB
variant: WS2812
restore_mode: ALWAYS_ON
effects:
- pulse:
name: "Blink Fast"
# fast, snappy flashes
transition_length: 120ms
update_interval: 120ms
min_brightness: 8%
max_brightness: 100%
- pulse:
name: "Blink Slow"
# gentle breathing blink
transition_length: 650ms
update_interval: 650ms
min_brightness: 10%
max_brightness: 100%
- lambda:
name: "Christmas"
update_interval: 120ms # driver tick; smaller = snappier, larger = lazier
lambda: |-
static uint32_t phase_start = 0;
static uint32_t phase_len = 2500;
static uint8_t cadence = 0;
static uint8_t step = 0;
static uint8_t r = 255, g = 0, b = 0;
// Helper: pick color with weights and set phase length per color
auto pick_color = [&]() {
struct C { uint8_t r,g,b, weight; uint16_t min_ms, max_ms; };
const C palette[] = {
{255, 0, 0, 3, 1600, 2800}, // red — medium/long
{ 0,255, 0, 3, 1600, 2800}, // green — medium/long
{255,215, 0, 4, 2200, 3600}, // gold — longest (feature color)
{ 0, 0,255, 2, 900, 1500}, // blue — shorter
{255, 0,128, 2, 1200, 2000}, // magenta
{ 0,255,180, 2, 1200, 2000} // teal
};
uint16_t sum = 0;
for (auto &c : palette) sum += c.weight;
uint16_t roll = random_uint32() % sum;
const C* sel = &palette[0];
for (auto &c : palette) { if (roll < c.weight) { sel = &c; break; } roll -= c.weight; }
r = sel->r; g = sel->g; b = sel->b;
phase_len = sel->min_ms + (random_uint32() % (sel->max_ms - sel->min_ms + 1));
};
auto pick_cadence = [&]() {
uint8_t roll = random_uint32() % 100;
if (roll < 45) cadence = 0;
else if (roll < 85) cadence = 1;
else cadence = 2;
step = 0;
};
uint32_t now = millis();
if (phase_start == 0) {
pick_color(); pick_cadence();
phase_start = now;
}
// Phase rollover
if ((now - phase_start) > phase_len) {
// Occasionally HOLD longer on the “feature” colors (gold/red/green)
bool is_feature = ( (r==255 && g==215 && b==0) || (r==255 && g==0 && b==0) || (r==0 && g==255 && b==0) );
if (is_feature && (random_uint32() % 100) < 50) {
phase_start = now;
phase_len = 600 + (random_uint32() % 900); // 0.6–1.5s
cadence = 3;
} else {
pick_color(); pick_cadence();
phase_start = now;
}
}
auto call = id(bindicator).turn_on();
call.set_rgb(r/255.0f, g/255.0f, b/255.0f);
float br = 1.0f; // 0.0–1.0
uint32_t elapsed = now - phase_start;
switch (cadence) {
case 0: {
step = (step + 1) % 8;
bool on = (step==0 || step==2 || step==4);
br = on ? 1.0f : ((step==5) ? 0.0f : 0.12f);
call.set_transition_length(on ? 90 : 90);
break;
}
case 1: {
float t = (float)elapsed / (float)phase_len;
if (t > 1.0f) t = 1.0f;
float smooth = t*t*(3.0f - 2.0f*t); // ease-in-out
br = 0.18f + 0.82f * smooth; // 18%..100%
call.set_transition_length(240);
break;
}
case 2: {
br = 0.1f + (random_uint32() % 156) / 155.0f * 0.9f; // ~0.1..1.0
call.set_transition_length(80);
break;
}
case 3: {
br = 1.0f;
call.set_transition_length(120);
break;
}
}
call.set_brightness(br);
call.perform();
switch:
- platform: template
name: "Christmas Mode"
id: christmas_mode
optimistic: true
turn_on_action:
- light.turn_on:
id: bindicator
effect: "Christmas"
brightness: 1.0
turn_off_action:
- light.turn_off: bindicator
And using the esp32, we instead use the esp32_rmt_led_strip platform.
light:
- platform: esp32_rmt_led_strip
id: bindicator
rgb_order: GRB
default_transition_length: 1s
pin: GPIO16
num_leds: 1
rmt_symbols: 96
chipset: WS2812
name: "bindicator"
effects:
- pulse:
name: "Blink Fast"
transition_length: 120ms
update_interval: 120ms
min_brightness: 8%
max_brightness: 100%
- pulse:
name: "Blink Slow"
transition_length: 650ms
update_interval: 650ms
min_brightness: 10%
max_brightness: 100%
- lambda:
name: "Christmas"
update_interval: 120ms
lambda: |-
static uint32_t phase_start = 0;
static uint32_t phase_len = 2500;
static uint8_t cadence = 0;
static uint8_t step = 0;
static uint8_t r = 255, g = 0, b = 0;
auto pick_color = [&]() {
struct C { uint8_t r,g,b, weight; uint16_t min_ms, max_ms; };
const C palette[] = {
{255, 0, 0, 3, 1600, 2800},
{ 0,255, 0, 3, 1600, 2800},
{255,215, 0, 4, 2200, 3600},
{ 0, 0,255, 2, 900, 1500},
{255, 0,128, 2, 1200, 2000},
{ 0,255,180, 2, 1200, 2000}
};
uint16_t sum = 0;
for (auto &c : palette) sum += c.weight;
uint16_t roll = random_uint32() % sum;
const C* sel = &palette[0];
for (auto &c : palette) { if (roll < c.weight) { sel = &c; break; } roll -= c.weight; }
r = sel->r; g = sel->g; b = sel->b;
phase_len = sel->min_ms + (random_uint32() % (sel->max_ms - sel->min_ms + 1));
};
auto pick_cadence = [&]() {
uint8_t roll = random_uint32() % 100;
if (roll < 45) cadence = 0;
else if (roll < 85) cadence = 1;
else cadence = 2;
step = 0;
};
uint32_t now = millis();
if (phase_start == 0) {
pick_color(); pick_cadence();
phase_start = now;
}
if ((now - phase_start) > phase_len) {
bool is_feature = ( (r==255 && g==215 && b==0) || (r==255 && g==0 && b==0) || (r==0 && g==255 && b==0) );
if (is_feature && (random_uint32() % 100) < 50) {
phase_start = now;
phase_len = 600 + (random_uint32() % 900);
cadence = 3; // hold
} else {
pick_color(); pick_cadence();
phase_start = now;
}
}
// Build call
auto call = id(bindicator).turn_on();
call.set_rgb(r/255.0f, g/255.0f, b/255.0f);
float br = 1.0f;
uint32_t elapsed = now - phase_start;
switch (cadence) {
case 0: {
step = (step + 1) % 8;
bool on = (step==0 || step==2 || step==4);
br = on ? 1.0f : ((step==5) ? 0.0f : 0.12f);
call.set_transition_length(90);
break;
}
case 1: {
float t = (float)elapsed / (float)phase_len;
if (t > 1.0f) t = 1.0f;
float smooth = t*t*(3.0f - 2.0f*t);
br = 0.18f + 0.82f * smooth;
call.set_transition_length(240);
break;
}
case 2: {
br = 0.1f + (random_uint32() % 156) / 155.0f * 0.9f;
call.set_transition_length(80);
break;
}
case 3: {
br = 1.0f;
call.set_transition_length(120);
break;
}
}
call.set_brightness(br);
call.perform();
switch:
- platform: template
name: "Christmas Mode"
id: christmas_mode
optimistic: true
turn_on_action:
- light.turn_on:
id: bindicator
effect: "Christmas"
brightness: 1.0
turn_off_action:
- light.turn_off: bindicator
Wiring Diagram
It really doesn’t warrant a diagram. Just make sure when wiring the WS2182B that you use the Data IN pin not the Data OUT pin. Usually, labelled DIN and DOUT Gets me every time.
| ESP32 | WS2812B |
| 5V | 5V |
| GND | GND |
| GPIO4 | DIN |
Buy Bindicator!
Buy Bindicator here!




All Comments
Received my Bindicator in the post yesterday 🙂 one issue though… the piece of paper with the instructional QR code can’t be read can you send me the URL
Hi Jamie, sure its at https://deanfourie.me/bindicator-setup. Just reach out if you need any help!