Telnet isn't dead

I bought Denon RCD-N12 AVR (audio/video receiver) and to my surprise I discovered that it supports a beautiful API accessible via… Telnet. In 2025 Telnet seems almost forgotten, with even Python 3.13 removing telnetlib, but here we are.

This is great news, because Telnet’s simplicity makes it almost trivial to script the AVR. Changing equalizer settings is so much easier via telnet than digging through menus and submenus with a remote. I think I’ll build a dedicated menu in Bash just for fun.

Frankly, I didn’t expect to integrate my AVR with Home Assistant. I won’t lie that the thought didn’t cross my mind, but when buying Denon I didn’t research this aspect at all and it wasn’t decisive about the purchase. I thought that maybe new AVR surprise me, the same as my cheap kitchen radio has. (sidenote: It has Frontier Silicon chipset.) With Denon it’s even better: nearly all settings can be controlled through telnet. Just look at the official protocol specification.

There are 2 Home Assistant integrations available for Denon AVRs: denonavr and heos. Only the former one works with my RCD-N12 and unfortunately Media Player exposed by it doesn’t support a simple ON/OFF toggle. Probably because there’s no real “OFF” state, only “STANDBY” From the end-user perspective “standby” works like a real “off”: everything’s off, except wi-fi, which consumes ~2 W of energy. To have this functionality, I had to resort to creating a “telnet switch”.

Basic Telnet

Telnet is a simple plain text protocol used to access a terminal on a remote server. You can asynchronously send commands and receive responses or status updates. Denon protocol uses this functionality extensively. For example, when you listen to FM radio, it continuously communicates updates of RDS (sidenote: Radio Data System) messages.

To connect we type telnet <ip>. Let’s say that IP of my Denon AVR is 192.168.1.206:

$ telnet 192.168.1.206
Trying 192.168.1.206...
Connected to 192.168.1.206.
Escape character is '^]'.

To disconnect press <ctrl> + ] and type close. Otherwise, just type commands from the AVR protocol and see what happens. Don’t mind that the text you’re typing in is constantly overwritten, just type the command and press enter.

For power management, the commands we’re interested in are (<CR> means carriage return, or \r character):

  • PWRON<CR> - turns the AVR on
  • PWRSTANDBY<CR> - turns the AVR off
  • PW?<CR> - reports whether AVR is turned on or off

Unfortunately, the simplicity of telnet can bite our asses and we might be unable to send single commands like echo PWON | telnet <ip>, because telnet client will process the input immediately, even before the connection. This is the reality of telnet, there’s no way around it, you have to deal with it. More sophisticated telnet servers display prompt, so the clients know when server is ready to accept commands. Denon doesn’t do such things, you have to guess.

Standard tool which works great with telnet is expect. We can either wait for a PW.* status report (epect -re "PW.*"), which Denon sends automatically every 10 seconds, or just set some short sleep interval. Even short sleeps work surprisingly well.

expect << EOF
spawn telnet 192.168.1.206
sleep 0.5
send "PWON\r"
EOF

Telnet in Home Assistant

Fortunately Home Assistant supports sending telnet commands via telnet integration. For now I have created only one switch for Denon, but I suspect that I might create more of them in the future, so I split a configuration to many files. In the main configuration.yaml I have this:

switch: !include_dir_merge_list switches/

and in switches/denon.yaml I have this:

- platform: telnet
  switches:
    denon_pw_toggle:
      resource: 192.168.1.206
      port: 23
      command_on: "PWON"
      command_off: "PWSTANDBY"
      command_state: "PW?"
      value_template: '{{ value == "PWON" }}'
      timeout: 1.0

This creates a boolean switch called “denon_pw_toggle”, which we can add to a dashboard. I don’t know if it’s possible to modify existing media player which HEOS integration exposes, or at least bind the color of media player’s icon and switch state, but I’ll continue to experiment and report back if I discover something. So far I’m happy that I can turn on/off my AVR with a single click.

Next: integrating TTS (Text To Speech) messages in media player, which requires a custom script which will clear the media player’s playlist before sending a new audio file.