Skip to content

2021

Introducing pi-dial - Part 5 - A detour into async

(Catch up on the pi-dial series of blog posts.)

With the hardware prototyping done, it’s time to write the code that is at the hart of pi-dial.

I need the hardware to do four things, all in Zone 2 of the receiver: 1. Change the inputs (CD to Phono to Tuner, etc.) 2. Change the volume up or down 3. Mute and un-mute the receiver

I mentioned briefly in my first post that the denonavr open source project has the methods I’ll need for this. It is possible to directly interact with the receiver as it has all of the functionality built using custom HTTP calls. But it’s definitely not optimal, leading to projects like denonavr.

Late last year, the denonavr project was re-written to use Python’s async library. I know of async, but I don’t know how to use async.

I found the documentation for using async with denonavr kind of sparse. I opened an issue in the project’s Github repo and received some pointers to get started.

After that, it was time to dive into async itself. I purchased the Everything Bundle from Talk Python Training earlier this year which included the Async Techniques and Examples course.

I kind of understood it? But not enough that I am going to use it (right now). I’m still very much a novice at coding in Python and I’m just trying to build something between a prototype and minimum viable product. This was not a rabbit hole I wanted to go down right now, but I might in the future.

I did take some time to update the project’s documentation with more async examples to make it easier for the next person. Those changes were merged in a couple weeks ago, which is nice.

Thankfully, the denonavr project still includes legacy support, which was pretty easy to get up and running, with the exception of one thing, which I’ll cover next time.

A brief aside: I feel bad for users who ask a question and don’t get a response in open source projects. Even worse, in my opinion, is adding a bot that will automatically close those questions or issues after a period of time without a response. Having been involved with a couple of open source projects, I understand how frustrating it can be for users to ask the same questions over and over or have a repository filled with “issues” that really aren’t issues. But then write some documentation to help those users! Don’t ignore them and then automatically close the issue.

Next up: Finally writing the Python code to power pi-dial.

Progress Update

It’s been a little bit since I posted an update about the pi-dial project. Don’t worry, I have some updates to share soon.

In typical fashion, I’ve been distracted by two new projects. That, and I’ve been putting off blogging. I probably need to do a better job keeping notes of the updates I want to write about because I’ve been doing it all from memory. And the longer I go between working on the project and writing about it, the less I remember.

The new projects? I have a CircuitPython project to share soon and I bought a 3D printer last Wednesday. The printer has taken up most of my time in the last few days and I’ll share more about that, too.

Ender 3 v2 3D Printer

Introducing pi-dial - Part Four - Adding an LCD

Previously: Part 1 Part 2 Part 3

While it may sound like I’ve been on a tear creating the pi-dial, the truth is somewhere in-between. While I’ve done a lot in the last month, there have been enough times where I’ve wandered down a rabbit hole doing research and getting distracted.

One of those times was thinking about how I was going to mount this contraption. One idea I had was to build a custom wooden box / case to mount the Pi in. The rotary encoder is panel mountable, so I thought I could drill a hole in the wood. I don’t have any wood working skills to really speak of, so I looked at this as another learning opportunity. (Like I need another learning project!)

I spent a lot of time on the MakerBot Thingiverse website browsing plans for 3D printers. I found a few plans that include a case for the Raspberry Pi and a hole the rotary encoder, but the plans were almost ten years old and for the original Raspberry Pi version 1 which hasn’t been around forever and all I have is a bunch of Raspberry Pi version 2s.

But then I found this plan that reminds me of an old car stereo. It would sit nicely on my desk:

Raspberry Pi Brackets and Enclosure

If you click through, I’m not sure I’ll use the power step down converter for the back plate, but it might be a nice project as well. That’s a problem for future Paul to deal with. I sent the plans to my friend with the 3D Printer and I’ll have to ask him if it’s possible to make the knob on the right equal to the one on the left, which is the rotary encoder (so I can use two of them).

Now I need to LCD to match the design. Another few hours of reading and research and this doesn’t look as hard as I thought it might be.

I looked at the Adafruit Pi Plate, which includes a 16x 2 screen (16 characters per row, 2 rows). It also has five buttons, and I don’t need that many. I did find some interesting 3D printer cases for it, but I’d rather use a rotary encoder to control the volume than buttons.

Looking at some of the tutorials on-line, I went with a cheap two-pack from Amazon. If you look at the photo below, you can see the LCD has 16 pins across above the LCD, but it also has an I2C interface on the left side where you can see the 4 pins coming out of it.

