[Custom component] Bird Buddy smart bird feeder

Is there always only 1 medias?

I’m guessing this is from the Automation? I believe it’s the “latest” post-card picture in that [0] media. I’m just using it to download the latest postcard picture to HA, so I can use the picture from “anywhere” (because I put it in the www folder). But @madcoder has since updated the BB-integration and you can now just use the “entity” picture instead if you want. :slight_smile:

medias[0] is basically the first photo in the postcard. If you were opening the postcard in the app, it would be the first photo you see. There are often more images in the postcard, but there’s no real criteria to go on, in order to determine the “best” image from the postcard, so we just use the first image to keep it simple. We could potentially be a little smarter about that, and maybe decode the report token in order to use the media id with the highest confidence of species recognition - on the assumption that the best/clearest photos will have the highest confidence scores. However, I believe when a photo is marked as a “recognized” species, it assigns confidence=100%, so that might not help much.

With the new Recent Visitor entity, it pulls the same first image from new postcards. Before the first postcard (i.e., after restarting HA or reloading the integration), it scans the collections looking for the most recent visit for a species, and then uses the most recent image of that most recent species.

Would it be an option to include the complete array in the recent visitor entity? Then I can save them all :slight_smile:

Not really – the Recent Visitor entity is just sensor. The value is the species name, and entity_picture is the URL of an image. Home Assistant requires that be a single URL, so we can’t really do that (one entity = one url). It would be possible to expose the rest of them as extra attributes, but HA warns against overusing (or misusing) attributes, because of how the state history works and how it writes data to the database.

If you want to use an automation like the examples earlier in this thread, you can use the downloader service to download as many of the media URLs as you’d like. I don’t know the easiest way to do that, so you would have to play around with the right kind of automation or script combination to download multiple URLs. Particularly, you need to be able to iterate the medias array, because the number of elements in each postcard can be different. Some might only have one image, some might have any other number of images (the only guarantee is that it won’t be empty!)

But it might depend on the use case you’re looking for. Do you just want to be able to see all the various images of birds, grouped by species? Cause you can do that in the Media browser (in HA sidebar, click Media, then Bird Buddy). That shows all the collections just like the BB app does. You don’t have to download and save all the media yourself, because once a postcard is collected those images are saved in your Bird Buddy collections. They don’t yet offer a way to download your collection all at once, but I wouldn’t be surprised if eventually they do.
Or do you want to see all of the individual sightings and images grouped together by postcard? Cause at that point that’s pretty much what the Bird Buddy app does. Once a postcard is collected, the feed turns into a list of visits and images.

I guess I could add another section to the Bird Buddy media browser that groups them by visit (i.e., feed items in the BB app). But you would still need to find a way to get a media directory surfaced in lovelace, and I don’t yet know of an easy way to do that. Even using a media-source:// URI I wasn’t able to get it to load or proxy images from the media browser, other than using the actual Bird Buddy content or thumbnail URLs from the postcard media.

The use case would be: I’m paranoid :smiley:

It’s more a case of “I don’t trust cloud services in the long (multi-year) run” and want to download them to storage I trust. Or: I’m a hoarder :wink:

I was indeed thinking to just iterate over that array and download them all. I will play with that.

Do you have raw json examples perhaps to play with? (I’m in the same boat where I don’t have much bird visitors to test with)


Edit, solved my own question:

repeat:
  count: "{{ trigger.event.data.sighting.medias | length }}"
  sequence:
    - service: downloader.download_file
      data:
        url: "{{ trigger.event.data.sighting.medias[repeat.index - 1].contentUrl }}"
        subdir: birdy
        filename: >-
          {{ (as_timestamp(now())-(0)) | timestamp_custom('%Y-%m-%d_%H-%M-%S') }}-birdy-{{ repeat.index }}.jpg
        overwrite: true
1 Like

By the way, as an idea to display birds, this could help: GitHub - craibo/ha_strava: Strava Activities and Summary data integration for Home Assistant

The strava integration has a camera entity which rotates an image every X seconds: ha_strava/camera.py at eedbe6983eb454c07fbb44791dee34f5687eb47a ¡ craibo/ha_strava ¡ GitHub

