796 lines
83 KiB
HTML
796 lines
83 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" dir="auto">
|
||
|
||
<head><meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
<meta name="robots" content="index, follow">
|
||
<title>Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 | Norm-working Packets 💾</title>
|
||
<meta name="keywords" content="homeassistant, python, diy">
|
||
<meta name="description" content="Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant.">
|
||
<meta name="author" content="Me">
|
||
<link rel="canonical" href="../posts/birdnet_homeassistant.html">
|
||
<script defer data-domain="selfhosted.rsmsn.co" src="https://analytics.rsmsn.co/js/script.js"></script>
|
||
<link crossorigin="anonymous" href="../assets/css/stylesheet.b609c58d5c11bb90b1a54e04005d74ad1ddf22165eb79f5533967e57df9c3b50.css" integrity="sha256-tgnFjVwRu5CxpU4EAF10rR3fIhZet59VM5Z+V9+cO1A=" rel="preload stylesheet" as="style">
|
||
<link rel="icon" href="../favicon.ico">
|
||
<link rel="icon" type="image/png" sizes="16x16" href="../favicon-16x16.png">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="../rsmsncircles.ico">
|
||
<link rel="apple-touch-icon" href="../apple-touch-icon.png">
|
||
<link rel="mask-icon" href="../safari-pinned-tab.svg">
|
||
<meta name="theme-color" content="#2e2e33">
|
||
<meta name="msapplication-TileColor" content="#2e2e33">
|
||
<link rel="alternate" hreflang="en" href="../posts/birdnet_homeassistant.html">
|
||
<noscript>
|
||
<style>
|
||
#theme-toggle,
|
||
.top-link {
|
||
display: none;
|
||
}
|
||
|
||
</style>
|
||
</noscript>
|
||
|
||
|
||
<meta property="og:title" content="Creating a BirdNetPi Dashboard in HomeAssistant - Part 1" />
|
||
<meta property="og:description" content="Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant." />
|
||
<meta property="og:type" content="article" />
|
||
<meta property="og:url" content="/posts/birdnet_homeassistant.html" />
|
||
<meta property="og:image" content="/birdnet-homeassistant.png" /><meta property="article:section" content="posts" />
|
||
<meta property="article:published_time" content="2023-09-30T11:21:55-04:00" />
|
||
<meta property="article:modified_time" content="2023-09-30T11:21:55-04:00" /><meta property="og:site_name" content="Self-hosted Norm" />
|
||
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="twitter:image" content="/birdnet-homeassistant.png" />
|
||
<meta name="twitter:title" content="Creating a BirdNetPi Dashboard in HomeAssistant - Part 1"/>
|
||
<meta name="twitter:description" content="Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant."/>
|
||
|
||
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BreadcrumbList",
|
||
"itemListElement": [
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 1 ,
|
||
"name": "Posts",
|
||
"item": "/posts.html"
|
||
}
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 1 ,
|
||
"name": "Creating a BirdNetPi Dashboard in HomeAssistant - Part 1",
|
||
"item": "/posts/birdnet_homeassistant.html"
|
||
}
|
||
]
|
||
}
|
||
</script>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BlogPosting",
|
||
"headline": "Creating a BirdNetPi Dashboard in HomeAssistant - Part 1",
|
||
"name": "Creating a BirdNetPi Dashboard in HomeAssistant - Part 1",
|
||
"description": "Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant.",
|
||
"keywords": [
|
||
"homeassistant", "python", "diy"
|
||
],
|
||
"articleBody": "This is Part One of a Two Part Series. You can find Part Two, here.\nUpdate: 10/11/2023. A huge thanks to Mastodon User e_mobile2014 who found a broken link in this guide and pointed out that I never explained how to get the mqtt sensors into HomeAssistant!\nWhat you will need BirdNET-Pi HomeAssistant AppDaemon MQTT Broker (I use Mosquitto) Background In early 2023, at the height of the Raspberry Pi shortage I felt like a king with an extra Rpi laying around, not being used. I’m a big fan of any sort of passive intake of information and had been looking around for various citizen science-style projects that can capture information from the world around me. Since I’m already running an ADS-B antenna with Flight Aware, I figured this next project would deal with radio waves/transmissions. Instead, to my amazement, I discovered BirdNET-Pi!\nWhat is BirdNET-Pi? In case you didn’t click the links above, BirdNET-Pi is an app built specifically made for Rapsberry Pi devices, that builds off the BirdNET Framework. BirdNET is one of the most advanced acoustic monitoring tools available for passively monitoring bird diversity populations. Where BirdNET-Pi takes it to the next level is the ability to setup an SBC - hopefully enclosed in a waterproof space! - and monitor birds in your local environment over time.\nI think this project is beyond neat. It runs a bit slow on a Raspberry Pi 3, but overall it runs smoothly. I was even able to contribute a PR to the project in April when I noticed a bug in the platform after a hard reset of my Pi.\nBirdNET-PI Notification Setup - MQTT Once you have BirdNET-Pi up and running, you’ll need to head over to the Settings and setup the correct MQTT payloads. Here are the possible variables you can pass in an MQTT payload:\n$sciname: Scientific Name $comname: Common Name $confidence: Confidence Score $confidencepct: Confidence Score as a percentage (eg. 0.91 =\u003e 91) $listenurl: A link to the detection $date: Date $time: Time $week: Week $latitude: Latitude $longitude: Longitude $cutoff: Minimum Confidence set in “Advanced Settings” $sens: Sigmoid Sensitivity set in “Advanced Settings” $overlap: Overlap set in “Advanced Settings” $flickrimage: A preview image of the detected species from Flickr. Set your API key below. For our purposes, we will only be using $comname, $sciname, $date, $time, $week, and $confidence. However, this entire process is extremely customizable, which you’ll learn more about in the AppDaemon section. Please expand on it and include information that is pertinent to your own uses.\nHere is how I’ve setup my MQTT payload from BirdNET-Pi Settings:\nHere it is in text form:\nNotification Title: $comname, Notification Body: $sciname, $date, $time, $week, $confidence [ ] Notify each new infrequent species detection (\u003c 5 visits per week) [ ] Notify each new species first detection of the day [X] Notify each new detection [X] Send weekly report Minimum time between notifications of the same species (sec): 5 To test my MQTT notifications, I use the iOS client “MQTTool”. After signing up, head to “Subscribe” and type birdnet as the topic and then click Subscribe. If everything is setup correctly and there are birds being recorded by the BirdNET-Pi’s microphone, you should start seeing those detections in the MQTTool app. If so, fantastic news! Let’s move onto AppDaemon.\nAppDaemon Script Now that we have the Pi communicating via MQTT, it’s time to get that information into HomeAssistant. I’ve shared the full script at the bottom of this page, but let’s jump into each section. This is not a full tutorial of how to use AppDaemon, but it may help fill in any knowledge gaps with the system.\nImports First, we’re going to import time and requests. We’re going to use time as a backup to the $time component in the payload. This can be helpful to see if there delays, or if BirdNET-Pi stopped detecting. We’re then going to use requests to pull from Wikipedia’s API and grab a description for our HomeAssistant Dashboard.\nClass Definition To start any AppDaemon app, you need to include a Class that is defined in the apps.yaml file. This is also where we initialize and define the various items that will be used in the remainder of the script.\nclass birdnet(adbase.ADBase): def initialize(self): self.hassapi = self.get_plugin_api(\"HASS\") self.adapi = self.get_ad_api() self.mqttapi = self.get_plugin_api(\"MQTT\") self.birdnet_mqtt = \"birdnet\" self.mqttapi.listen_event( self.birdnet_message, \"MQTT_MESSAGE\", topic=self.birdnet_mqtt ) For this script, we need to use a lot of the AppDaemon APIs across more than just HomeAssistant, so we’re going to be using ADBase. By using that, we can initialize the various APIs, which we do in the next 3 lines. In these 3 lines we need to get access to HomeAssistant’s APIs, AppDaemon’s APIs, and MQTT APIs - the first and third items are plugins of AppDaemon, and AppDaemon is… well… AppDaemon! Here are a few reference docs:\nMQTT AppDaemon API HomeAssistant AppDaemon API AppDaemon API These will indispensable to you as you leverage AppDaemon and expand this little script.\nOnce we have access to that, we need to setup the main topic for MQTT from BirdNET-Pi and finally, what event we are listening for that will trigger the functions in the rest of the script. self.birdnet_mqtt = \"birdnet\" is the definition for the MQTT topic. Let’s breakdown the last line of the class.\nHere’s a breakdown of each of the items in that last line. You can find the official documentation here.\nself.mqttapi.listen_event - this is what we use in AppDaemon to listen for an MQTT event in order to trigger a function. self.birdnet_message - the name of the function you’d like to trigger \"MQTT_MESSAGE\" - The default event in AppDaemon’s MQTT API plugin. This is used because MQTT doesn’t keep a state in this plugin. topic=self.birdnet_mqtt - The topic that will be received to trigger the function. Defined on the previous line. In other words, what we are telling AppDaemon is the following: “When AppDaemon’s MQTT API plugin receives a message with the topic of ‘birdnet’, run the function birdnet_message.”\nbirdnet_message Function Part 1: Variables Management Now we get into our first function of the script. The first portion of this script is splitting up the payload that we defined from the BirdNET-Pi UI into individual variables that we can better manage later on. If you test this script out by adding print() statements at various points, you’ll notice that the payload is received with the following json formatting:\n{ \"payload\": { \"data\": \"data\" } } As such, we need to look inside the payload to begin grabbing the data. The pre_split variable is now just looking at the data inside the payload and the rest of the variables take all the date into the payload, split it by the comma, and then grab the string by their index. If you remember what we did above above, you’ll see that we have the various BirdNET information at each of the indexes in the AppDaemon script - 0 through 5.\nPart 2: Re-Publishing MQTT Payloads This next section is shooting all the variables we just defined back via MQTT. The reason why we do it this way is because we need HomeAssistant to grab each of these variables as individual sensors. BirdNET doesn’t give us that capability - it’s a single message with all the information in one. Here is the documentation from AppDaemon on mqtt_publish. Later on, I’ll show you how to ensure that HomeAssistant takes those topic payloads and adds them as entities in your HA setup.\nPart 3: Wikipedia Sensor The next eight lines are a fairly straightforward API call to Wikipedia. We start out by passing the science_name into the URL. The rest of the flags that we are passing into the URL comes from Wikipedia’s Docs. url = f\"https://en.wikipedia.org/w/api.php?format=json\u0026action=query\u0026prop=extracts\u0026exintro\u0026explaintext\u0026redirects=1\u0026titles={science_name}\"\nOnce that’s done we call it with response.get(url) and format it with response.json(). Wikipedia returns the json payload with the top level of query (which was our action in the url ;) ), and we’re looking for the value within that query.\nAll that’s left is to take that query value and push it to HomeAssistant! We can do that with the self.hassapi.set_state function. Within the parenthesis we define the name of the sensor (sensor.birdnet_wiki), what it’s state should be (on), and any attributes associated with the entity. Since we can’t assign a long description to the basic status of the entity, we’re adding an attribute with the key of description and the value will be the wikipedia description garnered from the API call.\nurl = f\"https://en.wikipedia.org/w/api.php?format=json\u0026action=query\u0026prop=extracts\u0026exintro\u0026explaintext\u0026redirects=1\u0026titles={science_name}\" response = requests.get(url) response = response.json() for value in response['query']['pages']: wiki_desc = response['query']['pages'][value]['extract'] self.hassapi.set_state(\"sensor.birdnet_wiki\", state='on', attributes = {\"description\": wiki_desc}) Part 4: Generate Picture for Detection (Optional) This part is optional but I noticed that BirdNET-Pi was already grabbing a Flickr Picture for it’s front end, so I took the code from the BirdNET code base and adjusted it a bit for my needs. This will work very similarly to the Wikipedia API call, the main difference here being that you need an API key for Flickr. You can find more information here.\nGiven Flickr’s fairly robust API, by passing in the detected bird’s common name, we get amazing results from the community of various pictures of the same species of bird. Ever since I’ve set this up, I’ve not seen a mislabeled picture in my dashboard!\nThe most confusion portion of this section is the image_url as you’ll notice a bunch of data[\"value\"] strings at various portions of the URL. The short answer to this is in the previous line with the data variable. A successful query has Flickr returning a large payload of information. We’re specifically using this Flickr API endpoint. While you can pass a lot of variables for your needs, if you scroll down, you can see that the example response contains multiple photos in a single response. We’re passing per_page=5 to limit some of those response items. Left out of that response, though, is a one-stop-shop for a URL to that photo. Thankfully, Flickr can help us put together a URL from the data in the response.\nNote: Full Transparency that I only learned about this after reading through BirdNET-Pi’s code base. Full credit goes to mcguirepr89. For additional reference, here is Flickr’s official page on construction photo image URLS\nWith this response, we now have the variables we need to construct the URL to actually render the image. Those variables are: Farm ID, Server ID, ID and Secret. I haven’t yet looked into why we need “farm” when the official documentation doesn’t state anything about it.\nAlmost there! We now do the same as we did with the Wikipedia API response. We create a sensor in HomeAssistant! We’re calling this sensor sensor.birdpic, ensuring the state=on, and giving it the attributes of the image_url as garnered from Flickr.\nheaders = {'User-Agent': 'Python_Flickr/1.0'} flickr_api = \"enter_your_api_key\" flickr_url = f\"https://www.flickr.com/services/rest/?method=flickr.photos.search\u0026api_key={flickr_api}\u0026text={common_name} bird\u0026sort=relevance\u0026per_page=5\u0026media=photos\u0026format=json\u0026nojsoncallback=1\" flickr_resp = requests.get(url=flickr_url, headers=headers) data = flickr_resp.json()[\"photos\"][\"photo\"][0] image_url = 'https://farm'+str(data[\"farm\"])+'.static.flickr.com/'+str(data[\"server\"])+'/'+str(data[\"id\"])+'_'+str(data[\"secret\"])+'_n.jpg' self.hassapi.set_state(\"sensor.birdpic\", state='on', attributes={\"image\": image_url}) Importing MQTT Sensors into HomeAssistant Now that we have all the sensors defined and communicating via MQTT, we have one more step to import them into HomeAssistant. This MQTT documentation by HomeAssistant is good to read about if you need a broker setup. I will not be going over the broker in this tutorial, but may add one in the future. I tend to like the yaml configuration for HomeAssistant, so for the sake of this guide, I’ll be referencing the manual configuration of MQTT items and sensors.\nTo add the sensors from above, open up your configuration.yaml file in your favorite editor. You’ll then want to add the mqtt platform and domain:\nmqtt: - { domain }: For the BirdNet sensors, we will be using a single domain: sensor. Feel free to copy and paste my config from below, but make sure the names of each entity align with your needs, syntax, and nomenclature/system.\nFull MQTT Sensors in Configuration.yml\nmqtt: sensor: - name: \"Bird Common Name\" state_topic: \"birdnet/sensors/common_name\" - name: \"Bird Science Name\" state_topic: \"birdnet/sensors/science_name\" - name: \"Bird Time Seen\" state_topic: \"birdnet/sensors/time_seen\" - name: \"Bird Date Seen\" state_topic: \"birdnet/sensors/date_seen\" - name: \"Bird Confidence\" state_topic: \"birdnet/sensors/confidence\" value_template: '{{ (value|float(0) *100) | round(1) }}' unit_of_measurement: '%' You might be looking at the list above and wondering where the Flickr and Wikipedia Description entities are. They were already created by the AppDaemon script! Specifically, self.hassapi.set_state() function will either update the state for an exisiting entity or, if the entity doesn’t exist, it will create a new one.\nFor the rest of the mqtt payloads, we need HomeAssistant to create them as they come in, which is why we add the above code block to our HomeAssistant configuration file. To be clear, you do not need to add the Wikipedia and Flickr sensors to HA’s configuration file!\nAdding the Camera entity Last but not least, we need to add a camera entity to ensure that the sensor.birdpic can actually be rendered visually. It’s really easy to add this sensor, so this should be quick. Here’s how:\nIn HomeAssistant, navigate to Settings \u003e Devices \u0026 Services \u003e Integrations Click “+ Add Integration” in the bottom right-hand corner. Alternatively, if you already have a camera integration enabled, look for the “Generic Camera” card and click “Add Entry” Name the sensor. In this case, I called it “BirdPicturesfromFlickr” and renamed the entity to camera.birdnet_flickr. You should now see a “Still Image URL” as the first of a few options on the screen. Enter the following into the still image field: {{ state_attr('sensor.birdpic', 'image') }} (This is the sensor we created in AppDaemon with the flickr url as the attribute). Stream Source and RTSP transport protocol can both be left blank. Authentication - select “digest”. Username and Password can be left blank. Frame Rate - 2 Leave the rest of the check boxes unchecked and click Submit When you now click on the entity, you should see an image!\nBy this point, you should have successfully created 7 new sensors in HomeAssistant. In Part 2 of this article, we’ll take a look at Home Assistant, see what these sensors look like, and create a rudimentary dashboard.\nBirdnet AppDaemon Script import time import requests class birdnet(adbase.ADBase): def initialize(self): self.hassapi = self.get_plugin_api(\"HASS\") self.adapi = self.get_ad_api() self.mqttapi = self.get_plugin_api(\"MQTT\") self.birdnet_mqtt = \"birdnet\" self.mqttapi.listen_event( self.birdnet_message, \"MQTT_MESSAGE\", topic=self.birdnet_mqtt ) def birdnet_message(self, event_name, data, kwargs): pre_split = data[\"payload\"] common_name = pre_split.split(',')[0].strip() science_name = pre_split.split(',')[1].strip() date_seen = pre_split.split(',')[2].strip() time_seen = pre_split.split(',')[3].strip() week_seen = pre_split.split(',')[4].strip() confidence = pre_split.split(',')[5].strip() # print(f\"A {common_name} was seen on {date_seen} at {time_seen}. Confidence is {confidence}.\") self.mqttapi.mqtt_publish(\"birdnet/sensors/common_name\", common_name) self.mqttapi.mqtt_publish(\"birdnet/sensors/science_name\", science_name) self.mqttapi.mqtt_publish(\"birdnet/sensors/time_seen\", time_seen) self.mqttapi.mqtt_publish(\"birdnet/sensors/date_seen\", date_seen) self.mqttapi.mqtt_publish(\"birdnet/sensors/confidence\", confidence) url = f\"https://en.wikipedia.org/w/api.php?format=json\u0026action=query\u0026prop=extracts\u0026exintro\u0026explaintext\u0026redirects=1\u0026titles={science_name}\" response = requests.get(url) response = response.json() for value in response['query']['pages']: wiki_desc = response['query']['pages'][value]['extract'] self.hassapi.set_state(\"sensor.birdnet_wiki\", state='on', attributes = {\"description\": wiki_desc}) headers = {'User-Agent': 'Python_Flickr/1.0'} flickr_api = \"enter_your_api_key\" flickr_url = f\"https://www.flickr.com/services/rest/?method=flickr.photos.search\u0026api_key={flickr_api}\u0026text={common_name} bird\u0026sort=relevance\u0026per_page=5\u0026media=photos\u0026format=json\u0026nojsoncallback=1\" flickr_resp = requests.get(url=flickr_url, headers=headers) data = flickr_resp.json()[\"photos\"][\"photo\"][0] image_url = 'https://farm'+str(data[\"farm\"])+'.static.flickr.com/'+str(data[\"server\"])+'/'+str(data[\"id\"])+'_'+str(data[\"secret\"])+'_n.jpg' self.hassapi.set_state(\"sensor.birdpic\", state='on', attributes={\"image\": image_url}) ",
|
||
"wordCount" : "2484",
|
||
"inLanguage": "en",
|
||
"image":"/birdnet-homeassistant.png","datePublished": "2023-09-30T11:21:55-04:00",
|
||
"dateModified": "2023-09-30T11:21:55-04:00",
|
||
"author":{
|
||
"@type": "Person",
|
||
"name": "Me"
|
||
},
|
||
"mainEntityOfPage": {
|
||
"@type": "WebPage",
|
||
"@id": "/posts/birdnet_homeassistant.html"
|
||
},
|
||
"publisher": {
|
||
"@type": "Organization",
|
||
"name": "Norm-working Packets 💾",
|
||
"logo": {
|
||
"@type": "ImageObject",
|
||
"url": "/favicon.ico"
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
|
||
<body class=" dark" id="top">
|
||
<script>
|
||
if (localStorage.getItem("pref-theme") === "light") {
|
||
document.body.classList.remove('dark')
|
||
}
|
||
|
||
</script>
|
||
|
||
<header class="header">
|
||
<nav class="nav">
|
||
<div class="logo">
|
||
<a href="../" accesskey="h" title="Norm-working Packets 💾 (Alt + H)">Norm-working Packets 💾</a>
|
||
<div class="logo-switches">
|
||
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
|
||
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||
</svg>
|
||
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</button>
|
||
<ul class="lang-switch"><li>|</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<ul id="menu">
|
||
<li>
|
||
<a href="../posts.html" title="Posts">
|
||
<span>Posts</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</header>
|
||
<main class="main">
|
||
|
||
<article class="post-single">
|
||
<header class="post-header">
|
||
<div class="breadcrumbs"><a href="../">Home</a> » <a href="../posts.html">Posts</a></div>
|
||
<h1 class="post-title entry-hint-parent">
|
||
Creating a BirdNetPi Dashboard in HomeAssistant - Part 1
|
||
</h1>
|
||
<div class="post-description">
|
||
Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant.
|
||
</div>
|
||
<div class="post-meta"><span title='2023-09-30 11:21:55 -0400 EDT'>September 30, 2023</span> · 12 min · 2484 words · Me
|
||
|
||
</div>
|
||
</header> <div class="toc">
|
||
<details >
|
||
<summary accesskey="c" title="(Alt + C)">
|
||
<span class="details">Table of Contents</span>
|
||
</summary>
|
||
|
||
<div class="inner"><nav id="TableOfContents">
|
||
<ul>
|
||
<li><a href="#what-you-will-need">What you will need</a></li>
|
||
<li><a href="#background">Background</a></li>
|
||
<li><a href="#what-is-birdnet-pi">What is BirdNET-Pi?</a></li>
|
||
<li><a href="#birdnet-pi-notification-setup---mqtt">BirdNET-PI Notification Setup - MQTT</a></li>
|
||
<li><a href="#appdaemon-script">AppDaemon Script</a>
|
||
<ul>
|
||
<li><a href="#imports">Imports</a></li>
|
||
<li><a href="#class-definition">Class Definition</a></li>
|
||
<li><a href="#birdnet_message-function">birdnet_message Function</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a href="#importing-mqtt-sensors-into-homeassistant">Importing MQTT Sensors into HomeAssistant</a></li>
|
||
<li><a href="#adding-the-camera-entity">Adding the Camera entity</a></li>
|
||
<li><a href="#birdnet-appdaemon-script">Birdnet AppDaemon Script</a></li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
|
||
<div class="post-content"><p>This is Part One of a Two Part Series. You can find Part Two, <a href="../posts/birdnet_homeassistant_part2.html">here.</a></p>
|
||
<p><strong>Update: 10/11/2023. A huge thanks to Mastodon User <a href="https://mastodon.social/@e_mobil2014">e_mobile2014</a> who found a broken link in this guide and pointed out that I never explained how to get the mqtt sensors into HomeAssistant!</strong></p>
|
||
<h2 id="what-you-will-need">What you will need<a hidden class="anchor" aria-hidden="true" href="#what-you-will-need">#</a></h2>
|
||
<ul>
|
||
<li><a href="https://github.com/mcguirepr89/BirdNET-Pi">BirdNET-Pi</a></li>
|
||
<li><a href="https://www.home-assistant.io/">HomeAssistant</a></li>
|
||
<li><a href="https://appdaemon.readthedocs.io/en/latest/">AppDaemon</a></li>
|
||
<li>MQTT Broker (I use <a href="https://mosquitto.org/">Mosquitto</a>)</li>
|
||
</ul>
|
||
<h2 id="background">Background<a hidden class="anchor" aria-hidden="true" href="#background">#</a></h2>
|
||
<p>In early 2023, at the height of the <a href="https://www.tomshardware.com/news/raspberry-pi-availability-analysis">Raspberry Pi
|
||
shortage</a> I felt like a king with an extra Rpi laying
|
||
around, not being used. I’m a big fan of any sort of passive intake of information and had been looking around for various
|
||
citizen science-style projects that can capture information from the world around me. Since I’m already running an ADS-B
|
||
antenna with <a href="https://www.flightaware.com/">Flight Aware</a>, I figured this next project would deal with radio
|
||
waves/transmissions. Instead, to my amazement, I discovered <a href="https://github.com/mcguirepr89/BirdNET-Pi">BirdNET-Pi</a>!</p>
|
||
<h2 id="what-is-birdnet-pi">What is BirdNET-Pi?<a hidden class="anchor" aria-hidden="true" href="#what-is-birdnet-pi">#</a></h2>
|
||
<p>In case you didn’t click the links above, <a href="https://github.com/mcguirepr89/BirdNET-Pi">BirdNET-Pi</a> is an app built specifically
|
||
made for Rapsberry Pi devices, that builds off the <a href="https://github.com/kahst/BirdNET-Analyzer">BirdNET Framework</a>. BirdNET is
|
||
one of the most advanced acoustic monitoring tools available for passively monitoring bird diversity populations.
|
||
Where BirdNET-Pi takes it to the next level is the ability to setup an SBC - hopefully enclosed in a waterproof space! - and
|
||
monitor birds in your local environment over time.</p>
|
||
<p>I think this project is beyond neat. It runs a bit slow on a Raspberry Pi 3, but overall it runs smoothly. I was even able to
|
||
<a href="https://github.com/mcguirepr89/BirdNET-Pi/pull/821">contribute a PR to the project</a> in April when I noticed a bug in the
|
||
platform after a hard reset of my Pi.</p>
|
||
<h2 id="birdnet-pi-notification-setup---mqtt">BirdNET-PI Notification Setup - MQTT<a hidden class="anchor" aria-hidden="true" href="#birdnet-pi-notification-setup---mqtt">#</a></h2>
|
||
<p>Once you have BirdNET-Pi up and running, you’ll need to head over to the Settings and setup the correct MQTT payloads. Here
|
||
are the possible variables you can pass in an MQTT payload:</p>
|
||
<ul>
|
||
<li><code>$sciname</code>: Scientific Name</li>
|
||
<li><code>$comname</code>: Common Name</li>
|
||
<li><code>$confidence</code>: Confidence Score</li>
|
||
<li><code>$confidencepct</code>: Confidence Score as a percentage (eg. 0.91 => 91)</li>
|
||
<li><code>$listenurl</code>: A link to the detection</li>
|
||
<li><code>$date</code>: Date</li>
|
||
<li><code>$time</code>: Time</li>
|
||
<li><code>$week</code>: Week</li>
|
||
<li><code>$latitude</code>: Latitude</li>
|
||
<li><code>$longitude</code>: Longitude</li>
|
||
<li><code>$cutoff</code>: Minimum Confidence set in “Advanced Settings”</li>
|
||
<li><code>$sens</code>: Sigmoid Sensitivity set in “Advanced Settings”</li>
|
||
<li><code>$overlap</code>: Overlap set in “Advanced Settings”</li>
|
||
<li><code>$flickrimage</code>: A preview image of the detected species from Flickr. Set your API key below.</li>
|
||
</ul>
|
||
<p>For our purposes, we will only be using <code>$comname, $sciname, $date, $time, $week,</code> and <code>$confidence</code>. However, this entire
|
||
process is extremely customizable, which you’ll learn more about in the AppDaemon section. Please expand on it and include
|
||
information that is pertinent to your own uses.</p>
|
||
<p>Here is how I’ve setup my MQTT payload from BirdNET-Pi Settings:</p>
|
||
<p><img loading="lazy" src="../posts/img/birdnet_mqtt_settings.png" alt="Notification Settings" />
|
||
</p>
|
||
<p>Here it is in text form:</p>
|
||
<pre tabindex="0"><code class="language-none" data-lang="none">Notification Title: $comname,
|
||
Notification Body: $sciname, $date, $time, $week, $confidence
|
||
[ ] Notify each new infrequent species detection (< 5 visits per week)
|
||
[ ] Notify each new species first detection of the day
|
||
[X] Notify each new detection
|
||
[X] Send weekly report
|
||
Minimum time between notifications of the same species (sec): 5
|
||
</code></pre><p>To test my MQTT notifications, I use the iOS client “MQTTool”. After signing up, head to “Subscribe” and type <code>birdnet</code> as
|
||
the topic and then click Subscribe. If everything is setup correctly and there are birds being recorded by the BirdNET-Pi’s
|
||
microphone, you should start seeing those detections in the MQTTool app. If so, fantastic news! Let’s move onto AppDaemon.</p>
|
||
<h2 id="appdaemon-script">AppDaemon Script<a hidden class="anchor" aria-hidden="true" href="#appdaemon-script">#</a></h2>
|
||
<p>Now that we have the Pi communicating via MQTT, it’s time to get that information into HomeAssistant. I’ve shared <a href="../posts/birdnet_homeassistant.html#birdnet-appdaemon-script">the full
|
||
script</a> at the bottom of this page, but let’s jump into each
|
||
section. This is not a full tutorial of how to use AppDaemon, but it may help fill in any knowledge gaps with the system.</p>
|
||
<h3 id="imports">Imports<a hidden class="anchor" aria-hidden="true" href="#imports">#</a></h3>
|
||
<p>First, we’re going to import <code>time</code> and <code>requests</code>. We’re going to use time as a backup to the <code>$time</code> component in the
|
||
payload. This can be helpful to see if there delays, or if BirdNET-Pi stopped detecting. We’re then going to use requests to
|
||
pull from Wikipedia’s API and grab a description for our HomeAssistant Dashboard.</p>
|
||
<h3 id="class-definition">Class Definition<a hidden class="anchor" aria-hidden="true" href="#class-definition">#</a></h3>
|
||
<p>To start any AppDaemon app, you need to include a Class that is defined in the <code>apps.yaml</code> file. This is also where we
|
||
initialize and define the various items that will be used in the remainder of the script.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">birdnet</span>(adbase<span style="color:#f92672">.</span>ADBase):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">initialize</span>(self):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_plugin_api(<span style="color:#e6db74">"HASS"</span>)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>adapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_ad_api()
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_plugin_api(<span style="color:#e6db74">"MQTT"</span>)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>birdnet_mqtt <span style="color:#f92672">=</span> <span style="color:#e6db74">"birdnet"</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>listen_event(
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>birdnet_message, <span style="color:#e6db74">"MQTT_MESSAGE"</span>, topic<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>birdnet_mqtt
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span></code></pre></div><p>For this script, we need to use a lot of the AppDaemon APIs across more than just HomeAssistant, so we’re going to be using
|
||
<code>ADBase</code>. By using that, we can initialize the various APIs, which we do in the next 3 lines. In these 3 lines we need to get
|
||
access to HomeAssistant’s APIs, AppDaemon’s APIs, and MQTT APIs - the first and third items are plugins of AppDaemon, and
|
||
AppDaemon is… well… AppDaemon! Here are a few reference docs:</p>
|
||
<ul>
|
||
<li><a href="https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html">MQTT AppDaemon API</a></li>
|
||
<li><a href="https://appdaemon.readthedocs.io/en/latest/HASS_API_REFERENCE.html">HomeAssistant AppDaemon API</a></li>
|
||
<li><a href="https://appdaemon.readthedocs.io/en/latest/AD_API_REFERENCE.html">AppDaemon API</a></li>
|
||
</ul>
|
||
<p>These will indispensable to you as you leverage AppDaemon and expand this little script.</p>
|
||
<p>Once we have access to that, we need to setup the main topic for MQTT from BirdNET-Pi and finally, what event we are
|
||
listening for that will trigger the functions in the rest of the script. <code>self.birdnet_mqtt = "birdnet"</code> is the definition
|
||
for the MQTT topic. Let’s breakdown the last line of the class.</p>
|
||
<p>Here’s a breakdown of each of the items in that last line. You can find the official documentation <a href="https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html#appdaemon.plugins.mqtt.mqttapi.Mqtt.listen_event">here</a>.</p>
|
||
<ul>
|
||
<li><code>self.mqttapi.listen_event</code> - this is what we use in AppDaemon to listen for an MQTT event in order to trigger a function.</li>
|
||
<li><code>self.birdnet_message</code> - the name of the function you’d like to trigger</li>
|
||
<li><code>"MQTT_MESSAGE"</code> - The default event in AppDaemon’s MQTT API plugin. This is used because MQTT doesn’t keep a state in this
|
||
plugin.</li>
|
||
<li><code>topic=self.birdnet_mqtt</code> - The topic that will be received to trigger the function. Defined on the previous line.</li>
|
||
</ul>
|
||
<p>In other words, what we are telling AppDaemon is the following: “When AppDaemon’s MQTT API plugin receives a message with the
|
||
topic of ‘birdnet’, run the function <code>birdnet_message</code>.”</p>
|
||
<h3 id="birdnet_message-function">birdnet_message Function<a hidden class="anchor" aria-hidden="true" href="#birdnet_message-function">#</a></h3>
|
||
<h4 id="part-1-variables-management">Part 1: Variables Management<a hidden class="anchor" aria-hidden="true" href="#part-1-variables-management">#</a></h4>
|
||
<p>Now we get into our first function of the script. The first portion of this script is splitting up the payload that we
|
||
defined from the BirdNET-Pi UI into individual variables that we can better manage later on. If you test this script out by
|
||
adding <code>print()</code> statements at various points, you’ll notice that the payload is received with the following json formatting:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">"payload"</span>: {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">"data"</span>: <span style="color:#e6db74">"data"</span>
|
||
</span></span><span style="display:flex;"><span> }
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span></code></pre></div><p>As such, we need to look <em>inside</em> the payload to begin grabbing the data. The <code>pre_split</code> variable is now just looking at the
|
||
data inside the payload and the rest of the variables take all the date into the payload, split it by the comma, and then
|
||
grab the string by their index. If you remember what <a href="../posts/birdnet_homeassistant.html#birdnet-pi-notification-setup-mqtt">we did above</a> above, you’ll see that we have the various BirdNET information at each of the indexes in the AppDaemon script - 0 through 5.</p>
|
||
<h4 id="part-2-re-publishing-mqtt-payloads">Part 2: Re-Publishing MQTT Payloads<a hidden class="anchor" aria-hidden="true" href="#part-2-re-publishing-mqtt-payloads">#</a></h4>
|
||
<p>This next section is shooting all the variables we just defined back via MQTT. The reason why we do it this way is because we
|
||
need HomeAssistant to grab each of these variables as individual sensors. BirdNET doesn’t give us that capability - it’s a
|
||
single message with all the information in one. <a href="https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html">Here is the documentation from AppDaemon</a> on <code>mqtt_publish</code>. Later on, I’ll show you how to ensure that HomeAssistant takes those topic payloads and adds them as
|
||
entities in your HA setup.</p>
|
||
<h4 id="part-3-wikipedia-sensor">Part 3: Wikipedia Sensor<a hidden class="anchor" aria-hidden="true" href="#part-3-wikipedia-sensor">#</a></h4>
|
||
<p>The next eight lines are a fairly straightforward <a href="https://en.wikipedia.org/api/rest_v1/">API call to Wikipedia</a>. We start
|
||
out by passing the <code>science_name</code> into the URL. The rest of the flags that we are passing into the URL comes from Wikipedia’s
|
||
Docs.
|
||
<code>url = f"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={science_name}"</code></p>
|
||
<p>Once that’s done we call it with <code>response.get(url)</code> and format it with <code>response.json()</code>. Wikipedia returns the json payload
|
||
with the top level of <code>query</code> (which was our action in the url ;) ), and we’re looking for the value within that query.</p>
|
||
<p>All that’s left is to take that query value and push it to HomeAssistant! We can do that with the <code>self.hassapi.set_state</code>
|
||
function. Within the parenthesis we define the name of the sensor (<code>sensor.birdnet_wiki</code>), what it’s state should be (<code>on</code>),
|
||
and any attributes associated with the entity. Since we can’t assign a long description to the basic status of the entity,
|
||
we’re adding an attribute with the key of <code>description</code> and the value will be the wikipedia description garnered from the API
|
||
call.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span> url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=</span><span style="color:#e6db74">{</span>science_name<span style="color:#e6db74">}</span><span style="color:#e6db74">"</span>
|
||
</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url)
|
||
</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>json()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> value <span style="color:#f92672">in</span> response[<span style="color:#e6db74">'query'</span>][<span style="color:#e6db74">'pages'</span>]:
|
||
</span></span><span style="display:flex;"><span> wiki_desc <span style="color:#f92672">=</span> response[<span style="color:#e6db74">'query'</span>][<span style="color:#e6db74">'pages'</span>][value][<span style="color:#e6db74">'extract'</span>]
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi<span style="color:#f92672">.</span>set_state(<span style="color:#e6db74">"sensor.birdnet_wiki"</span>, state<span style="color:#f92672">=</span><span style="color:#e6db74">'on'</span>,
|
||
</span></span><span style="display:flex;"><span> attributes <span style="color:#f92672">=</span> {<span style="color:#e6db74">"description"</span>: wiki_desc})
|
||
</span></span></code></pre></div><h4 id="part-4-generate-picture-for-detection-optional">Part 4: Generate Picture for Detection (Optional)<a hidden class="anchor" aria-hidden="true" href="#part-4-generate-picture-for-detection-optional">#</a></h4>
|
||
<p>This part is optional but I noticed that BirdNET-Pi was already grabbing a Flickr Picture for it’s front end, so I took the
|
||
code from the BirdNET code base and adjusted it a bit for my needs. This will work very similarly to the Wikipedia API call,
|
||
the main difference here being that you need an API key for Flickr. You can find more <a href="https://www.flickr.com/services/api/misc.api_keys.html">information here</a>.</p>
|
||
<p>Given Flickr’s fairly robust API, by passing in the detected bird’s common name, we get amazing results from the community of
|
||
various pictures of the same species of bird. Ever since I’ve set this up, I’ve not seen a mislabeled picture in my
|
||
dashboard!</p>
|
||
<p>The most confusion portion of this section is the <code>image_url</code> as you’ll notice a bunch of <code>data["value"]</code> strings at various
|
||
portions of the URL. The short answer to this is in the previous line with the <code>data</code> variable. A successful query has Flickr
|
||
returning a large payload of information. We’re specifically using <a href="https://www.flickr.com/services/api/flickr.photos.search.html">this</a>
|
||
Flickr API endpoint. While you can pass a lot of variables for your needs, if you scroll down, you can see that the example
|
||
response contains multiple photos in a single response. We’re passing <code>per_page=5</code> to limit some of those response items.
|
||
Left out of that response, though, is a one-stop-shop for a URL to that photo. Thankfully, Flickr can help us put together a
|
||
URL from the data in the response.</p>
|
||
|
||
|
||
<style type="text/css">
|
||
.box-shortcode {
|
||
padding: 1.6em;
|
||
padding-top: 1.4em;
|
||
line-height: 1.4em;
|
||
margin-top: 1em;
|
||
margin-bottom: 2em;
|
||
border-radius: 4px;
|
||
color: #444;
|
||
background: #f3ebe850;
|
||
}
|
||
|
||
.box-title {
|
||
margin: -18px -18px 12px;
|
||
padding: 4px 18px;
|
||
border-radius: 4px 4px 0 0;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
background: #6ab0de;
|
||
}
|
||
.box-shortcode.warning .box-title {
|
||
background: #ff6b6b;
|
||
}
|
||
.box-shortcode.warning {
|
||
background: #ff6b6b4f;
|
||
}
|
||
.box-shortcode.info .box-title {
|
||
background: #0089e488;
|
||
}
|
||
.box-shortcode.info {
|
||
background: #0089e41c;
|
||
box-shadow: 3px 3px 5px #0089e410;
|
||
}
|
||
.box-shortcode.important .box-title {
|
||
background: #f7ec2c;
|
||
}
|
||
.box-shortcode.important {
|
||
background: #f7ec2c7d;
|
||
}
|
||
.box-shortcode.tip .box-title {
|
||
background: #a3ffa34d;
|
||
}
|
||
.box-shortcode.tip {
|
||
background: #a3ffa34d;
|
||
box-shadow: 3px 3px 5px #0089e410;
|
||
}
|
||
.icon-box {
|
||
display: inline-flex;
|
||
align-self: center;
|
||
margin-right: 8px;
|
||
}
|
||
.icon-box img,
|
||
.icon-box svg {
|
||
height: 1em;
|
||
width: 1em;
|
||
fill: currentColor;
|
||
}
|
||
.icon-box img,
|
||
.icon-box.baseline svg {
|
||
top: 0.125em;
|
||
position: relative;
|
||
}
|
||
.box-shortcode p {
|
||
margin-bottom: 0.6em;
|
||
}
|
||
.box-shortcode p:first-of-type {
|
||
display: inline;
|
||
}
|
||
.box-shortcode p:nth-of-type(2) {
|
||
margin-top: 0.6em;
|
||
}
|
||
.box-shortcode p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
</style>
|
||
|
||
|
||
<svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">
|
||
<symbol id="tip-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">
|
||
<path
|
||
d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>
|
||
</symbol>
|
||
<symbol id="important-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">
|
||
<path
|
||
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>
|
||
</symbol>
|
||
<symbol id="warning-box" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">
|
||
<path
|
||
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>
|
||
</symbol>
|
||
<symbol id="info-box" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">
|
||
<path
|
||
d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>
|
||
</symbol>
|
||
</svg><div class="box box-shortcode info" >
|
||
<span class="icon-box baseline">
|
||
<svg><use href="#info-box"></use></svg>
|
||
</span>
|
||
<p><em>Note: Full Transparency that I only learned about this after reading through BirdNET-Pi’s code base. Full credit goes to
|
||
<a href="https://github.com/mcguirepr89">mcguirepr89</a>. For additional reference, here is Flickr’s <a href="https://www.flickr.com/services/api/misc.urls.html">official page on construction
|
||
photo image URLS</a></em></p>
|
||
</div>
|
||
|
||
<p>With this response, we now have the variables we need to construct the URL to actually render the image. Those variables are:
|
||
Farm ID, Server ID, ID and Secret. I haven’t yet looked into why we need “farm” when the official documentation doesn’t state
|
||
anything about it.</p>
|
||
<p>Almost there! We now do the same as we did with the Wikipedia API response. We create a sensor in HomeAssistant! We’re
|
||
calling this sensor <code>sensor.birdpic</code>, ensuring the <code>state=on</code>, and giving it the attributes of the <code>image_url</code> as garnered
|
||
from Flickr.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span> headers <span style="color:#f92672">=</span> {<span style="color:#e6db74">'User-Agent'</span>: <span style="color:#e6db74">'Python_Flickr/1.0'</span>}
|
||
</span></span><span style="display:flex;"><span> flickr_api <span style="color:#f92672">=</span> <span style="color:#e6db74">"enter_your_api_key"</span>
|
||
</span></span><span style="display:flex;"><span> flickr_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=</span><span style="color:#e6db74">{</span>flickr_api<span style="color:#e6db74">}</span><span style="color:#e6db74">&text=</span><span style="color:#e6db74">{</span>common_name<span style="color:#e6db74">}</span><span style="color:#e6db74"> bird&sort=relevance&per_page=5&media=photos&format=json&nojsoncallback=1"</span>
|
||
</span></span><span style="display:flex;"><span> flickr_resp <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url<span style="color:#f92672">=</span>flickr_url, headers<span style="color:#f92672">=</span>headers)
|
||
</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> flickr_resp<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">"photos"</span>][<span style="color:#e6db74">"photo"</span>][<span style="color:#ae81ff">0</span>]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> image_url <span style="color:#f92672">=</span> <span style="color:#e6db74">'https://farm'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"farm"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'.static.flickr.com/'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"server"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'/'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"id"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'_'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"secret"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'_n.jpg'</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi<span style="color:#f92672">.</span>set_state(<span style="color:#e6db74">"sensor.birdpic"</span>, state<span style="color:#f92672">=</span><span style="color:#e6db74">'on'</span>,
|
||
</span></span><span style="display:flex;"><span> attributes<span style="color:#f92672">=</span>{<span style="color:#e6db74">"image"</span>: image_url})
|
||
</span></span></code></pre></div><h2 id="importing-mqtt-sensors-into-homeassistant">Importing MQTT Sensors into HomeAssistant<a hidden class="anchor" aria-hidden="true" href="#importing-mqtt-sensors-into-homeassistant">#</a></h2>
|
||
<p>Now that we have all the sensors defined and communicating via MQTT, we have one more step to import them into HomeAssistant.
|
||
<a href="https://www.home-assistant.io/integrations/mqtt/">This MQTT documentation</a> by HomeAssistant is good to read about if you
|
||
need a broker setup. I will not be going over the broker in this tutorial, but may add one in the future. I tend to like the
|
||
yaml configuration for HomeAssistant, so for the sake of this guide, I’ll be referencing the <a href="https://www.home-assistant.io/integrations/mqtt/#manual-configured-mqtt-items">manual configuration of MQTT
|
||
items and sensors</a>.</p>
|
||
<p>To add the sensors from above, open up your <code>configuration.yaml</code> file in your favorite editor. You’ll then want to add the
|
||
mqtt platform and domain:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">mqtt</span>:
|
||
</span></span><span style="display:flex;"><span> - { <span style="color:#f92672">domain }</span>:
|
||
</span></span></code></pre></div><p>For the BirdNet sensors, we will be using a single domain: <code>sensor</code>. Feel free to copy and paste my config from below, but
|
||
make sure the names of each entity align with your needs, syntax, and nomenclature/system.</p>
|
||
<p><strong>Full MQTT Sensors in Configuration.yml</strong></p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">mqtt</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">sensor</span>:
|
||
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#e6db74">"Bird Common Name"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">state_topic</span>: <span style="color:#e6db74">"birdnet/sensors/common_name"</span>
|
||
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#e6db74">"Bird Science Name"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">state_topic</span>: <span style="color:#e6db74">"birdnet/sensors/science_name"</span>
|
||
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#e6db74">"Bird Time Seen"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">state_topic</span>: <span style="color:#e6db74">"birdnet/sensors/time_seen"</span>
|
||
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#e6db74">"Bird Date Seen"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">state_topic</span>: <span style="color:#e6db74">"birdnet/sensors/date_seen"</span>
|
||
</span></span><span style="display:flex;"><span> - <span style="color:#f92672">name</span>: <span style="color:#e6db74">"Bird Confidence"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">state_topic</span>: <span style="color:#e6db74">"birdnet/sensors/confidence"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">value_template</span>: <span style="color:#e6db74">'{{ (value|float(0) *100) | round(1) }}'</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">unit_of_measurement</span>: <span style="color:#e6db74">'%'</span>
|
||
</span></span></code></pre></div><p>You might be looking at the list above and wondering where the Flickr and Wikipedia Description entities are. They were
|
||
already created by the AppDaemon script! Specifically, <code>self.hassapi.set_state()</code> function will either update the state for
|
||
an exisiting entity or, if the entity doesn’t exist, it will create a new one.</p>
|
||
<p>For the rest of the mqtt payloads, we need HomeAssistant to create them as they come in, which is why we add the above
|
||
code block to our HomeAssistant configuration file. To be clear, you <em>do not</em> need to add the Wikipedia and Flickr sensors to
|
||
HA’s configuration file!</p>
|
||
<h2 id="adding-the-camera-entity">Adding the Camera entity<a hidden class="anchor" aria-hidden="true" href="#adding-the-camera-entity">#</a></h2>
|
||
<p>Last but not least, we need to add a camera entity to ensure that the <code>sensor.birdpic</code> can actually be rendered visually.
|
||
It’s really easy to add this sensor, so this should be quick. Here’s how:</p>
|
||
<ol>
|
||
<li>In HomeAssistant, navigate to Settings > Devices & Services > Integrations</li>
|
||
<li>Click “+ Add Integration” in the bottom right-hand corner. Alternatively, if you already have a camera integration
|
||
enabled, look for the “Generic Camera” card and click “Add Entry”</li>
|
||
<li>Name the sensor. In this case, I called it “BirdPicturesfromFlickr” and renamed the entity to <code>camera.birdnet_flickr</code>.</li>
|
||
<li>You should now see a “Still Image URL” as the first of a few options on the screen. Enter the following into the still
|
||
image field: <code>{{ state_attr('sensor.birdpic', 'image') }}</code> (This is the sensor we created in AppDaemon with the flickr url
|
||
as the attribute).</li>
|
||
<li>Stream Source and RTSP transport protocol can both be left blank.</li>
|
||
<li>Authentication - select “digest”.</li>
|
||
<li>Username and Password can be left blank.</li>
|
||
<li>Frame Rate - 2</li>
|
||
<li>Leave the rest of the check boxes unchecked and click Submit</li>
|
||
</ol>
|
||
<p>When you now click on the entity, you should see an image!</p>
|
||
<p><img loading="lazy" src="../posts/img/birdnet_camera_entity.png" alt="HomeAssistant BirdNET Camera Entity" />
|
||
</p>
|
||
<p>By this point, you should have successfully created 7 new sensors in HomeAssistant. In Part 2 of this article, we’ll take a
|
||
look at Home Assistant, see what these sensors look like, and create a rudimentary dashboard.</p>
|
||
<h2 id="birdnet-appdaemon-script">Birdnet AppDaemon Script<a hidden class="anchor" aria-hidden="true" href="#birdnet-appdaemon-script">#</a></h2>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> time
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">birdnet</span>(adbase<span style="color:#f92672">.</span>ADBase):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">initialize</span>(self):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_plugin_api(<span style="color:#e6db74">"HASS"</span>)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>adapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_ad_api()
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>get_plugin_api(<span style="color:#e6db74">"MQTT"</span>)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>birdnet_mqtt <span style="color:#f92672">=</span> <span style="color:#e6db74">"birdnet"</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>listen_event(
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>birdnet_message, <span style="color:#e6db74">"MQTT_MESSAGE"</span>, topic<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>birdnet_mqtt
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">birdnet_message</span>(self, event_name, data, kwargs):
|
||
</span></span><span style="display:flex;"><span> pre_split <span style="color:#f92672">=</span> data[<span style="color:#e6db74">"payload"</span>]
|
||
</span></span><span style="display:flex;"><span> common_name <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">0</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span> science_name <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span> date_seen <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">2</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span> time_seen <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">3</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span> week_seen <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span> confidence <span style="color:#f92672">=</span> pre_split<span style="color:#f92672">.</span>split(<span style="color:#e6db74">','</span>)[<span style="color:#ae81ff">5</span>]<span style="color:#f92672">.</span>strip()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># print(f"A {common_name} was seen on {date_seen} at {time_seen}. Confidence is {confidence}.")</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>mqtt_publish(<span style="color:#e6db74">"birdnet/sensors/common_name"</span>, common_name)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>mqtt_publish(<span style="color:#e6db74">"birdnet/sensors/science_name"</span>, science_name)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>mqtt_publish(<span style="color:#e6db74">"birdnet/sensors/time_seen"</span>, time_seen)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>mqtt_publish(<span style="color:#e6db74">"birdnet/sensors/date_seen"</span>, date_seen)
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>mqttapi<span style="color:#f92672">.</span>mqtt_publish(<span style="color:#e6db74">"birdnet/sensors/confidence"</span>, confidence)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=</span><span style="color:#e6db74">{</span>science_name<span style="color:#e6db74">}</span><span style="color:#e6db74">"</span>
|
||
</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url)
|
||
</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> response<span style="color:#f92672">.</span>json()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> value <span style="color:#f92672">in</span> response[<span style="color:#e6db74">'query'</span>][<span style="color:#e6db74">'pages'</span>]:
|
||
</span></span><span style="display:flex;"><span> wiki_desc <span style="color:#f92672">=</span> response[<span style="color:#e6db74">'query'</span>][<span style="color:#e6db74">'pages'</span>][value][<span style="color:#e6db74">'extract'</span>]
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi<span style="color:#f92672">.</span>set_state(<span style="color:#e6db74">"sensor.birdnet_wiki"</span>, state<span style="color:#f92672">=</span><span style="color:#e6db74">'on'</span>,
|
||
</span></span><span style="display:flex;"><span> attributes <span style="color:#f92672">=</span> {<span style="color:#e6db74">"description"</span>: wiki_desc})
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> headers <span style="color:#f92672">=</span> {<span style="color:#e6db74">'User-Agent'</span>: <span style="color:#e6db74">'Python_Flickr/1.0'</span>}
|
||
</span></span><span style="display:flex;"><span> flickr_api <span style="color:#f92672">=</span> <span style="color:#e6db74">"enter_your_api_key"</span>
|
||
</span></span><span style="display:flex;"><span> flickr_url <span style="color:#f92672">=</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=</span><span style="color:#e6db74">{</span>flickr_api<span style="color:#e6db74">}</span><span style="color:#e6db74">&text=</span><span style="color:#e6db74">{</span>common_name<span style="color:#e6db74">}</span><span style="color:#e6db74"> bird&sort=relevance&per_page=5&media=photos&format=json&nojsoncallback=1"</span>
|
||
</span></span><span style="display:flex;"><span> flickr_resp <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(url<span style="color:#f92672">=</span>flickr_url, headers<span style="color:#f92672">=</span>headers)
|
||
</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> flickr_resp<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">"photos"</span>][<span style="color:#e6db74">"photo"</span>][<span style="color:#ae81ff">0</span>]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> image_url <span style="color:#f92672">=</span> <span style="color:#e6db74">'https://farm'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"farm"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'.static.flickr.com/'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"server"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'/'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"id"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'_'</span><span style="color:#f92672">+</span>str(data[<span style="color:#e6db74">"secret"</span>])<span style="color:#f92672">+</span><span style="color:#e6db74">'_n.jpg'</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hassapi<span style="color:#f92672">.</span>set_state(<span style="color:#e6db74">"sensor.birdpic"</span>, state<span style="color:#f92672">=</span><span style="color:#e6db74">'on'</span>,
|
||
</span></span><span style="display:flex;"><span> attributes<span style="color:#f92672">=</span>{<span style="color:#e6db74">"image"</span>: image_url})
|
||
</span></span></code></pre></div><style>
|
||
.box-shortcode {
|
||
color: #e8e8e8;
|
||
border: none;
|
||
}
|
||
.post-content img {
|
||
margin: auto
|
||
}
|
||
</style>
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="post-footer">
|
||
<ul class="post-tags">
|
||
<li><a href="../tags/homeassistant.html">Homeassistant</a></li>
|
||
<li><a href="../tags/python.html">Python</a></li>
|
||
<li><a href="../tags/diy.html">Diy</a></li>
|
||
</ul>
|
||
<nav class="paginav">
|
||
<a class="prev" href="../posts/birdnet_homeassistant_part2.html">
|
||
<span class="title">« Prev</span>
|
||
<br>
|
||
<span>BirdNET-PI & HomeAssistant: Part 2</span>
|
||
</a>
|
||
<a class="next" href="../posts/multiple_git_remotes.html">
|
||
<span class="title">Next »</span>
|
||
<br>
|
||
<span>Pushing a Single Local Git Repo to Multiple Remote Repos</span>
|
||
</a>
|
||
</nav>
|
||
|
||
|
||
<ul class="share-buttons">
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on x"
|
||
href="https://x.com/intent/tweet/?text=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201&url=%2fposts%2fbirdnet_homeassistant.html&hashtags=homeassistant%2cpython%2cdiy">
|
||
<svg version="1.1" viewBox="0 0 512 512" xml:space="preserve" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M512 62.554 L 512 449.446 C 512 483.97 483.97 512 449.446 512 L 62.554 512 C 28.03 512 0 483.97 0 449.446 L 0 62.554 C 0 28.03 28.029 0 62.554 0 L 449.446 0 C 483.971 0 512 28.03 512 62.554 Z M 269.951 190.75 L 182.567 75.216 L 56 75.216 L 207.216 272.95 L 63.9 436.783 L 125.266 436.783 L 235.9 310.383 L 332.567 436.783 L 456 436.783 L 298.367 228.367 L 432.367 75.216 L 371.033 75.216 Z M 127.633 110 L 164.101 110 L 383.481 400.065 L 349.5 400.065 Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on linkedin"
|
||
href="https://www.linkedin.com/shareArticle?mini=true&url=%2fposts%2fbirdnet_homeassistant.html&title=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201&summary=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201&source=%2fposts%2fbirdnet_homeassistant.html">
|
||
<svg version="1.1" viewBox="0 0 512 512" xml:space="preserve" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M449.446,0c34.525,0 62.554,28.03 62.554,62.554l0,386.892c0,34.524 -28.03,62.554 -62.554,62.554l-386.892,0c-34.524,0 -62.554,-28.03 -62.554,-62.554l0,-386.892c0,-34.524 28.029,-62.554 62.554,-62.554l386.892,0Zm-288.985,423.278l0,-225.717l-75.04,0l0,225.717l75.04,0Zm270.539,0l0,-129.439c0,-69.333 -37.018,-101.586 -86.381,-101.586c-39.804,0 -57.634,21.891 -67.617,37.266l0,-31.958l-75.021,0c0.995,21.181 0,225.717 0,225.717l75.02,0l0,-126.056c0,-6.748 0.486,-13.492 2.474,-18.315c5.414,-13.475 17.767,-27.434 38.494,-27.434c27.135,0 38.007,20.707 38.007,51.037l0,120.768l75.024,0Zm-307.552,-334.556c-25.674,0 -42.448,16.879 -42.448,39.002c0,21.658 16.264,39.002 41.455,39.002l0.484,0c26.165,0 42.452,-17.344 42.452,-39.002c-0.485,-22.092 -16.241,-38.954 -41.943,-39.002Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on reddit"
|
||
href="https://reddit.com/submit?url=%2fposts%2fbirdnet_homeassistant.html&title=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201">
|
||
<svg version="1.1" viewBox="0 0 512 512" xml:space="preserve" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M449.446,0c34.525,0 62.554,28.03 62.554,62.554l0,386.892c0,34.524 -28.03,62.554 -62.554,62.554l-386.892,0c-34.524,0 -62.554,-28.03 -62.554,-62.554l0,-386.892c0,-34.524 28.029,-62.554 62.554,-62.554l386.892,0Zm-3.446,265.638c0,-22.964 -18.616,-41.58 -41.58,-41.58c-11.211,0 -21.361,4.457 -28.841,11.666c-28.424,-20.508 -67.586,-33.757 -111.204,-35.278l18.941,-89.121l61.884,13.157c0.756,15.734 13.642,28.29 29.56,28.29c16.407,0 29.706,-13.299 29.706,-29.701c0,-16.403 -13.299,-29.702 -29.706,-29.702c-11.666,0 -21.657,6.792 -26.515,16.578l-69.105,-14.69c-1.922,-0.418 -3.939,-0.042 -5.585,1.036c-1.658,1.073 -2.811,2.761 -3.224,4.686l-21.152,99.438c-44.258,1.228 -84.046,14.494 -112.837,35.232c-7.468,-7.164 -17.589,-11.591 -28.757,-11.591c-22.965,0 -41.585,18.616 -41.585,41.58c0,16.896 10.095,31.41 24.568,37.918c-0.639,4.135 -0.99,8.328 -0.99,12.576c0,63.977 74.469,115.836 166.33,115.836c91.861,0 166.334,-51.859 166.334,-115.836c0,-4.218 -0.347,-8.387 -0.977,-12.493c14.564,-6.47 24.735,-21.034 24.735,-38.001Zm-119.474,108.193c-20.27,20.241 -59.115,21.816 -70.534,21.816c-11.428,0 -50.277,-1.575 -70.522,-21.82c-3.007,-3.008 -3.007,-7.882 0,-10.889c3.003,-2.999 7.882,-3.003 10.885,0c12.777,12.781 40.11,17.317 59.637,17.317c19.522,0 46.86,-4.536 59.657,-17.321c3.016,-2.999 7.886,-2.995 10.885,0.008c3.008,3.011 3.003,7.882 -0.008,10.889Zm-5.23,-48.781c-16.373,0 -29.701,-13.324 -29.701,-29.698c0,-16.381 13.328,-29.714 29.701,-29.714c16.378,0 29.706,13.333 29.706,29.714c0,16.374 -13.328,29.698 -29.706,29.698Zm-160.386,-29.702c0,-16.381 13.328,-29.71 29.714,-29.71c16.369,0 29.689,13.329 29.689,29.71c0,16.373 -13.32,29.693 -29.689,29.693c-16.386,0 -29.714,-13.32 -29.714,-29.693Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on facebook"
|
||
href="https://facebook.com/sharer/sharer.php?u=%2fposts%2fbirdnet_homeassistant.html">
|
||
<svg version="1.1" viewBox="0 0 512 512" xml:space="preserve" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M449.446,0c34.525,0 62.554,28.03 62.554,62.554l0,386.892c0,34.524 -28.03,62.554 -62.554,62.554l-106.468,0l0,-192.915l66.6,0l12.672,-82.621l-79.272,0l0,-53.617c0,-22.603 11.073,-44.636 46.58,-44.636l36.042,0l0,-70.34c0,0 -32.71,-5.582 -63.982,-5.582c-65.288,0 -107.96,39.569 -107.96,111.204l0,62.971l-72.573,0l0,82.621l72.573,0l0,192.915l-191.104,0c-34.524,0 -62.554,-28.03 -62.554,-62.554l0,-386.892c0,-34.524 28.029,-62.554 62.554,-62.554l386.892,0Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on whatsapp"
|
||
href="https://api.whatsapp.com/send?text=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201%20-%20%2fposts%2fbirdnet_homeassistant.html">
|
||
<svg version="1.1" viewBox="0 0 512 512" xml:space="preserve" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M449.446,0c34.525,0 62.554,28.03 62.554,62.554l0,386.892c0,34.524 -28.03,62.554 -62.554,62.554l-386.892,0c-34.524,0 -62.554,-28.03 -62.554,-62.554l0,-386.892c0,-34.524 28.029,-62.554 62.554,-62.554l386.892,0Zm-58.673,127.703c-33.842,-33.881 -78.847,-52.548 -126.798,-52.568c-98.799,0 -179.21,80.405 -179.249,179.234c-0.013,31.593 8.241,62.428 23.927,89.612l-25.429,92.884l95.021,-24.925c26.181,14.28 55.659,21.807 85.658,21.816l0.074,0c98.789,0 179.206,-80.413 179.247,-179.243c0.018,-47.895 -18.61,-92.93 -52.451,-126.81Zm-126.797,275.782l-0.06,0c-26.734,-0.01 -52.954,-7.193 -75.828,-20.767l-5.441,-3.229l-56.386,14.792l15.05,-54.977l-3.542,-5.637c-14.913,-23.72 -22.791,-51.136 -22.779,-79.287c0.033,-82.142 66.867,-148.971 149.046,-148.971c39.793,0.014 77.199,15.531 105.329,43.692c28.128,28.16 43.609,65.592 43.594,105.4c-0.034,82.149 -66.866,148.983 -148.983,148.984Zm81.721,-111.581c-4.479,-2.242 -26.499,-13.075 -30.604,-14.571c-4.105,-1.495 -7.091,-2.241 -10.077,2.241c-2.986,4.483 -11.569,14.572 -14.182,17.562c-2.612,2.988 -5.225,3.364 -9.703,1.12c-4.479,-2.241 -18.91,-6.97 -36.017,-22.23c-13.314,-11.876 -22.304,-26.542 -24.916,-31.026c-2.612,-4.484 -0.279,-6.908 1.963,-9.14c2.016,-2.007 4.48,-5.232 6.719,-7.847c2.24,-2.615 2.986,-4.484 4.479,-7.472c1.493,-2.99 0.747,-5.604 -0.374,-7.846c-1.119,-2.241 -10.077,-24.288 -13.809,-33.256c-3.635,-8.733 -7.327,-7.55 -10.077,-7.688c-2.609,-0.13 -5.598,-0.158 -8.583,-0.158c-2.986,0 -7.839,1.121 -11.944,5.604c-4.105,4.484 -15.675,15.32 -15.675,37.364c0,22.046 16.048,43.342 18.287,46.332c2.24,2.99 31.582,48.227 76.511,67.627c10.685,4.615 19.028,7.371 25.533,9.434c10.728,3.41 20.492,2.929 28.209,1.775c8.605,-1.285 26.499,-10.833 30.231,-21.295c3.732,-10.464 3.732,-19.431 2.612,-21.298c-1.119,-1.869 -4.105,-2.99 -8.583,-5.232Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on telegram"
|
||
href="https://telegram.me/share/url?text=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201&url=%2fposts%2fbirdnet_homeassistant.html">
|
||
<svg version="1.1" xml:space="preserve" viewBox="2 2 28 28" height="30px" width="30px" fill="currentColor">
|
||
<path
|
||
d="M26.49,29.86H5.5a3.37,3.37,0,0,1-2.47-1,3.35,3.35,0,0,1-1-2.47V5.48A3.36,3.36,0,0,1,3,3,3.37,3.37,0,0,1,5.5,2h21A3.38,3.38,0,0,1,29,3a3.36,3.36,0,0,1,1,2.46V26.37a3.35,3.35,0,0,1-1,2.47A3.38,3.38,0,0,1,26.49,29.86Zm-5.38-6.71a.79.79,0,0,0,.85-.66L24.73,9.24a.55.55,0,0,0-.18-.46.62.62,0,0,0-.41-.17q-.08,0-16.53,6.11a.59.59,0,0,0-.41.59.57.57,0,0,0,.43.52l4,1.24,1.61,4.83a.62.62,0,0,0,.63.43.56.56,0,0,0,.4-.17L16.54,20l4.09,3A.9.9,0,0,0,21.11,23.15ZM13.8,20.71l-1.21-4q8.72-5.55,8.78-5.55c.15,0,.23,0,.23.16a.18.18,0,0,1,0,.06s-2.51,2.3-7.52,6.8Z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a target="_blank" rel="noopener noreferrer" aria-label="share Creating a BirdNetPi Dashboard in HomeAssistant - Part 1 on ycombinator"
|
||
href="https://news.ycombinator.com/submitlink?t=Creating%20a%20BirdNetPi%20Dashboard%20in%20HomeAssistant%20-%20Part%201&u=%2fposts%2fbirdnet_homeassistant.html">
|
||
<svg version="1.1" xml:space="preserve" width="30px" height="30px" viewBox="0 0 512 512" fill="currentColor"
|
||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
|
||
<path
|
||
d="M449.446 0C483.971 0 512 28.03 512 62.554L512 449.446C512 483.97 483.97 512 449.446 512L62.554 512C28.03 512 0 483.97 0 449.446L0 62.554C0 28.03 28.029 0 62.554 0L449.446 0ZM183.8767 87.9921H121.8427L230.6673 292.4508V424.0079H281.3328V292.4508L390.1575 87.9921H328.1233L256 238.2489z" />
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
|
||
</footer>
|
||
</article>
|
||
</main>
|
||
|
||
<footer class="footer">
|
||
<span>© 2024 <a href="../">Norm-working Packets 💾</a></span>
|
||
<span>
|
||
Powered by
|
||
<a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
|
||
<a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
|
||
</span>
|
||
</footer>
|
||
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
|
||
<path d="M12 6H0l6-6z" />
|
||
</svg>
|
||
</a>
|
||
|
||
<script>
|
||
let menu = document.getElementById('menu')
|
||
if (menu) {
|
||
menu.scrollLeft = localStorage.getItem("menu-scroll-position");
|
||
menu.onscroll = function () {
|
||
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
var id = this.getAttribute("href").substr(1);
|
||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
|
||
behavior: "smooth"
|
||
});
|
||
} else {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
|
||
}
|
||
if (id === "top") {
|
||
history.replaceState(null, null, " ");
|
||
} else {
|
||
history.pushState(null, null, `#${id}`);
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
<script>
|
||
var mybutton = document.getElementById("top-link");
|
||
window.onscroll = function () {
|
||
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
|
||
mybutton.style.visibility = "visible";
|
||
mybutton.style.opacity = "1";
|
||
} else {
|
||
mybutton.style.visibility = "hidden";
|
||
mybutton.style.opacity = "0";
|
||
}
|
||
};
|
||
|
||
</script>
|
||
<script>
|
||
document.getElementById("theme-toggle").addEventListener("click", () => {
|
||
if (document.body.className.includes("dark")) {
|
||
document.body.classList.remove('dark');
|
||
localStorage.setItem("pref-theme", 'light');
|
||
} else {
|
||
document.body.classList.add('dark');
|
||
localStorage.setItem("pref-theme", 'dark');
|
||
}
|
||
})
|
||
|
||
</script>
|
||
<script>
|
||
document.querySelectorAll('pre > code').forEach((codeblock) => {
|
||
const container = codeblock.parentNode.parentNode;
|
||
|
||
const copybutton = document.createElement('button');
|
||
copybutton.classList.add('copy-code');
|
||
copybutton.innerHTML = 'copy';
|
||
|
||
function copyingDone() {
|
||
copybutton.innerHTML = 'copied!';
|
||
setTimeout(() => {
|
||
copybutton.innerHTML = 'copy';
|
||
}, 2000);
|
||
}
|
||
|
||
copybutton.addEventListener('click', (cb) => {
|
||
if ('clipboard' in navigator) {
|
||
navigator.clipboard.writeText(codeblock.textContent);
|
||
copyingDone();
|
||
return;
|
||
}
|
||
|
||
const range = document.createRange();
|
||
range.selectNodeContents(codeblock);
|
||
const selection = window.getSelection();
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
try {
|
||
document.execCommand('copy');
|
||
copyingDone();
|
||
} catch (e) { };
|
||
selection.removeRange(range);
|
||
});
|
||
|
||
if (container.classList.contains("highlight")) {
|
||
container.appendChild(copybutton);
|
||
} else if (container.parentNode.firstChild == container) {
|
||
|
||
} else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
|
||
|
||
codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
|
||
} else {
|
||
|
||
codeblock.parentNode.appendChild(copybutton);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|