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.
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.
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.
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
InputStreamReader, and friends.
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.
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…
…to get a friendly help message printed:
Usage: avrctrl [-dhV] [-n=<host>] [-s=<String=String>[|<String=Strin g>...]]...
[COMMAND]Control Denon AVR-Xx100 receivers
-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
-V, --version Print version information and exit.Commands:
on Turn on receiver (all zones)
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:
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
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.
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:
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:
- Turn on the receiver (using
- Wait two seconds, allowing for the receiver to wake up
- 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:
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…
…configure like this…
…and run it like this:
All without even leaving our desk. Instant quality of life improvement unlocked.
And that’s about it.