Maybe you can borrow the same functionality to display a camera with last entries?


Edit:
This could probably be a solution too: GitHub - jodur/imagesdirectory-camera: Camera entity for images, to create a slidehow or timelapse from images in a directory. The camra support browsing manually trough the slideshow also. The creation of anmiated Gif or mp4 is also supported by services

Since I have the snapshots in a directory, I can slideshow them :slight_smile:

Awesome! Thank you for sharing the sample, that’s exactly what I had in mind.

For what it’s worth, you can also use the medias[].createdAt attribute, to get a timestamp from it. It’ll be in this format: createdAt: '2023-01-23T19:12:58.779Z'. That will also be the actual timestamp of the BB media, whereas what you have now is using the timestamp of now, which is whenever the automation runs. The default polling interval is 10 minutes, so your timestamp could be 10 minutes or more after the actual image was taken, while createdAt will be when the image was uploaded to the cloud.

As for sample JSON that you can expect, you’ll see that when you get your first triggered automation. When you go to Traces for the automation, you can click on “Changed variables” tab, and you’ll see the full YAML. I also recently added a unit test to pybirdbuddy with a fixture that mimics the payload. These top-level fixture attributes will be inside the trigger.event.data.* model. You can also inspect the GraphQL schema if you really want to know all the possibilities.

However, if your goal is simply to create a backup of all your images, a much easier (or at least more resilient long-term) way to do that would be to write a separate python script utilizing the pybirdbuddy library (underlying library behind the HA integration - I wrote both). That has functions for things like refreshing the list of collections, and inspecting all the media in each collection. You could use that to download each image individually. One thing you’ll run into is how to avoid re-downloading images you’ve already downloaded before. The createdAt+repeat.index can help with that. Or you can use the medias[].id UUID as or in the filename, to determine if the file already exists, so you can skip it if you already have it.

I recommend the separate script instead, because with the postcard automation the automation will only trigger when a new previously unseen postcard arrives in the feed. If you open a postcard in the BB app yourself for example, then that postcard will never arrive in HA. But the library can iterate the entire collections.

Another idea that still runs as a separate script, but doesn’t require iterating the entire list of all media in every collection, would be a script that works similar to how this integration’s “new postcard” automation trigger works: Use pybirdbuddy to monitor the feed, using the refresh_feed( [, since=$lastTimestamp ] ) function. You’ll get the feed items that appear in the BB feed (each one with one of these types). If you filter those to FeedItemSpeciesSighting or FeedItemSpeciesUnlocked, each one will contain the media info for each individual sighting (i.e., same as the sightings that come into the automation trigger). Then once you’ve processed a batch of feed items, you would save the feed.newest_edge.node.created_at timestamp (to a file or whatever), and use that in the next call to refresh_feed(since=$lastTimestamp). One risk in doing this, would be if there’s a non-sighting feed item that later becomes a sighting feed item (such as if it was a postcard feed item that later got collected, and now it’s a new sighting with an older timestamp).


I did have the idea to replicate the GenericCamera entity (which is built in, and if you look up earlier in this thread, that was the simplest suggestion for getting the “Recent Visitor” entity image to appear in the “Picture Entity” card). Having it be part of the integration would remove the need to set it up manually, and would also remove the need for the Recent Visitor entity_picture. And once we have that, it’s not a stretch to expand on that functionality, supporting multiple URLs or media folders, and so on.

What I was trying to avoid is recreating something that already exists, if I can avoid it. Generic Camera already exists. The entities above that you link to, already exist. HA is designed to be intertwined, so if there’s a Lovelace card or another integration that does what you want, then I think it’s reasonable and expected that you would just combine those together. This integration is not really geared towards presenting multiple forms of media in multiple ways - it has a specific focus, which is to surface the Bird Buddy metrics to HA, and pulling in the media is a bonus.

If you can find one that does the same thing by accessing a media-source:// URI to an image or directory of images, then that’s perfect. Because then BB integration exposes the media in the media browser, and all you have to do is give that other entity the media-source URI, and it’s done.