Working 16x2 LCD on a Raspberry Pi

The great thing about it using I2C - inter-integrated circuit - is that it makes using peripherals like this LCD a snap. Out of the 40 pins on a Raspberry Pi, only 4 are I2C, so you have to plan ahead on how you’re going to connect things to the Pi when using multiple peripherals, like 2 rotary encoders and an LCD. The rotary encoders were easy to hook up just using the jumper cables, so I didn’t have a concern.

My LCD came with the default address, 0x27, and I had it working within an hour using the RPi_GPIO_i2c_LCD library.

In less than 50 lines of Python, I was able to create a while loop using the denonavr library. Every 5 seconds in my prototype code it polls the receiver asking it for the current volume and current input and displays on the screen above.

Voila! This was probably the easiest part of the project so far.

Next up: A detour with Python’s async

Introducing pi-dial - Part Three - Prototyping

Previously: Part 1, Part 2

At the end of my second blog post, I had decided to switch from using the CircuitPython Media Dial to using a Raspberry Pi instead. The media dial is much smaller (and cuter!) than a full blown Raspberry Pi, but I need to start somewhere and writing one Python application for the Pi seems easier (for the moment) than two different programs.

It was time to start prototyping and write some code! There are a ton of guides on the internet for programming a Raspberry Pi with a rotary encoder. Unfortunately, a lot of them are older from when the Pi first came out almost ten years ago. The Python programs I found were either legacy Python 2 code or used old Raspberry Pi libraries in Python when I wanted to use the gpiozero library.

I hooked up T-Cobbler breakout board and cable from the Pi to the breadboard. This makes it simple to swap jumper cables to make sure it’s wired correctly.

Breadboard connected to a Raspberry Pi and Cobbler breakout board

I couldn’t quite get it at first. I ran one of the Python 2 programs I found and it was working. So I knew I had wired it up correctly and it was time to read the gpiozero docs some more.

Within three or four hours I had it all working. I wish I had kept better notes for these blog posts to share some of the broken code versus where I ended up, but the end result is what I wanted. I created a handful of different Python programs for testing all the different functions in pi-dial repo that I'll eventually need to clean up. (Don't look, the code is terrible, needs to be re-formatted, etc. etc.) If I moved the encoder clockwise, the volume goes up. Counter-clockwise, the volume goes down and if I press the button it either mutes or unmutes depending on its status.

I couldn't get this tweet to embed correctly, but if you click through, you can see and hear a 12 second video of me muting and unmuting my receiver over Zone 2 using the Raspberry Pi over the network.

Next up: I can’t keep it simple

Introducing pi-dial - Part Two - Oops

Previously: Introducing pi-dial Part 1

During this year’s PyCon, I completed two exercises in the Microsoft booth virtually. A week later I was rewarded with two $50 gift certificates to Adafruit! (Thanks Microsoft!)

Using the gift certificates, I bought a soldering iron and the parts I needed to make the CircuitPython Media Dial. But I’m impatient, and while I was waiting for those to ship I thought I’d try and build a prototype in CircuitPython using a Circuit Playground Express (aka CPX) I already had.

I ran into a couple errors trying to get one of the libraries in the CircuitPython Bundle to load on the CPX. I’ve already been lurking in the Adafruit Discord channel which is a very friend community and I decided to ask for help. After fixing my library issue and talking about my goals for the project, a kind person pointed out a problem that I hadn’t thought of:

There is no network connection on the Circuit Playground Express or the Trinket I purchased that powers the Media Dial.

Oops.

I take network connectivity via wireless or wired for granted on all my devices. It just never occurred to me that the microcontrollers don't have network access unless you physically add it.

I started to think about how I could get around it and researched how I could fix this. Two ideas jumped out at me:

  1. Add a wifi co-processor, using a second circuit board that is soldered to the Trinket M0 microcontroller that I already purchased for the project.
  2. The Media Dial project as created sees the dial as a USB device and the dial rotations as a keypress. I could write a Python application that runs on the Mac I’ll plug the dial into to listen for a certain keypress from the Trinket M0 and then send the commands to the receiver from this new Python program.

I quickly abandoned #1. I have no idea how to modify or create designs for a 3D printer and all the wifi co-processors were much bigger than the Trinket M0. I’m not going to figure out how to cram those together.

