From 84ecd6c471b84294e6da1a1cb82c2c59a51eb27c Mon Sep 17 00:00:00 2001 From: Norm Rasmussen Date: Wed, 11 Oct 2023 17:18:04 -0400 Subject: [PATCH] Notes for some companies. --- CustomerNotes/DataSnipper/DataSnipper.md | 23 +- CustomerNotes/G2/G2.md | 9 + CustomerNotes/Walmart/Walmart.md | 48 +++ CustomerNotes/Zenjob/Zenjob.md | 6 + temp.md | 375 +++++++++++++++++++++++ 5 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 temp.md diff --git a/CustomerNotes/DataSnipper/DataSnipper.md b/CustomerNotes/DataSnipper/DataSnipper.md index 989febdd..a2d11e29 100644 --- a/CustomerNotes/DataSnipper/DataSnipper.md +++ b/CustomerNotes/DataSnipper/DataSnipper.md @@ -122,4 +122,25 @@ In addition: activate or deactivate activities based on a property. - Questions about Badges - he said he couldn't find them and Patrycja chuckled and let him know that its totally custom. - Dirk has some designs, but only one was added to Figma. - Internal discussions on the number of activities per course. Some DS people adding a ton of activities. -- Dirk wants to add time next to section or activities for estimated time to complete. +- Dirk wants to add time next to section or activities for estimated time to complete + +## 10/10/2023 + +### Sender Domains, Milestones, and Custom Domain + +| Record Type | URL | Value| +|----|----|----| +| CNAME | np._domainkey.datasnipper.com | np.domainkey.u1860655.wl156.sendgrid.net | +| CNAME | np2._domainkey.datasnipper.com | np2.domainkey.u1860655.wl156.sendgrid.net | +| TXT | datasnipper.com | northpass-domain-verification=9e3afad8d68c30cc9c50e8a02afda447 | + +TODO: Add logo to Footer + +Design: + +- Grey-out badges should be more opaque/faded +- Fix Links in header +- Add Resources Description on button. +- Learner preview shows blank page - no header, footer, etc. + +Candide to connect Norm to Gabriele Pipo & IT to help set up the custom domains for email and URL. diff --git a/CustomerNotes/G2/G2.md b/CustomerNotes/G2/G2.md index fdf71a14..0cb66308 100644 --- a/CustomerNotes/G2/G2.md +++ b/CustomerNotes/G2/G2.md @@ -432,3 +432,12 @@ Erin to put in fast lane request to switch Cert name from G2 Core to G2 Fundamen House keeping: * Erin in Boston - October 17th & 18th. + +## 10/10/2023 + +### Content sync - Erin + +* Erin got message today about a 12th due date. Design team asked for the 16th. +* If she says no, they likely still won't deliver by the 12th. +* Potential delay, as long as EOM release, Kaitlin will be happy. +* TODO: Review the two draft courses - Core Onboarding & Review Growth Onboarding diff --git a/CustomerNotes/Walmart/Walmart.md b/CustomerNotes/Walmart/Walmart.md index aa7df6b5..9d33bd3f 100644 --- a/CustomerNotes/Walmart/Walmart.md +++ b/CustomerNotes/Walmart/Walmart.md @@ -708,3 +708,51 @@ TODO: Test if a course can be accessible outside of Northpass without it being a * Sara is no longer working on the Spark side * Frank is drowning in gif work and they need help * Norm to ask if we can replace him. + +## 10/10/2023 + +### Metrics Reporting Meeting + +*Background:* + +* Patrick Duffy - Drivers Ops +* "My Metrics" for driver metrics +* Spark was started 5 years ago. +* Currently revamping all of MyMetrics programs +* Want to understand how MyMetrics intersects with Resource Center + +*What they want to see differently* + +* Goal is to decrease number of clicks for Drivers +* App experience has brief description of every metric and the value +* Link that goes between individual metric +* Metrics include: + * Dropped Trips + * Accepted Offers + * Customer Rating + * On time Arrivals + * (Shopping Metrics) - Items Found +* "4 Total trips, Learn more about your metrics >" --> This would link to Resource Center + * Currently links to website + * Website is for prospective and new drivers, not active + * Want to link to RC, which is for existing drivers +* Current metrics will stay in the app. Those are not moving. +* They just want to show the definition of the metrics in the RC. +* They keep bringing up "landing pages". What else do they want on the landing pages? +* Two versions: + * Short term - expanded definition with metric rules and exceptions + * Long term - increased transparency, what went into their calculation. + * If 40%, what went into and affected the 40%. +* Possible Flows: + * MyMetrics > click "see definition" > [utilize context aware mechanism] click "Done" at bottom + * MyMetrics > click "see definition" > include box that says "click here to go back or click below to see other definitions." +* Issues: + * Major pain point is lack of understanding of MyMetrics + * Drivers complain about definitions of metrics +* Kaitlyn: a few avenues for solutions +* TODO: Ask Rob if Walmart engineering is needed for deep linking to activities. They claim they need to do that. +* How does Pat update MyMetrics page and content? + * Looking for a January release + * This would be a big overhaul + * They are providing avenues and solutions to leadership + * Ece to be managing front end and back end dependencies. diff --git a/CustomerNotes/Zenjob/Zenjob.md b/CustomerNotes/Zenjob/Zenjob.md index b36786cf..875fd5da 100644 --- a/CustomerNotes/Zenjob/Zenjob.md +++ b/CustomerNotes/Zenjob/Zenjob.md @@ -149,3 +149,9 @@ Thanks, Sophie, and Viky! Enjoy your vacation, Sophie! Talk to you when you retu Best, Norm + +## 10/11/2023 + +### Sophie back from PTO + + diff --git a/temp.md b/temp.md new file mode 100644 index 00000000..de21f5c7 --- /dev/null +++ b/temp.md @@ -0,0 +1,375 @@ + +--- +title: 'Creating a BirdNetPi Dashboard in HomeAssistant - Part 1' +date: 2023-09-30T11:21:55-04:00 +tags: ["homeassistant", "python", "diy"] +author: "Me" +showToc: true +TocOpen: false +draft: false +hidemeta: false +description: 'Learn how to take BirdNET-Pi Detections to create and display entities in HomeAssistant.' +disableHLJS: true # to disable highlightjs +disableShare: false +disableHLJS: false +hideSummary: false +searchHidden: true +ShowReadingTime: true +ShowBreadCrumbs: true +ShowPostNavLinks: true +ShowWordCount: true +ShowRssButtonInSectionTermList: true +UseHugoToc: true +cover: + image: "birdnet-homeassistant.png" + alt: "BirdNET-Pi and HomeAssistant: Happier together!" + caption: "Bring together your BirdNET-Pi setup and display a dashboard in HomeAssistant" + relative: false # when using page bundles set this to true + hidden: true # only hide on current single page +--- + +This is Part One of a Two Part Series. You can find Part Two, [here.]({{}}) + +**Update: 10/11/2023. A huge thanks to Mastodon User [e_mobile2014](https://mastodon.social/@e_mobil2014) who found a broken link in this guide and pointed out that I never explained how to get the mqtt sensors into HomeAssistant!** + +## What you will need + +* [BirdNET-Pi](https://github.com/mcguirepr89/BirdNET-Pi) +* [HomeAssistant](https://www.home-assistant.io/) +* [AppDaemon](https://appdaemon.readthedocs.io/en/latest/) +* MQTT Broker (I use [Mosquitto](https://mosquitto.org/)) + +## Background + +In early 2023, at the height of the [Raspberry Pi +shortage](https://www.tomshardware.com/news/raspberry-pi-availability-analysis) 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](https://www.flightaware.com/), I figured this next project would deal with radio +waves/transmissions. Instead, to my amazement, I discovered [BirdNET-Pi](https://github.com/mcguirepr89/BirdNET-Pi)! + +## What is BirdNET-Pi? + +In case you didn't click the links above, [BirdNET-Pi](https://github.com/mcguirepr89/BirdNET-Pi) is an app built specifically +made for Rapsberry Pi devices, that builds off the [BirdNET Framework](https://github.com/kahst/BirdNET-Analyzer). 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. + +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 +[contribute a PR to the project](https://github.com/mcguirepr89/BirdNET-Pi/pull/821) in April when I noticed a bug in the +platform after a hard reset of my Pi. + +## BirdNET-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: + +* `$sciname`: Scientific Name +* `$comname`: Common Name +* `$confidence`: Confidence Score +* `$confidencepct`: Confidence Score as a percentage (eg. 0.91 => 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. + +Here is how I've setup my MQTT payload from BirdNET-Pi Settings: + +![Notification Settings](/posts/img/birdnet_mqtt_settings.png, "MQTT Settings in BirdNET-Pi") + +Here it is in text form: + +```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 +``` + +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. + +## AppDaemon 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. + +### Imports + +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. + +### Class 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. + +```python +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 + ) +``` + +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: + +* [MQTT AppDaemon API](https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html) +* [HomeAssistant AppDaemon API](https://appdaemon.readthedocs.io/en/latest/HASS_API_REFERENCE.html) +* [AppDaemon API](https://appdaemon.readthedocs.io/en/latest/AD_API_REFERENCE.html) + +These will indispensable to you as you leverage AppDaemon and expand this little script. + +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. `self.birdnet_mqtt = "birdnet"` is the definition +for the MQTT topic. Let's breakdown the last line of the class. + +Here's a breakdown of each of the items in that last line. You can find the official documentation [here](https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html#appdaemon.plugins.mqtt.mqttapi.Mqtt.listen_event). + +* `self.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`." + +### birdnet_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: + +```json +{ + "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. + +#### Part 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](https://appdaemon.readthedocs.io/en/latest/MQTT_API_REFERENCE.html) 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. + +#### Part 3: Wikipedia Sensor + +The next eight lines are a fairly straightforward [API call to Wikipedia](https://en.wikipedia.org/api/rest_v1/). 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&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={science_name}"` + +Once 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. + +All 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. + +```python + url = f"https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={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](https://www.flickr.com/services/api/misc.api_keys.html). + +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! + +The 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](https://www.flickr.com/services/api/flickr.photos.search.html) +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. + +{{< box info >}} +_Note: Full Transparency that I only learned about this after reading through BirdNET-Pi's code base. Full credit goes to +[mcguirepr89](https://github.com/mcguirepr89). For additional reference, here is Flickr's [official page on construction +photo image URLS](https://www.flickr.com/services/api/misc.urls.html)_ +{{< /box >}} + +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. + +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 `sensor.birdpic`, ensuring the `state=on`, and giving it the attributes of the `image_url` as garnered +from Flickr. + +```python + 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&api_key={flickr_api}&text={common_name} bird&sort=relevance&per_page=5&media=photos&format=json&nojsoncallback=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](https://www.home-assistant.io/integrations/mqtt/) 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](https://www.home-assistant.io/integrations/mqtt/#manual-configured-mqtt-items). + +To 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: + +```yaml +mqtt: + - { 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. + +**Full MQTT Sensors in Configuration.yml** + +```yaml +mqtt: + 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. + +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 _do not_ need to add the Wikipedia and Flickr sensors to +HA's configuration file! + +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. + +## Birdnet AppDaemon Script + +```python +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&action=query&prop=extracts&exintro&explaintext&redirects=1&titles={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&api_key={flickr_api}&text={common_name} bird&sort=relevance&per_page=5&media=photos&format=json&nojsoncallback=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}) + +``` + +