silveradept: A head shot of Firefox-ko, a kitsune representation of Mozilla's browser, with a stern, taking-no-crap look on her face. (Firefox-ko)
[personal profile] silveradept
Last we checked in with the Home Assistant, after some significant amounts of gluing together components with code and scripting, both in Home Assistant proper and on the single-board computer that was connected to it, the perfectly working television with the broken infrared receiver returned to the control of the Home Assistant. At about the same time, I added a radio to the Home Assistant setup that allowed it to gracefully take control of the first set of smart lights and bulbs bought for the house, since the hub that had been bought with the lights was failing to connect to the wider Internet at large, and therefore, the community integration I was using with Home Assistant to mimic the app and its controls wasn't being of any help with managing that light group.

Disconnecting the lights from the hub, the app, and the Application Programming Interface (API), however, meant losing out on some of the functions that had been programmed with them, and so this time, the projects are about trying to bring back that which was lost, through the clever use of APIs and the data they provide.

I started with the weather matters because I already had the appropriate integration set up to handle it. As a consequence of wanting weather information available to the voice assistant, I'd set up an account and API key with OpenWeatherMap and fed that into the integration to get an entire bevy of weather and climate sensors at my disposal, based on my home location. All I needed to create from there was an automation that would check for specific weather conditions and then turn the indicator light an appropriate color to provide weather warning.

Since there was only one light to control, I also had to ask about which warnings should have higher priority than others, so that the most important of the weather warnings would display every time one of the weather warning conditions was met.