The second option was intriguing. On the plus side, by running a Python program on macOS, I get access to the denonavr library making interacting with my receiver a breeze. But the more I thought about it, the more I questioned the solution. The way it would work:

  • The Media Dial is seen on macOS as a USB device. This shouldn’t be too hard, the code is available in the guide on Adafruit. (“This shouldn’t be too hard” - famous last words.)
  • If each turn of the rotary encoder is a key press (that can be programmed using CircuitPython in the code linked above), my Python application listens for that keypress and then sends an API call to my receiver.
    • When turning the rotary encoder, is each step a keypress?
    • How quickly does each step turn and does it turn the volume up or down too quickly?
    • How much lag is there between turning the rotary encoder, the CircuitPython program interpreting that, and then sending it to the Python program on my Mac, which sends the API command over the network to the receiver.

I looked into which keys presses are reserved in macOS for shortcuts and programs, looking for a keypress that doesn’t conflict with anything else. I only need 3: volume up, volume down, and a button that turns mute on or off depending on its state.

The other concern I thought of was having to start the Python application every time I reboot my MacBook. Coding two different apps seems like a lot of work and I started to think about it differently. What if I used a Raspberry Pi instead? Yes, it’s a lot bigger and bulkier, but I only have to write one application and I can wire the buttons directly to the GPIO buttons on the Pi. The tradeoff of a bigger physical device instead of a dial seems worth it compared to the above. Besides, this is more of a learning exercise. I'll consider this a prototype to see how I do in adding a rotary encoder to a Raspberry Pi.

So that’s what I’m doing. More on that in the next blog post.

Introducing pi-dial - Part One

The backstory and overview

I’ve started work on yet another project: pi-dial

I’ve been looking at the Media Dial project on Adafruit for a while as a method to control the volume on my home theater receiver. I’m lucky that my home theater shares a wall with my home office, which made it easy to wire an extra set of speakers to use with Zone 2. This allows me to listen to my record collection while working in my office for my day job or when I’m at my workbench. My record player is still only 10 feet away, making it convenient to flip a record or put a new one on the turntable.

The dial runs CircuitPython, and would connect to my receiver using the denon-avr Python library or the HTTP POST commands that the receiver also excepts over the network. The dial would let me change the volume either up or or down and clicking the button would mute or unmute Zone 2. I’m lucky that Denon has a pretty well documented API using these HTTP commands and even better that Oliver aka scarface-4711 wrote the denonavr Python library to easily use Python commands to control the receiver and licensed it liberally with the MIT license.

The project became possible when my boss bought a 3D printer. When I first came across this project last year, I briefly looked at some co-op maker spaces and even a commercial company to see if I could get things printed, but during the pandemic lockdown it wasn’t going to work.

I’m doing this project for two reasons:

  1. I want to continue learning Python, especially with hardware like CircuitPython or the Raspberry Pi. This is very different than the web development I’ve done with Python so far.
  2. I like the idea of something physical to change the volume or mute. My partner walks into my office? Hit the button and mute it. Records have a different volume range? Move the dial!

I’m also excited to learn a whole new set of skills - breadboards, electricity, soldering, and more.

It also turns out I can already do this in Home Assistant, the Python-based open source home automation software, that is fantastic.

denonavr Python library configured in Home Assistant

I have a Firefox tab pinned for Home Assistant and the only reason is to be able to control my receiver. From here I can mute, change the volume or change the inputs. All using the denonavr Python library. I have a few other devices configured in Home Assistant, but I haven’t spent the time to set up a Lovelace UI or other automations. My house isn’t very “smart” and I definitely don’t have any voice assistants. I may make some of my own sensors some day, when I’m better at soldering and CircuitPython. But I digress - one step at a time.

But then I ran into a problem. CircuitPython has a limited subset of 3rd party libraries maintained by Adafruit. There is no denonavr module available, which abstracts away all of the HTTP POST commands to control the receiver over the network. But that’s ok - I can use the requests library that is in CircuitPython to do this. It will be more work, but it will help me learn that library better.

In the next blog post, I’ll share some of the challenges I ran into when I was ready to start prototyping.

Retiring My First Python Projects

It’s a sad weekend in my workshop. I’ve officially retired and archived my first two Python projects, NFLPool and MLBPool2.

I vividly remember Thanksgiving weekend a few years back when I decided it was time lo learn to code. I bought a few O’Reilly books on a Black Friday special and quickly learned that I can’t learn from books. It would be another year and a half before I buckled down and really learned Python.

I’m of the opinion that if you’re going to be community taught (I don’t believe you’re really self taught - you’re learning from the community of users and teachers, whether that’s in a formal course you’re taking online or asking questions on Reddit or Stack Overflow) you have to have an end goal in mind. It’s one of the most common questions I see on Reddit: “I’ve learned the basics, but I don’t know what to do now?” Most people need an itch to scratch, a problem to solve to really apply what they’ve learned.

