Skip to content

Blog

December 2021 Project Updates

Since my last blog post just over a month ago, I've been extremely productive. I have a few blog posts planned over the next week (no, really, I swear) sharing some updates on a number of projects I've been working on.

The 3D Printer I bought back in September has been keeping me very busy. Not just learning how to use it, but also constant tweaking and maintenance it needs. I bought a Creality Ender 3 v2 and as I like to joke about it, I have more time than money to spend on a 3D Printer. The Ender series is inexpensive, but it does take a lot of work to maintain it. I read once that you will learn as much about the hardware as you will about 3D printing if you buy an Ender series, and it's definitely true.

Learning CAD software has also been a time consuming process. Sure, there are thousands of pre-made plans you can print on your printer, but sometimes you need to customize something or build something just for you. In my case it's a new speaker stand for my AudioEngine P4s that integrates an Adafruit rp2040 Feather with Neopixel FeatherWing. Trying to figure out how to get the USB-C cable to work inside the stand was more complex than I thought it would be. As we speak, I'm printing - what I hope - is the final version.

The rp2040 Feather and NeoPixel FeatherWing is another project that I've spend considerable time on the last few months and more on that soon.

I also wrapped up version one of the Pi-Dial project last weekend. I made some tweaks to the code and it's working exactly as I want. If and when I get more time, I'm already thinking about version 2 which use Python's asyncio library. I could also pick up a Raspberry Pi Zero and make a version that uses that in a much smaller enclsoure. It may even be possible to embed that in a speaker stand as well, but the LCD would take up most of the front and I'd have to mount the rotary encoders on the side. I have enough projects though right now...

Last but not least, I've started working on Silver Saucer again. Looking at the blog, it's almost exactly a year since I spent significant time on it, which amused me. I just finished merging the change from Pyramid to FastAPI in and now I need to re-write the Discogs integration. I'll write more on that this week as well. I also have some cool things planned that will use an Adafruit MatrixPortal and LED matrices to display album art of what I'm listening to, but that's phase two.

More to come. Really!

Introducing pi-dial - Part 7 - Fixing the LCD

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

In my last post, I teased about 3D printing the enclosure. That will come in the next blog post, I promise.

First, I wanted to talk about an issue I ran into with how text was displayed on the 16x2 LCD screen. Using the RPi_GPIO_i2c_LCD library when I changed the input, if the input had less characters than the new input, the extra characters were staying on the screen.

For example, if I started on the Xbox One input, I would see:

Input: XBoxOne

If I changed to the Vinyl input, I would see:

Input: Vinylne

Since XboxOne had 7 characters and Vinyl only had 5, the extra 2 characters were staying on the screen. If I used the clear() method, it caused the line to blink. When I stopped to think about it, it makes sense, as the method clears the space and redraws it. Since I’m constantly polling the receiver via the API to check the volume and input, it would redraw the screen based on the sleep call I make in the while loop at the end of the pidial-lcd.py file.

I really don’t want it to blink. It’s distracting when sitting on my desk.

A quick search later I came across the RPLCD library. They also have their docs on ReadtheDocs and after quickly scanning that, I used it as a replacement. I had to change just a few lines of code and voila! It’s clear command doesn’t blink and it actually clears the LCD before writing to it again and the extra characters were gone.

I’ve said it before and I’ll say it again: I love open source.

And make sure you thank an open source maintainer today.

Next up: 3D printing and assembly

pi-dial - Part 6 - When is a list not a list?

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

In addition to controlling the volume of my receiver’s Zone 2, I want to be able to use a second rotary encoder to change the input. For example, I may want to change it from the Phono input that I use to listen to records to the Tuner input to listen to the radio.

The Denon AVR-3806 that I own has twelve total inputs, of which I’ve customized a few of the names. If I print out the list using the denonavr library, I see:

All inputs: ['8K', 'AUX', 'AppleTV', 'Bluetooth', 'HEOS Music', 'Phono', 'Retropie', 'SOURCE', 'TV Audio', 'Tuner', 'UBP-X700', 'Vinyl', 'Xbox One']

I’m using Python, so iterating through the list and / or slicing it to get to a specific position in the list shouldn’t be hard. Shouldn’t being the key word.

Spoiler: It was anything but.

If I attempted to iterate through the list, whenever the list position was equal to 8K, Bluetooth, or Source, the list would stop. It didn’t matter if I tried to move up or down the list. Now shame on me for not blogging about this earlier as it’s been almost two months since I worked on this particular piece, because I think there was another position in the list that did something similar. But of course I don’t have great notes or even a lot of commits to go back and look through.

I’m still very much a novice at coding, so I did what most newbies do. I wrote multiple if / else statements to try and figure out how to get around this limitation. I was 100% sure it was my code. Nothing I did worked, even as I wrote very procedural code to step through the list. I was on vacation the week I worked on this and spent literally the entire week trying to get this to work.

I finally broke down and asked my wife for help. In about five minutes she ripped up my if / else statements and wrote a methodthat passed a variable on whether the rotary encoder was turning up or down. And then she ran into the problem where the list would get stuck on positions 0, 3, and 7 (8k, Bluetooth, and Source) as you can see in the code’s comments. A couple hours later she was able to work around it. This was a fun way to spend a Friday night together!

Neither of us have any idea why those inputs caused the issue, but she was able to write a method to work around it.

The good news is that it wasn’t necessarily my code, the bad news is that I still have so much to learn about the best way to write my code.

The better news? The code is done! I have one program that monitors the current input and volume and displays it on the LCD and I have another program that can change the inputs and the volume.

Next up: 3D printing the enclosure.

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.