Is this Mahler? This sounds like Mahler

One of the most delightful parts of being a software engineer and hardware tinkerer is having the ability to solve my own specific (and often niche) problems. In this post, the particular problem I’ll be solving is the burning need to know what is currently playing on WQXR, NYC’s classical radio station.

A typical Saturday for me looks like this: I wake up and make a coffee, bring it to the couch in the living room, pick up my knitting, and turn on the radio. Maybe midway through the morning a halfway-familiar piece comes on, but I’ve left my phone in the other room and won’t be able to go check the WQXR website without disentangling myself from my knitting and missing a bit of the piece that has captured my attention.

Or how about a weekday: I’m working on a project, elbows deep in the code, with the WQXR livestream open in one of my million browser tabs. I feel a glimmer of recognition, but I’m fully focused on my work and don’t want to break my concentration to go hunt down the livestream player and find out what it is.

To solve this particular problem, I needed two things: a way to find out what was on the radio, and a place to display it.

There are a couple of different ways you could find out what’s playing, including music identification services like Shazam. But those rely on already having a recording in their database to match against, and I’m listening to classical music where I could be hearing any one of dozens of different recordings (and even live performances) for any given piece, making it unlikely that such a service would be able to find a match. So I started with the source I was already using to get this information: the WQXR website.

If you leave the website open long enough, you’ll notice that it automatically updates to reflect what’s currently playing. This was great news for me, because it meant that somewhere in the page a script is periodically making ajax requests to get that information, requests that I could also make myself. To find out what these requests were, I eavesdropped on my browser’s network calls using the developer console.

There are two calls being made here, one to an endpoint called streams, and one to something very promisingly named whats_on. Making a request to that second endpoint, we get a beautiful json response containing information about what’s playing on New York Public Radio’s various livestreams. Great! This is exactly what I need to satisfy the first piece of this project.

(I honestly can’t remember how I figured this out, but you can append the call letters for the particular station you’re interested in to the URI to just get information for that stream, but the full response would also have worked just fine.)

Okay, cool, so I have the data. How do I get this information in front of my eyeballs when I’m listening to the radio?

You already know from the teaser photo at the top of the post that I ultimately put it on my mantel, but I took an iterative approach to getting there.

I use tmux to manage my terminal sessions, and it occurred to me that the status bar, always there along the bottom of my terminal, might be a nice place to have information about what’s currently playing. I wrote some lightweight python classes for fetching and parsing the radio’s API response, and a tiny script, tmux.py that outputs the information in the format I want for this purpose.

Adding something to the tmux status bar is just a matter of adding a couple of lines to .tmux.conf:

set -g status-right-length 200
set -g status-right '#[bg=#d7ff5f] #(python3
/home/sarabee/development/nowplaying/tmux.py)  |  [%H:%M] '

This overwrote the clock that was there by default, so I added one back in. By default, scripts run in .tmux.conf are executed once every 15 seconds, which was more than fast enough for my purpose. My status bar then looks like this:

This fixed the problem of not wanting to leave my terminal to find out what I’m listening to, but what about when I’m in the living room? I knew I wanted a display, that I wanted it to update automatically, like the tmux status bar, that I wanted it to be constantly running, like an indoor thermometer readout, and that I wanted it to be readable from any position in the room, with good view angles and readability under a variety of lighting conditions. This all sounded to me like the perfect excuse to work with e-paper.

Pimoroni was having a sale, so I picked up two e-ink displays: the smaller inkypHAT, and the larger inkywHAT. I also got a couple of Raspberry Pi Zero Ws to pop them on top of (quick aside: these things cost $10 and have wifi and bluetooth, wowww).

Starting with the inkypHAT and the code I’d already written for handling radio data, I prototyped my idea, creating a tiny display that sits on top of my monitor. I set the Pi Zeroes up the way I set up my larger Raspberry Pi 3, with Raspbian Lite. Without worrying too much about styling the display aesthetically, I learned how to work with the Inky and Pillow libraries and wrote a script to get the composer and title chopped up to fit across multiple lines on the screen:

I have it running once a minute on a cron, and to avoid refreshing the e-ink display unnecessarily, I keep the last piece written to the display in memory and update only if I’ve gotten something new back from WQXR.

Reworking this script for the larger inkywHAT display wasn’t difficult; it mostly involved tweaking font size to take advantage of the larger screen. But since this is meant to live on my mantel and be highly visible in my living room, I wanted to make it look a bit nicer than just throwing the text on there. I found a clip-art scrollwork frame, and using only the MacOS Preview app and ImageMagick, got it into the right size and format for the e-ink display.

In Preview, I grabbed a corner of the frame and pasted it in again three more times, rotated 90 degrees each time, and carefully bumped each corner around until they were lined up and could be scaled down reasonably to the right dimensions (400x300px). Even though the image appeared to be entirely black and white, a closer look shows this wasn’t true at all! It’s actually full of many different shades of gray:

To get the image into the right format and flatten it down to just two colors, I used the ImageMagick command-line tools. While this extremely thorough page in the IM docs goes pretty far in-depth with the various ways you can convert an image to black and white, I ultimately ended up going with:

magick input.png -colorspace gray -colors 2 -normalize PNG8:output.png

Which produces a frame that looks like this:

It’s a little rough when you see it on a relatively high resolution monitor, but looks great on the 400x300 e-ink display! After that, adding the text was pretty straightforward; earlier when only displaying text, I was still actually using Pillow to create an empty image in the correct dimensions that I drew the text onto:

img = Image.new("P", (inky_display.WIDTH, inky_display.HEIGHT))

To use the frame, I simply started with the frame image instead:

img = Image.open(os.path.join(current_dir,
"whatnow.png")).resize(inky_display.resolution)

The last little bit of clean-up work involved getting the text centered in the frame, and setting the margins in my script so that the line breaks were a comfortable distance from its edges.

I’m happy with where this project is at; the text is clear and legible from anywhere in my living room, and my Saturday morning listening experience has been greatly improved. Eventually, it’ll get custom wooden housing, which will be its own post, I’m sure! Feel free to dig around in the project’s repo on GitHub to get an even better idea of how this all works.

Plant Tweets, Part 2

This post is a follow-up to the one I wrote last month on how I got my plant to tweet. That post covered the hardware involved in getting my plant’s current thoughts as far as Linux running on my Raspberry Pi. Today I’ll show you how I used Python to turn the output into something I could use in code. Again, my goal is for this to be easy to follow even if you’ve never built a project like this before, so please @ me (@SaraBee) if you have questions.

Last post, we had the Arduino hooked up to my plant’s sensor plugged into my Raspberry Pi via USB. First thing’s first: we need to find the Arduino here in Linux land. Conveniently, Arduino has a set of command line tools called arduino-cli. We can look for any boards connected to the Raspberry Pi using the command arduino-cli board list:

Cool, it’s in there, and now we know which port it’s hooked up to! Next, let’s listen in on what’s being sent from the Arduino. Because in Linux our serial ports are represented by files, we can watch the latest data stream to the command line using tail -f /dev/ttyACM0:


You may notice that this output contains not just moisture levels but also temperature and humidity; this is because I added a sensor since my last post.

Okay, great! Monstera, you’re coming through loud and clear. Now, how do we gain access to this data in code? I used this project to get more comfortable writing Python, and there’s a very straightforward Python library called PySerial that does exactly what we’re looking for. To grab some bytes off of our serial port buffer and print to the command line, our Python looks like this:

import serial

ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
sample = ser.read(200) # pull 200 bytes off serial

print(sample)

Our output is a bytes object that looks like:

b'\r\nmoisture:345\r\nhumidity:63.1\r\ntemp:21.1\r\np:21.1\r\nmoisture:345\r\nhumidity:60.9\r\ntemp:21.1\r\nmoisture:345\r\nhumidity:61.9\r\ntemp:21.1\r\nmoisture:345\r\nhumidity:63.1\r\ntemp:21.1\r\np:21.1\r\nmoisture:345\r\nhumidity'

Cool cool. If you’re like me, you might be more comfortable manipulating strings than byte objects, so let’s decode it using utf-8 and turn it into a list of readings:

import serial
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
sample = ser.read(200) # pull 200 bytes off serial

sample_list = sample.decode('utf-8').split('\r\n')

Rad. Now we’ve got a list of individual readings that each look something like temp:21.1 or moisture:345. However, because we’re reading bytes off a buffer, we might also sometimes get an incomplete reading that looks like moisture:3 or emp:21.1. In my actual implementation, I only split on newline (\n) and used the carriage return character (\r) to indicate that we were looking at the complete value for a reading before stripping it off. I could then split each reading again on the colon and place the pieces into keys and values in a dictionary:

import serial

ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
sample = ser.read(200) # pull 200 bytes off serial

sample_list = sample.decode('utf-8').split('\n')

readings = {
        "temp": [],
        "humidity": [],
        "moisture": []
}