NFLPool was that for me. We had spun NFLPool off of MLBPool2 and I was the commissioner. I was doing it all in a spreadsheet manually, each Tuesday having to look up the team and player stats and I knew there had to be a different way. And there was.

MLBPool2 was based on the NFLPool code, which made it easier to get up and running later. But neither project would have existed if it hadn’t been for Talk Python’s Python for Entrepreneurs course. The skeleton of both sites came from that course and modifying that code base taught me so much about Python and Pyramid, the Python web framework that powers both sites.

I didn’t know if I would stick with it, but here I am four or five years later, still learning and branching out in how I use Python. I’m still working on another web site, building it twice with two different frameworks (I really want to learn FastAPI) and also learning how to use hardware with Python. (More on my experiments with the Raspberry Pi, rotary encoders, soldering and more will be recapped in another post).

NFLPool never really had enough players to survive. MLBPool2 did, but in the second year of it being online, the commissioner changed some of the rules and how points were scored. Updating the code introduced a bug in how ERA is calculated and I was never able to find the bug.

I thought I had turned off registration in both applications, but script kiddies are still registering themselves with NFLPool. Before they find a flaw with SQL injection or something else stupid, I’ve replaced both sites with a landing page that redirects to the project pages here. If it wasn’t for them, I would have left the site and scores up to show what had been built. I've officially put both repositories in Archive mode on Github.

In the end, they both served their purposes in giving me motivation to learn how to code and for that I’ll always be grateful.

My First PyCon

I'm attending my first PyCon today. I'm pretty excited. I know some argue whether virtual vs. physical is good, but as somebody new to the community, it's almost easier to come lurk. But if I knew more people, there's no question that the hallway track in a physical conference is always fun.

Just a few of the talks I'm looking forward to:

  • "Learning python during lockdown: a surprising bonding experience with my child"
  • "More Fun With Hardware and CircuitPython - IoT, Wearables, and more!"
  • "Diversity & Inclusion Workgroup Panel"
  • "Introduction to Pydantic"

And more! This will be a fun weekend.

Richard Stallman Must Go

Back when my blog ran on Wordpress and I had analytics and comments enabled, one of the most viewed blog posts was one from almost ten years ago, where I agreed with another blogger that the FSF should be forked.

With Richard Stallman re-joining the FSF this week and the valid outrage that has followed, I remembered the blog post but not the content. Larry Cafiero's original blog is no longer on the internet, but I found his original blog post and follow-up on the Internet Archive's Wayback Machine.

It just amazes me that after all these years, we're still arguing about someone as toxic as Richard Stallman being around. Ever since I saw his keynote at GUADEC and his awful, awful talk, I've wanted nothing to do with him. I disagreed with he how he spoke to GNOME Foundation members running for the board, his views on women and the disabled, and I strongly disagree with his views on trans people. He needs to be held accountable for his views and the things he has said, and the Free Software Foundation's board needs to be held accountable for allowing him to be re-appointed to their board.

I'm proud to add my name to this open letter from major organizations and over 1,000 people calling for Richard Stallman to be removed from all leadership positions, and the Board of the FSF to step down.

Richard Stallman must go. Again.

Silver Saucer Update - March 2021

Following up on my last blog post, I did deploy Silver Saucer a couple days later. I spent a day trying to track down why MLBPool2 kept loading when I visited silversaucer.com and it turns out that the nginx web server defaults to the application with the lowest port number. I had made a mistake in the nginx configuration in one little place that took me forever to figure out. And with that, Silver Saucer is live on the web.

It still has a nasty viewport problem and the screen height is not 100% and it’s driving me crazy. Maybe if I had finished my Coursera class on HTML, CSS and Javascript I would have figured it out…

A bad habit that I have is when I get stuck, I move on to a different project. I’m hoping that my subconscious will fix it, but it’s more out of frustration. So what did I do? I bought more training courses from Talk Python and decided to learn the FastAPI web framework instead. I was surprised to see such a new project in third place in the recent PSF survey, especially as it was the first time being included as a framework.

I decided to build my site along with the training, rather than the PyPI clone the training has you do. After sorting through a couple of different bugs, guess what? I still need to learn CSS.

I’ve switched courses, moving from Coursera to Pluralsight, and am taking a new class to learn HTML & CSS. I’ll get there one of these days.