The Birdweather PUC is really interesting (and admittedly expensive) self-contained microphone array that records bird calls and updates a cloud database, adds the bird identification magic, and feeds an IOS app that updates almost in real time. Although the PUC is dependent on using their no-charge cloud back-end, the company provides pretty extensive APIs. Note, there is also a BirdNet-Pi project that provides the same functionality and is included in the Birdweather.com database so these sensors should work. Using either input, it seemed like a natural HA project to import the JSON data and update a dashboard.
Birdweather provides two extensive APIs: a GraphQL API and a REST API, both of which provide various JSON responses. I chose to use the REST API, 'cause it seemed easier.
Here’s the end result so far, updating the latest detection in the big box, and the day’s top 10 in order of number of detections. Lots more tinkering to do.
Step 1. Reach out to Tim at Birdweather.com for an API token. He got back to me via email after a day or two.
Step 2. Modify your HA configuration.yml file to include the new sensors.
The code is not very artful, but it works. I failed several times looking for a way to create the individual top 10 sensors programmatically (which would make it less tedious and more elegant to expand it to 50), but alas I ended up just repeating the code, altering the placeholder for each of the top 10. I think the solution is to create a script to create the individual sensors.
Here are the two rest sensors I used. The first retrieves the top 50 species detected in the past day. The value template creates a sensor.top_50_bird_species that uses the common name of the most detected species as the state, and includes a lot of JSON data packaged as the attribute species. I use that attribute later to create sensors to track each of the top 10 most detected species.
The second rest sensor retrieves the most recent detection. The scan interval default is 30 seconds and I’m going with that.
sensor:
- platform: rest
name: Top 50 Bird Species
resource: https://app.birdweather.com/api/v1/stations/{token}/species/?period=day&limit=50
value_template: "{{ value_json.species[0].commonName }}"
json_attributes:
- species
- platform: rest
name: Latest Bird Detection
resource: https://app.birdweather.com/api/v1/stations/{token}/detections/?limit=1
value_template: >
{% if value_json.detections and value_json.detections | length > 0 %}
{{ value_json.detections[0].species.commonName }}
{% else %}
No detections
{% endif %}
json_attributes:
- detections
The real challenge for me was figuring out how to extract the right bits of data and get it to show up on the dashboard. The path I took was to define a separate sensor for each of the top 10 detected species. For each sensor, the underlying attributes provide the data I want to display. Here’s what it looks like, but instead of including all 10, I just included the first two. I look forward to seeing how this can be made much more elegant with a for loop.
template:
- sensor:
- name: "Top Bird Species 0"
state: >
{{ state_attr('sensor.top_50_bird_species', 'species')[0]['commonName'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
attributes:
ImageURL: >
{{ state_attr('sensor.top_50_bird_species', 'species')[0]['imageUrl'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
id: >
{{ state_attr('sensor.top_50_bird_species', 'species')[0]['id'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
detections: >
{{ state_attr('sensor.top_50_bird_species', 'species')[0]['detections'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
last_detection_time: >
{{ state_attr('sensor.top_50_bird_species', 'species')[0]['latestDetectionAt'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
- name: "Top Bird Species 1"
state: >
{{ state_attr('sensor.top_50_bird_species', 'species')[1]['commonName'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
attributes:
ImageURL: >
{{ state_attr('sensor.top_50_bird_species', 'species')[1]['imageUrl'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
id: >
{{ state_attr('sensor.top_50_bird_species', 'species')[1]['id'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
detections: >
{{ state_attr('sensor.top_50_bird_species', 'species')[1]['detections'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
last_detection_time: >
{{ state_attr('sensor.top_50_bird_species', 'species')[1]['latestDetectionAt'] if state_attr('sensor.top_50_bird_species', 'species') is defined and state_attr('sensor.top_50_bird_species', 'species')|length > 0 else 'Unknown' }}
tip You have to restart the first time you add the configuration code, but after that, when debugging your yaml, you can quick reload (Settings>System>Upper Right Corner>Quick Reload) instead of Restarting everything.
Step 3. Add the dashboard
Again, I’m absolutely sure there’s a better way to do this with less repetitive code, but I was surprised how difficult it was to work with sensor attribute data. After a bunch of false starts, I landed on a simple markdown card. I used a fresh dashboard page sized for an old iPad Pro setup as the bird dash. Here’s the code (again, you’ll get the idea after the first two markdown cards):
- path: bird-dash
type: sections
sections:
- type: grid
cards:
- type: markdown
content: |+
### Latest Detection
![image]({{ state_attr('sensor.latest_bird_detection',
'detections')[0]['species']['imageUrl'] }})
## {{ states('sensor.latest_bird_detection') }}
{% set raw_timestamp = state_attr('sensor.latest_bird_detection',
'detections')[0]['timestamp'] %}
{% set timestamp = strptime(raw_timestamp, '%Y-%m-%dT%H:%M:%S.%f%z') %}
##### {{ timestamp.strftime('%B %d, %I:%M %p') }}
title: Today’s Top 10 Detected Birds
- type: grid
cards:
- square: false
type: grid
cards:
- type: markdown
content: >
### #1 / 10
![image]({{
states.sensor.top_bird_species_0.attributes.ImageURL }})
#### {{ states('sensor.top_bird_species_0') }}
##### Detections: {{
states.sensor.top_bird_species_0.attributes.detections.total
}}
{% set date_str = state_attr('sensor.top_bird_species_0',
'last_detection_time') %}
{% if date_str %}
{% set date_obj = as_datetime(date_str) %}
{% set local_date = as_local(date_obj) %}
##### {{ local_date.strftime('%b %d, %I:%M:%S %p') }}
{% else %}
No detections
{% endif %}
- type: markdown
content: >
### #2 / 10
![image]({{
states.sensor.top_bird_species_1.attributes.ImageURL }})
#### {{ states('sensor.top_bird_species_1') }}
##### Detections: {{
states.sensor.top_bird_species_1.attributes.detections.total
}}
{% set date_str = state_attr('sensor.top_bird_species_1',
'last_detection_time') %}
{% if date_str %}
{% set date_obj = as_datetime(date_str) %}
{% set local_date = as_local(date_obj) %}
##### {{ local_date.strftime('%b %d, %I:%M:%S %p') }}
{% else %}
No detections
{% endif %}
columns: 2
cards: []
max_columns: 4
icon: mdi:bird
I’ve only been working with it off and on for a couple of weeks, so no doubt it’ll evolve. Hope you find it interesting or useful.
Cheers.