for reading in sample_list:
        if '\r' not in reading:
                break
        reading = reading.strip()
        reading_kv = reading.split(':')

        if len(reading) == 2:
                key = reading[0]
                val = reading[1]

                if key == "moisture":
                        readings["moisture".append(int(val))
                elif key in ["humidity", "temp"]:
                        readings[key].append(float(val))

In my actual implementation I do a little bit more to ensure I succesfully was able to cast the values to ints or floats, but that’s basically it. I then have a dictionary where my keys are the different types of readings, and the values are lists of readings for each type.

Quick aside: you may notice that if you run a script like the above multiple times in succession that subsequent runs may not receive a full sample (or maybe nothing at all). When we call ser.read(200) we are asking the kernel for 200 bytes; if the buffer has all 200 bytes, we’re all set, and then buffer then gets flushed. The next time we call ser.read(200), maybe there isn’t anything much in the buffer, so we get either 200 bytes or as much as can be scraped together before the timeout we set when we instantiate Serial the line above that. This behavior will all depend on how frequently you’re writing out data on the Arduino side, your timeout, and how frequently you’re reading on the RPi side. I am super curious about how reading from the buffer clears it out, and while I haven’t yet found resources on exactly this topic I did find the Linux Serial HOWTO doc to be an interesting read.

At this point, we’ve successfully liberated our readings from the bytes streaming in over serial and turned them into something we can use in our code however we’d like. This feels like a good stopping point, but if you’re interested in the rest of the project and want to hear about how it uses thought catalogs to randomize the content of its tweets, how I wrote my Twitter API client, or how I used argparse and a couple of crons to tweet about moisture and humidity on different cadences, please let me know!

Plant Tweets, Part 1

This past week my monstera deliciosa, a lovely tropical houseplant, started to tweet. This is the first of two posts on this here skeleton of a blog showing how I helped it get its voice out into the internet. First, we’ll take a look at the hardware and code needed to read my plant’s mind; Part 2 will look at how those thoughts are broadcast to the world. These posts should make sense even if you’ve never played with hardware before, so please @ me (@SaraBee) if you have questions!

A month ago, I was shopping for a cheap soil moisture meter to help me care for my growing army of plants. In my search results were hits for the simple mechanical sensors I was looking for, but also inexpensive hardware sensors for use in projects with, for example, Arduinos. Of course, my first thought was: “My plant needs to connect to the internet.”

In researching which sensors to buy, I saw that there were two types available: resistive and capacitive. Resistive sensors work by measuring the conductivity of the soil in which it is placed by running electricity through it, and if you remember back to high school science class you may remember something something electrolysis something something ionization (that’s about where I’m at) – basically the electricty creates a chemical reaction with the water and metal that removes copper from the sensor over time, eventually causing it to stop working. Capacitive sensors, on the other hand, do not have exposed electrodes, and so should not fail due to electrolysis over time. I really enjoyed this video, which gets into how each sensor type works and demonstrates this particular failure mode of resistive sensors. Needless to say, I went capacitive, since I was planning to leave my sensor in-place over long periods of time.

The sensor on its own wouldn’t really get me anywhere, all it does is output an analog value (like 273, or 418, or 550) for something else to read. I had already set up a Raspberry Pi as an always-on linux server that I use as my dev box, so initially I was interested in connecting the sensor straight to the rpi’s GPIO pins (general-purpose input/output pins, for use with sensors and other stuff). One small problem: rpi’s GPIO pins only work with digital input and output, not analog like my sensor’s output. If I plugged the sensor directly into my rpi, it wouldn’t be able to make sense of the signal.

My first thought was to use a small, inexpensive component in between the two called an ADC - analog-to-digital converter - which would convert the sensor’s signal into something that my rpi could use. I bought two of these when I bought my sensor, and got as far as soldering header pins on them so that I could seat them on a breadboard before realising that they were both duds. Bummer!

All was not lost, however. In my bin of hardware toys was an Arduino Uno, which was a good fit for this project for a number of reasons. First, Arduinos do have analog connections built right in, meaning I wouldn’t need any extra components, I could plug the sensor straight into the Arduino. Second, while the Arduino Uno doesn’t have a way to connect to the internet itself, it does have a USB port (more on this later).

Arduinos run small snippets of code called Sketches, which are flashed into their onboard memory by the tinkerer (that’s me) and can run continuously as long as the board is receiving power. I plugged my sensor into my Arduino’s first analog pin (number 0), and so the sketch to read its output once every 100ms and write it out to the serial port looks like this:

void setup() {
        Serial.begin(9600); // open serial port, set the baud rate as 9600 bps
}
void loop() {
        int val;
        val = analogRead(0); //connect sensor to Analog 0
        Serial.println(val); //print the value to serial port
        delay(100);
}

Something that might not make sense yet is this serial business. What’s a serial port, and why is the sketch writing to it? Serial communciation is a simple way for two computers (or components, or circuits) to talk to each other, one bit at a time. The baud rate is how many bits are sent per second, and both sides need to agree so they each know how to receive bits from the other. I found this article over on SparkFun to be a really great deep-dive on how all of this works. In the case of my Arduino, the serial input and output can be done via pins (like how the sensor is hooked up) or, handily, over USB (which, remember, is short for universal serial bus).

Plugging the Arduino into my Raspberry Pi via USB gets the signal from my plant into Linux land, which opens up all kinds of possibilities for using the data in code. In Part 2, I’ll talk through how I explored just one of those possibilities, hooking this mess up to Twitter.