Thanks for the suggestions, I’ve changed it now to:

alias: "[Birdy] Download new postcard sighting"
description: ""
trigger:
  - platform: event
    event_type: birdbuddy_new_postcard_sighting
condition: []
action:
  - service: birdbuddy.collect_postcard
    data:
      strategy: best_guess
      best_guess_confidence: 10
      postcard: "{{ trigger.event.data.postcard }}"
      sighting: "{{ trigger.event.data.sighting }}"
    enabled: false
  - service: downloader.download_file
    data:
      url: "{{ trigger.event.data.sighting.medias[0].contentUrl }}"
      subdir: birdy
      filename: last_postcard.jpg
      overwrite: true
  - repeat:
      count: "{{ trigger.event.data.sighting.medias | length }}"
      sequence:
        - service: downloader.download_file
          data:
            url: >-
              {{ trigger.event.data.sighting.medias[repeat.index - 1].contentUrl }}
            subdir: birdy
            filename: >-
              {{ trigger.event.data.sighting.medias[repeat.index - 1].createdAt }}-birdy-{{ trigger.event.data.sighting.medias[repeat.index - 1].id }}.jpg
            overwrite: true
  - service: notify.david_devices
    data:
      title: Birdy Spotted!
      message: A bird has been spotted, go check it out.
      data:
        group: ha-notification-group
        tag: bird-buddy-sighting
        push:
          interruption-level: passive
mode: single

This results in:

Which is ok for my requirements just now, no need to make it “complex” by writing a script. I realize the limitations I’m exposing myself too with regards to opening in the BB app beforehand. I let HA be very much in control mostly though. It sends me a notification on a new sighting too, which pushes me to open the BB app (by then HA has already downloaded the sightings)

For now I’m sticking to Picture entity to show the last one :slight_smile:

If it inspires somebody, my view so far:

