(LIFE) HACK

Everyday Computing

Solving life’s minor nuisances with code

Patrik Fredriksson

--

In this article we will address one of life’s insignificant annoyances by writing a quick command-line tool for controlling Denon AVR home cinema receivers using Java, Picocli, GraalVM, and Alfred.

Photo by Andrew Neel on Unsplash

The Problem

Most mornings, as I sit down at my desk, I find myself wanting to put on some music. With the Sonos app on my Mac, that should be easy enough. But, I also need to power on the receiver for any sound to actually be heard. So I have to get up, walk over to the receiver and turn it on and, as I get back to my desk, I realize I need to go back up and also change the input source. The receiver typically was used with the Apple TV the evening before. Sure, I hear you, this is not that big of a deal. Some of you might even say that a bit of exercise would do us all good. Well, you’re not wrong. But on the other hand, some things just needs to be fixed.

AVR-X2100W web interface
AVR-X2100W web interface

This should be easily solved if there just was some kind of programmatic API available. The receiver in question, a Denon AVR-X2100W, is not exactly the latest and greatest, but it connects to the local network and comes with a built in web server and a simple GUI. The user interface is pretty much as delightful as you’d expect, pictured here for your viewing enjoyment.

Yeah, so I’m obviously not going to use that. But where there is a GUI, there must be an API…

Armed with that API we should be able to write a simple CLI to control the receiver from the command line. Firing up the browser’s Web Developer console and issuing a few commands by manipulating the GUI reveals an HTTP API. An API with about as much aesthetic appeal as the GUI itself. A quick web search failed to bring up the specification for this API, but it did find a specification of something called the Denon AVR control protocol — basically a text-based API to interact with the receiver over port 23. Yup, that’s Telnet.

The Solution

I/O

Time to write some code. It might seem an odd choice, writing a CLI in Java. It’s been a long time since I did much socket programming, and last time I did, it was in Java. So I resisted the urge to further extend this journey by exploring better suited alternatives and instead tried to remember the ins and outs of Socket, PrintWriter, InputStreamReader, and friends.

Picocli

Adding the picocli library to the mix helped out with making my program a little more command-line friendly. Soon enough, I had the necessary functionality to control my receiver from the terminal. Now, the Denon AVR control protocol is rather extensive, so I settled for implementing only the subset of features I needed. It should be straightforward to add additional functionality by extending the program, code is available on GitHub.

GraalVM

To add a bit of a native feeling to the setup, I turned to GraalVM and it’s native image support. GraalVM is a high performance runtime that can do all sorts of tricks, but here I wanted to leverage its support for ahead-of-time compiling Java code to a standalone binary executable using its native image builder.

GraalVM comes in an open source Community Edition that can be downloaded and run on a number of platforms. If you are on a Mac, there is also a Homebrew distribution. The native image builder is a separate utility installed using the GraalVM updater tool, gu.

It’s possible to integrate building the native image into GitHub actions workflows. There are even mac runners to use if you want to build a macOS compatible binary. Checkout the repository’s release.yml for an example.

With GraalVM and the native Image builder installed, and the path augmented, we can create a standalone native binary by running (here assuming version 0.10):

%> mvn clean install
%> native-image --no-server --no-fallback -jar target/avr-ctrl-0.10.jar avrctrl

This produces the avrctrl binary, which we can use like this…

%>avrctrl --help

…to get a friendly help message printed:

Usage: avrctrl [-dhV] [-n=<host>] [-s=<String=String>[|<String=Strin g>...]]...
[COMMAND]
Control Denon AVR-Xx100 receivers
-d, --debug
-h, --help Show this help message and exit.
-n, --host-name=<host> The AVR network name or IP address
(default: denon-avr-x2100w.local)
-s, --sources=<String=String>[|<String=String>...]
Sources map in format
'-s "Sonos=CD|AppleTV=MPLAY"'
-V, --version Print version information and exit.
Commands:
on Turn on receiver (all zones)
select-input
standby Set receiver to standby (all zones)
vol Set master volume
vol-down Decrease master volume 0.5 step
vol-up Increase master volume 0.5 step
z2-off Power off Zone 2
z2-on Power on Zone 2

So, to turn on the receiver, issue:

%>avrctrl on

Now, for the native image build to work correctly, there is a bit of config that needs to happen. Thankfully picocli handles this for us using it’s codegen annotation processor, conveniently set up in the pom.xml file.

The CLI tool allows for some settings (e.g. the host name or IP of the receiver, and input source mappings) to be defined in a configuration file read from ~/.avrctrl.properties, the readme has the details.

So, there is that. Clearly having this versatile and super-convenient command-line tool to our disposal is a huge life upgrade over having to walk over to the receiver. But, although hard to believe, we can do even better.

And better in this case comes as an Alfred workflow.

Alfred

If you are on a Mac and haven’t tried Alfred before, now would be a good time to do so. You can think of it as a turbocharged version of macOS Spotlight that not only allows you to search and quickly access files and apps, but also to control your mac with a few carefully selected keystrokes. With Alfred workflows we can stitch together workflows for all sorts of things, including launching the Sonos app and starting the receiver.

Here is the graphical representation of the startsonos workflow right from within Alfred preferences:

Visual representation of the ‘startsonos’ workflow.
‘startsonos’ Alfred workflow

The workflow is started by invoking Alfred using the global shortcut keys and typing startsonos. This will launch the Sonos app, and in parallel run a couple of scripts to:

  1. Turn on the receiver (using avrctrl on)
  2. Wait two seconds, allowing for the receiver to wake up
  3. Switch off the second zone — which is turned on by default, but not needed — (avrctrl z2-off), select Sonos as input source (avrctrl select-input sonos), and finally, set the volume to a comfortable morning listening level (avrctrl vol 45)

Here is what’s inside the last box, down to the right:

Script defined as the last step of the ‘startsonos’ workflow.
‘startsonos’ workflow, final step

Workflows will pass along supplied arguments, so if reaching for the terminal just feels like too much work for invoking individual avrctrl commands, we can set up a generic workflow like this…

Visual representation of the ‘avrctrl’ workflow.
‘avrctrl’ Alfred workflow

…configure like this…

Script defined as the script step of the ‘avrctrl’ workflow.
‘avrctrl’ workflow script.

…and run it like this:

Dialogue invoking the ‘avrctrl’ workflow with arguments ‘vol 50’.
Invoke the ‘avrctrl’ workflow with arguments ‘vol 50’.

All without even leaving our desk. Instant quality of life improvement unlocked.

And that’s about it.

--

--

Patrik Fredriksson

Developing software, products, teams, and organizations