Weather colors were selected for heat (magenta), cold (dark blue), humidity (green), the UV index (yellow), and snow (white). To achieve all of this, I needed to set conditions either about the state of the sensor (for when the weather said "snow") or the numeric state of the sensor (for when temperature, humidity, or the UV index crossed over certain thresholds). Since the last time, Home Assistant itself has also improved significantly, allowing for much more conditional logic possible in the visual editor, instead of having to drop directly into YAML. (Even though there's still a lot of necessary work being done in YAML.) Setting the priorities of each of the weather alerts was simple enough: arrange the possible actions in the order that I wanted them to fire so that the most important one would always stick while it was active. It works, as best as we have had conditions to check on so far, and in the correct priority of alerts. That was all doable in the visual editor, without the need for additional tricks or helpers, even though it probably seems complicated at the outset.

Having completed the weather alerts, I turned my attention to the next possibility before me, which was an aesthetic set of operations. First was a desire to have some of the lights turn on and provide illumination before sunset so that things didn't get too dark to see. Home Assistant provides a sun sensor and the ability to set an offset from events like sunrise and sunset, so that wasn't an issue, and with the grouping improvements that have happened since the last post, all the necessary lights could be addressed as a group to turn on, which made addiing voice commands to handle them as a group equally easy.

The second part was an aesthetic request to have a light turn on a warm color and slowly dim and redden over time. That was not easy to do at all in any of the clever ways I wanted to try doing it, so eventually I ended up brute-forcing it with hardcoded values of color and brightness in a very multi-step script. This would otherwise be okay, except that the ambient dimming light and the weather warning light are the same light, and the light remembers how bright it was when it was last turned on or off. So any weather warnings after the dim sequence would be very dim themselves, and we don't want that.

So I went back into the weather warning automation and added explicit brightness values to them so any overnight weather issues would bring things back to full brightness. That was easy enough. The less easy condition was bringing the lights back to a reasonable brightness after they had been dimmed and no weather warning had happened in the meantime. That could be added to the daily reset script, so that while we were all asleep the light would turn on, reset its own brightness, and then turn off again. The least easy condition was making sure the brightness setting on the reset didn't override the brightness set by a weather alert. Most of that difficulty was brought on by Home Assistant not allowing for full templating and logic to be implemented and recognized everywhere in the system.

I did eventually solve the last of those conditions by creating a toggle switch helper that would be turned on at the end of the dimmer script and off by any of the weather alerts firing. Setting an if-then on the brightness reset at the daily reset so that it would only fire if the switch was still on (no weather alerts) made the whole thing work smoothly. There are bright weather indicator lights usually awaiting us when we get up in the morning, because humidity.

Successful API usage handled by Home Assistant and a little bit of kludging and brute force makes these situations possible, but they're doable through the visual interface without needing to touch the underlying code side. (Victory fanfare goes here.)

After that part, I felt ready to tackle the other thing that losing the hub had taken away. There had been set up an If This, Then That (IFTTT) integration such that when someone posted to Twitter dot com, one of the lights would turn white, and when someone had mentioned their username in an @, the light would turn blue to indicate the unread mentions. Home Assistant does integrate with IFTTT, but it requires Home Assistant to have the ability to receive commands from the Internet. Since I specifically want as much as possible of my Home Assistant to be locally controlled, and the cloud access operations are a subscription fee, I didn't want to use the IFTTT integration. The Twitter integration is about posting, not receiving, so it wouldn't help, either.

Twitter, however, does have an API, and they have a tier where regular users can access data from Twitter through the API. Armed with an appropriate Bearer token for authentication, I needed to turn my attention to the documentation of the API to see what was possible. To my happiness, Twitter allows API access to both the tweets timeline and the mentions timeline through the API and therefore all I had to do was extract the correct data from the JSON object sent. Since Twitter's API claims to be RESTful, I could use the REST integration to access and parse the data for what I wanted.

This might not have been an easy idea to go through with from the outset, though, because the API returns full Tweets with associated metadata from a timeline request. Even though you can set a maximum cap, the minimum retrieval is still 5 Tweets. So whatever came back would require some parsing to make sure that I was only capturing the most recent Tweet and getting the right data out of it. Twitter's documents says they return requested Tweets in reverse chronological order, so I could reasonably make the assumption that the first Tweet was the only one I had to care about on either timeline, and then I just needed to make a proper comparison to determine which color light to use. And, to help with the parsing, they provide examples of what to expect in JSON Tweet objects, so I don't have to figure it out for myself and can go straight to the piece of data I want to set up the comparison. The most obvious way of getting to the most recent item for comparison would be to ingest the created_at field of the most recent Tweet and do a datetime comparison between the two of them to determine what happens.

I have done that kind of comparison before, in building the script to ask how much time is left on any running timers, and I don't particularly like having to parse those kinds of objects. It's a fair amount of work to get them into forms that can be compared appropriately. Once again, though, Twitter helps me out and makes the comparison less painful that it could be. Each Tweet is assigned a unique identification number, and barring something extremely weird, I'm pretty sure those numbers only ever go up, so rather than compare time objects, I can compare ID numbers of the most recent Tweet on each timeline to determine which color the light turns when the automation is triggered. Twitter agrees with me that this is the right way to go, since the reference response of what happens when using the endpoints to get the timelines is ID numbers and also contains a "meta" category. One of the items in the meta category is the ID number of the newest Tweet in the returned set. Woo-hoo!

That's the two numbers to compare settled, then, established as sensors with the values incorporated. Unfortunately, Home Assistant has very limited ways of comparing numbers when using the visual editor to try and build an automation. More often than not, if there are comparisons to be made, or logical operations to be performed, Home Assistant expects specific data types present to do those operations. For checking and comparing states, it expects strings, most of the time, unless it's specifically for a numeric state, at which point it expects floats. Numeric state doesn't work for me, because it's about crossing a specific threshold that the sensor is expected to fall below again. Constantly increasing numbers doesn't work there. I tried using a Trend sensor to catch the increasing numbers and then use the state of the Trend to turn the light on and off, but that really only works if there are breaks in the trend that would turn the sensor off and then on again.

Eventually, the Dirty Hacks Done Dirt Cheap mentality contributed a working solution. Since the comparison would only have two outcomes, (mentions or Tweets), I could assign them to the on or off state of an input toggle, such that I could then make an if-then comparison about whether or not the toggle was on to determine which color to turn the light to. I know, from previous experience, that choosing which service to call accepts logic in its template, so I added an action before the light color action to do the comparison between the two sensors' values and set the input toggle accordingly. Numbers become a toggle that can be tested and the lights change color based on whether there's a mention or a Tweet that's more recent. Boo-yah. It's awesome to be able to work with an outside API and get it to work correctly for purposes. (Victory Fanfare here.)

I also had to do work with an input number helper recently. We're getting into the warmer seasons, and there's a nice floor fan that's been assembled to help create some breeze in areas where the air conditioning can reach, but that could use some extra airflow all the same. The fan can be controlled by a remote, so we taught the universal remote nearest to the fan all of the remote commands for the fan, so that we could now control it through Home Assistant. And it turns out that this fan, desire being "dumb," is actually brilliantly designed for our purposes. This fan has four possible speed settings, and most crucially, the fan forgets whatever speed setting it was on when it is turned off. It defaults to a specific setting when turned on, and that's what makes the whole thing work. Because the fan always starts in a particular mode, I can have the fan start at a specific setting of my choice by using the ability of the remote control service to "push" a button as many times as I need it to, at a specified interval. (I can reuse that command set to turn it off, as well, as after the power button goes through to turn off the fan none of the speed control button pushes are acted upon.)

So I programmed the fan to turn on and then set it to the lowest speed setting by the request of the people who were going to be in its path the most. From there, I stored the current speed setting of the fan as an input number. So far, so good. Like many fans, though, this fan cycles through its speed settings in a set pattern, moving one space for one button push and then wrapping around again to the top. To be able to change the fan speed to an arbitrary setting from any other setting, the remote has to know how many times to push the change button. The fan doesn't know what it's set at in any way that can communicate directly with Home Assistant, and neither did the remote. So we use the input number to store the fan speed, and every time the fan speed gets changed by the voice command, the input number gets updated to reflect the new speed, and the change button gets pushed the correct number of times to set the fan on the new speed.

The number of button pushes is different based on the current input number and the new desired setting, so the script that handles all of that has a few different logic statements in it. Because of the way that the numeric state condition works, I had to set the condition up so that the input number was above one number below it and below one number above it to be sure that it would only fire the branching path that was specific to its current fan speed. And then, for each of them, after catching the desired destination speed from the voice assistant and slinging it over to the script, (now that I knew how to do it from all the work I did with timer scripts) I had to then do some conditional logic based on what the target speed was to feed the remote the correct number of button presses. Surprisingly, once I got all of the script syntax errors ironed out, it worked as I intended it to. Occasionally, the fan will cycle an extra time before settling on the correct setting, but it generally doesn't go wrong if it understands the number being sought.

The whole design is pretty fragile, though - if things get changed using the actual remote or the console on the fan, the input number will be wrong, and everything will be that far out of synchronization from there. Thankfully, turning it off and on again by voice will correct that, so it's not too terrible to get things back in sync if they should start going out of sync.

The next thing after that was working was figuring out if there's an easy way we can determine when our connection is having a hiccup and signal that, since it looks like the sensors that would be easiest to use to determine whether or not the Internet pipeline is actually flowing don't change their state when the cable modem is hiccuping and trying to reconnect itself. We've also determined that the router moves enough traffic over the network even when the cable modem is fritzing that we can't set a reasonable threshold for the sensors to drop under to trigger the indicator light for connectivity being out and back. There's a ping sensor that can be set to any website, but that's more Internet traffic going out and coming back on some sort of regular basis.

I had noticed that the outside API sensors lost their state (or rather, updated the state to a blank) if they tried to make a call and there wasn't a connection to be made. The visual editor for automations doesn't understand a blank state, so there wasn't a way of using the blank state as the potential trigger without dropping into the code interface again, and at that point, it would interfere with the way I had set up the script to determine the comparison by requiring me to wrap the entire script in an additional if-then around everything else. I didn't actually want to do that. At which point, Duty Hacks Done Dirt Cheap came back with another suggestion. The two sensors storing the latest Tweet IDs are derived from the two REST entities that I set up, and they don't simply blank when their API call fails, they change state to "unavailable." When the connection is restored, they change back to a different state. That I can work with, so I set those entities as the Internet canaries and assigned working and not working light colors to them in the indicator lights. I added a duration to the automation that's checking to see if the connection is back, just so that we're not giving ourselves a false hope if a blip returns before the Internet goes down again. This entire solution seems really inelegant to me, but I would rather have a working Dirty Hack than a nonworking hyper-elegant solution that I'm trying to make work.

There was one detail left to handle, and that was any time there was a state change, including when coming back from an outage, the "mention or Tweet?" comparison would be done and the indicator light lit for whether or not there was a mention or a Tweet that was most recent. We wanted to prioritize the signal of the return of the Internet instead, so I dropped into code on the mention or Tweet automation and used one of the optional fields to indicate I didn't want the automation triggered when the sensors were coming back from either an "unavailable" state of the blank state, which I represented as "", two double quotation marks worth nothing in between them. As best I can tell, one of those two representations is accurate for the blanked state, and so the comparisons shouldn't start getting made again until both sensors are back online and have had the opportunity to make a successful call. Having it all together seems to be working for now, although we'll have to keep an eye out and see whether the things that have been set up work accordingly and in the correct conditions, but this seems appropriate to say (Insert Victory Theme Here.)

I feel like some of my skills have leveled up again, or that having done things like this before means I have patterns to copy and adapt to the new styles. I still learn things every time I try to do something new, but it feels like these things are going faster from concept to a working execution, and that I'm learning the system and its limitations and workarounds every time I ask it to do something new. It probably means that I'm doing "real programming," even though it's in the guise of tinkering around with a toy that someone got me to play with. (Says the librarian who knows full well that "play" is a valid form of learning.)

So, that's the current adventure's progress. More code weirdness and hilarity later.
Depth: 1

Date: 2022-06-10 06:12 am (UTC)
alexseanchai: Katsuki Yuuri wearing a blue jacket and his glasses and holding a poodle, in front of the asexual pride flag with a rainbow heart inset. (Default)
From: [personal profile] alexseanchai
🎊

Profile

silveradept: A kodama with a trombone. The trombone is playing music, even though it is held in a rest position (Default)
Silver Adept

January 2026

S M T W T F S
     12 3
4 56 78 910
1112 1314 15 16 17
18 1920 2122 2324
25262728293031

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 25th, 2026 04:10 pm
Powered by Dreamwidth Studios