Our local council (DCC) have implemented a curb-side recycling program with multiple coloured bins that need to go out on different weeks. There's really only two configurations that alternate each week, but I'm trying to justify writing software of considerably higher complexity than the problem it solves.
The pattern basically boils down to a "blue" week and a "yellow" week, with the main rubbish being collected every week.
The council has just rolled out new garden waste weekly collection and reduced rubbish collection to fortnightly, the blue/yellow pattern still stands but there may be an update to this in the future.
The goal
Originally every household was sent a fridge magnet with what colour each week was for their household, works great except it doesn't remind you on the day... my goals for an alerting system were:
- Reminds me in the evening on the night before rubbish collection (but only if I'm home)
- Tells me what bins need taken out to the street
The data
The DCC have always had a page on their website you could enter your address and see what rubbish collection week you were on, and some network inspection showed that this was querying their ArcGIS system in the background. Turns out there is an API that returns JSON that is publicly available (at least there is no authentication).
1const apiUrl = "https://apps.dunedin.govt.nz/arcgis/rest/services/Public/Refuse_Collection_Property/MapServer/find"
To query this data we need to send a POST
request with a form encoded query in the body:
1const body = new URLSearchParams();
2const headers = new Headers();
3
4headers.append("Content-Type", "application/x-www-form-urlencoded");
5
6body.append("searchText", address);
7body.append("layers", "0"); // Refuse and Recycling Collection layer
8body.append("contains", "true"); // Match on not fully complete address
9body.append("returnGeometry", "false"); // We don't need the geometry data for map drawing
10body.append("returnZ", "false"); // We don't need height information
11body.append("returnM", "false"); // Don't need this value if it is present
12body.append("returnUnformattedValues", "false"); // Returns everything as a string
13body.append("returnFieldName", "false"); // Not used for JSON response
14body.append("f", "json"); // Give us JSON formatted data
15
16const req = await fetch(apiUrl, {
17 method: "POST",
18 headers,
19 body,
20});
We should get back some results, matching the address we supplied in address
, it is possible to get multiple results if the address is vague, but we should end up with some useful data in the attributes section:
1"attributes": {
2 ...
3 "collectionDay": "Wednesday",
4 "numberDay": "3",
5 "CurrentWeek": "b",
6 ...
7}
Now we've got something we can use for automations!
Turning it into a REST API
While it is possible to do some fairly complicated HTTP requests directly from Home Assistant, I wanted to separate this out a bit so I didn't have to write as much YAML, in case the implementation changed and so I could share the service with friends.
The API is quite simple, just a query builder that returns a formatted result and is a simple Lambda function behind a CloudFront distribution for a nice URL. You can find the code this GitHub repository.
I have also implemented a cache in my system, where each successful result is stored until local midnight (when the GIS system updates its responses) as I don't want to send more requests to someone else's system than necessary. Checking every 5 minutes or so shouldn't ever be a problem, but I like to be a responsible netizen, and if my checking script malfunctions it's only fair that I wear the cost of that.
Home Assistant integration
Now that we've got a REST API we can query, that allows us to use the RESTful Sensor from Home Assistant and create a sensor that pulls that data every 5 minutes.
1sensor:
2 - platform: rest
3 name: Recycling Bin
4 resource: https://dcc-recycling.aramsay.co.nz?address=<<MY ADDRESS>>
5 method: GET
6 scan_interval: 3600
7 value_template: '{{ value_json.nextCollection }}'
8 json_attributes:
9 - beforeCollectionDay
10 - collectionDay
11 - collectionDayNumber
And from there we can start creating automations, for my purposes an alert on my phone the evening before collection day is ideal, checking to make sure I'm home first since I don't really need this to fire if we're on holiday.
1alias: Recycling Notification
2description: Send an alert to my phone to let me know what bins to take out
3trigger:
4 - platform: time
5 at: "20:00:00"
6condition:
7 - condition: template
8 # Compare the day before collection day to the current day
9 value_template: >-
10 {{ state_attr("sensor.recycling_bin", "collectionDayNumber")-1 ==
11 now().isoweekday() }}
12 - condition: state
13 entity_id: person.allan_ramsay
14 state: home
15action:
16 - alias: ""
17 data:
18 # Read the data directly from the sensor we configured above
19 message: Take out the {{ states('sensor.recycling_bin') }} bin
20 title: Recycling
21 service: notify.mobile_app_sm_s908e
There are lots of other things you could do, templates to set light colours the colour of the bin, show something on the TV via Chromecast, announce it via the smart speakers in the house, etc.
This, while more complicated that it probably needs to be, has been reliable so far and saved me looking down the street to see what bins the neighbours have put out.