The yaml for this dashboard, also I agree it can be made simpler, but it does it’s job for now :wink:

  - theme: Backend-selected
    title: Bird Buddy
    path: bird-buddy
    icon: mdi:bird
    type: custom:horizontal-layout
    badges: []
    cards:
      - show_state: true
        show_name: false
        camera_view: auto
        type: picture-entity
        entity: sensor.birdy_recent_visitor
        tap_action:
          action: none
        hold_action:
          action: none
        camera_image: camera.birdy
        aspect_ratio: 3x4
      - type: custom:apexcharts-card
        graph_span: 5days
        hours_12: false
        header:
          show: true
          show_states: true
          colorize_states: true
          title: Batterij
        series:
          - entity: sensor.birdy_battery
            type: area
            stroke_width: 2
            opacity: 0.3
            fill_raw: last
            name: Batterij
            color: '#FFE15D'
            show:
              in_header: true
              name_in_header: false
        apex_config:
          xaxis:
            tooltip:
              enabled: false
          legend:
            show: false
          grid:
            borderColor: '#7B7B7B'
          chart:
            foreColor: '#7B7B7B'
            toolbar:
              show: false
      - type: vertical-stack
        cards:
          - type: custom:mushroom-update-card
            entity: update.birdy_firmware_update
            name: Firmware
            hold_action:
              action: none
            double_tap_action:
              action: none
            show_buttons_control: false
            collapsible_controls: false
            icon: mdi:bird
          - type: custom:mushroom-template-card
            entity: sensor.birdy_feeder_state
            tap_action:
              action: more-info
            icon: mdi:bird
            icon_color: >-
              {% if is_state(entity, 'DEEP_SLEEP') %}grey{% elif
              is_state(entity, 'FIRMWARE_UPDATE') %}orange{% elif
              is_state(entity, 'OFFLINE') %}red{% elif is_state(entity,
              'OFF_GRID') %}red{% elif is_state(entity, 'ONLINE') %}green{% elif
              is_state(entity, 'OUT_OF_FEEDER') %}red{% elif is_state(entity,
              'READY_TO_STREAM') %}green{% elif is_state(entity, 'STREAMING')
              %}green{% elif is_state(entity, 'TAKING_POSTCARDS') %}green{%
              endif %}
            primary: Status
            secondary: >-
              {% if is_state(entity, 'DEEP_SLEEP') %}Deep Sleep{% elif
              is_state(entity, 'FIRMWARE_UPDATE') %}Firmware Update{% elif
              is_state(entity, 'OFFLINE') %}Offline{% elif is_state(entity,
              'OFF_GRID') %}Off Grid{% elif is_state(entity, 'ONLINE')
              %}Online{% elif is_state(entity, 'OUT_OF_FEEDER') %}Out of
              Feeder{% elif is_state(entity, 'READY_TO_STREAM') %}Ready for
              Birds!{% elif is_state(entity, 'STREAMING') %}Streaming{% elif
              is_state(entity, 'TAKING_POSTCARDS') %}Taking Postcards{% endif %}
          - type: custom:mushroom-template-card
            entity: sensor.birdy_battery
            icon: >-
              {% set s = states(entity) %} mdi:battery{{"-" + (((s|int/10)
              |round(0)) * 10) | string if ((s|int) < 90) and ((s|int) > 5)}}
            primary: Batterij
            icon_color: |-
              {% set battery_level = states(entity) | int %}
              {% if battery_level >= 60 %}
                green
              {% elif battery_level >= 40 %}
                orange
              {% else %}
                red
              {% endif %}
            secondary: '{{ (states(entity)) }}%'
            tap_action:
              action: more-info
          - type: custom:mushroom-template-card
            entity: binary_sensor.bird_buddy_charging
            icon: mdi:power-plug{{'-off' if states(entity) == 'off'}}
            icon_color: '{{''grey'' if states(entity) == ''off'' else ''green'' }}'
            primary: |
              {{ states(entity) 
                  | replace('off','Not Charging')
                  | replace('on','Charging')
              }}
            tap_action:
              action: more-info
            secondary: ''
          - type: custom:mushroom-template-card
            entity: sensor.birdy_signal_strength
            icon: |-
              {% set rssi_level = states(entity) | int %}
              {% if rssi_level >= -50 %}
                mdi:wifi-strength-4
              {% elif rssi_level >= -61 %}
                mdi:wifi-strength-3
              {% elif rssi_level >= -72 %}
                mdi:wifi-strength-2
              {% elif rssi_level >= -85 %}
                mdi:wifi-strength-1
              {% elif rssi_level >= -94 %}
                mdi:wifi-strength-outline
              {% else %}
                mdi:wifi-strength-off-outline
              {% endif %}
            primary: Netwerk
            icon_color: |-
              {% set rssi_level = states(entity) | int %}
              {% if rssi_level >= -70 %}
                green
              {% elif rssi_level >= -85 %}
                orange
              {% else %}
                red
              {% endif %}
            secondary: '{{ states(entity) }} dBm'
            tap_action:
              action: more-info

The next thing I am doing is adding an event to my google calendar when it spots a bird with direct links to the images :wink: (Nothing new, I already have this for my doorbell)

Nice work! :slight_smile:

One suggestion, which I realize has not been called out explicitly in this thread yet, but it is important for making sure you get all new postcards. The automation mode should be changed to queued. I see your automation yaml is set to the default, single, but that means if you get 2 new postcards within the 10 minute polling interval, it will only trigger for one of them, and the other will generate an error in your log (because the automation is “already executing”). Switching to queued means it will run the automation back to back, for both postcards, and you won’t miss any.

Thanks for the tip!

My S3 upload and add to Google calendar is done too:

image

service: google.create_event
target:
  entity_id: calendar.calls
data:
  summary: Birdy!
  start_date_time: "{{ (as_datetime(trigger.event.data.sighting.medias[0].createdAt) - timedelta( minutes = 1 )).strftime('%Y-%m-%d %H:%M:%S')  }}"
  end_date_time: "{{ as_datetime(trigger.event.data.sighting.medias[0].createdAt).strftime('%Y-%m-%d %H:%M:%S') }}"
  description: >
    {% for bird in trigger.event.data.sighting.medias -%}
    {% set file = bird.createdAt ~ "-birdy-" ~ bird.id ~ ".jpg" %}
    {% set name = bird.createdAt %}
    <a href="https://my-fancy-birdy-bucket.s3.eu-west-1.amazonaws.com/{{ file }}">Vogel gespot op {{ name }}</a><br>
    {% endfor %}

Do you know if we can also retrieve the video’s BB makes? The sightings event now seems to only have jpg data for the images, but there’s usually a video included in the app too.

The videos will appear in the media browser alongside the jpegs. Not every postcard has a video.

If you want to differentiate the medias array items for your downloads, you’ll see medias[*].__typename=="MediaImage", or MediaVideo. I believe the videos are .mp4 files, but I’m not 100% sure of that. I do know that the videos in collections appear with the play icon in the media browser, and I’ve confirmed that they play.

Bingo:
" * Creates a camera entity in Home Assistant to feature recent Strava pictures as a photo-carousel"

That’s exactly what I was looking for to show me the last 10 or so pics

1 Like

Hello,

I’ve given this some more thought and I’d like to do this since I have a Synology NAS connected to HA. Can this script be converted to grab this:

and send them to my Synology?

The reason I’m questioning is because I’m not sure if your script is pulling all media that was accepted in BB or if its pulling all sightings. If new pics are saved in BB, I want HA to automatically detect them and send a copy to Synology.

I’m not super familiar with the nuanced requirements of the media/URLs and how to pass them back and forth but I can follow directions :slight_smile:

Thanks, I’ll have a look, I have some sightings with videos

they got my second BB online today. Is there still testing you need done? (I haven’t connected it to HA yet)

Thanks for asking. @Snille was kind enough to invite me to his feeder, which helped uncover some issues with having multiple feeders on one account, as well as an edge case where you’ve redeemed an invitation code, but still waiting for the owner to confirm the invitation. Those fixes went into v0.0.10 already; and there’s another fix for the media browser (removing the per-Feeder directories in the browser), along with an unrelated improvement to the firmware updater, that merged last week but those have not been released yet.

There’s still an issue with the Recent Visitor entity when you have multiple feeders, where it might associate a visit from a bird species as the recent visitor on the wrong feeder, specifically when looking for visitors on startup (once the integration is running, it’ll keep them associated with the correct feeder as new postcards arrive). I know why it happens, and the latest changes make a best effort attempt to keep it associated correctly, but it can still get confused if the same species visits both feeders (in my case it was actually an incorrect bird identification on his feeder, but that visit was attributed to my feeder because the cover photo of that species is a photo from my feeder). A future change will pull this info from the feed of sightings rather than from collections, so it’ll be much more reliable. That of course won’t fix the problem of an incorrect bird identification, because I can only use what BB says.

I only saw that issue once, and it only happened as a result of a bad identification, because our feeders are in different countries and continents, so there really ought to be very little overlap. If your two feeders are in the same region, it’s a lot more likely to hit this bug.

I’ll try to get that resolved this week, but I’ve been on vacation for a week, so it’ll have to wait until I’ve caught up at work.

Long story short: most of the functionality should work correctly with multiple feeders, whether the additional feeders are owner access or guest/member access.

This screenshot is from the media browser, which is effectively a clone of the BB app’s Collections tab (the middle tab in the app). It lists the bird species collections, and the individual images within each collection. It’s not showing “sightings” per se, but individual images that have been collected from all sightings, grouped together by species.

In other words, if you don’t collect the postcard sightings, either manually in the BB app or with the collect_postcard service, they won’t appear here (and they also won’t appear in the app’s collections). It’s also possible for a single postcard to contain multiple “sightings” – of the same OR different birds/species. But the postcard itself is no longer relevant in the collections, because the image was already collected, or saved.

1 Like

By the way, the HA integration does not “connect” individual feeders. It works by login account. So once it’s paired with your account it should be auto discovered. However, it might only create the new devices and entities on integration startup. So if you see the second feeder in the app, but not in HA, you should probably be able to just reload the integration to have it discover the new feeder automatically.

In the future I’ll make sure it can add the newly identified feeders automatically without having to reload (I haven’t tested this). It also doesn’t auto “un-discover” a removed feeder, which is something I’ll work on in the future.