Monday, 18 December 2023

Denon : Using Heos

Intro

In the previous two Denon posts I have described setting up a web page which duplicates most of the functions available in the Denon AVR App, tailored to my own needs.  I now want to see what the Heos app can do for me and which functions I can access through my own web page.

Heos is a brand associated with smart speakers, I believe it is owned by Denon/Marantz.  When you switch the amplifier to Heos input it functions like a Smart Speaker.  Heos, like Sonos, needs you to choose what to play and to control the speakers.   Heos has Android and Apple apps which allow you to choose music and change volume, pause, skip tracks etc.  Heos and Sonos focus on Streaming Services such as Spotify and Amazon for their music input but they additionally allow you to add music from local files or shared folders.  I am most interested in playing my own music, more specifically I want to easily select and play albums.

What can Heos do?

Heos can select the standard Denon inputs for you:

  •  AMUSE
  • CD
  • JONGO
  • Media Player (USB)
  • Tuner.

In addition it can play:

  • Local music from the iPad / phone running the Heos App
  • Music from a network DLNA shared folder.  For me this is principally my Router USB Media Server (which is called RouterShare).
  • USB (which is a stick plugged into Denon)
  • Heos playlists which you can define (by saving a list of items off the playing queue).  These playlists are stored on your cloud HEOS account, so they available on all apps.
  • TuneIn radio stations, including a list of favourites
  • Music Services such as Spotify or Amazon Music
  • Recently played tracks can be selected.
When music is playing you have standard options to control:
  • volume/mute
  • pause/resume
  • next/previous
  • shuffle/repeat
  • save current music queue as a playlist
When a track is playing you can see time elapsed, time remaining, a list of items on the queue

My particular objective is to be able to select and play albums from the RouterShare DLNA server.

Development Approach

Of course I would prefer to build on someone elses solution for my Heos web page.  The approach used to develop Denon functions entailed providing a python client as a back-end and invoking those functions from a web page using javascript and web sockets.  Separation of front-end and back-end programming was very successful, making testing and fault-finding comparatively straigh-forward, so I followed the same approach for Heos.

Github is now the principal repository for open-source projects.  Searching for "Heos" on github yielded heospy as the most appropriate python project.  However I found that the Heos CLI protocol specification gives me the information I need.  It explains that you telnet into Denon port 1255 and use commands in the format heos://player/get_players to communicate with Heos as shown below

As an aside, my development environment is Windows and there is no official Heos "app" for Windows.  Some forum posts recommended "Heos Remote" which is a very simple client available from the Windows Store for £5.  I bought a copy which allowed me to quickly see what was happening inside the Heos environment.


Heos Web Page PoC

When programming Denon XML back-end I tried out the python Telnet package interactively.  It is quite simple to adapt this to my needs so that the function telnet_client writes a request to the Denon Heos port and reads the response.



There were a few formatting wrinkles to resolve to make the data format acceptable for input to javascript on the web page.  Firstly the response above includes plenty of newline characters so it is readable.  We use the command heos://system/prettify_json_response?enable=off
to remove them.  Secondly our JSON conversion alters double quotes to single and we have to put them back to doubles.  Once this has been done we have our JSON ready to pass to the web page.

Our mechanism for invoking python scripts from a web page using web sockets is now well established.

Step 1: On the web page make a web socket request using the javascript we.send command.

Step 2: Web socket processing sees the denon command and calls the python denon script.  Within this script the "H" parameter causes our heos.py script to be executed.

Step 3: Output from heos.py is returned to the web socket and is captured into the web page within the ws.onmessage function as event.data.  ws.onmessage parses event.data input into a JSON structure myobj

Step 4: javascript jquery function inserts appropriate output from myobj into the web page.
In this PoC example there is a top level json field payload which is an array.  We take the first element of the array and insert the name field into html field "heos1".  In fact this displays the name of the Denon receiver "Bruce" in the web page.  I called the receiver Bruce as Google home had a problem understanding the (rather dull) name Denon.

We have now successfully demonstrated that we can execute Heos commands and utilise output in our web page.


I found a useful summary of Heos commands we can use in a github project heospy


Heos Control commands

Now we have a working mechanism we can start adding useful stuff.
We add buttons for play, pause, stop, next, previous and status.  The status button will update information on the current song, artist and track.

In python we send appropriate commands to Denon for the corresponding functions:
The "status" parameter causes Denon to provide song, artist and track info which is received by ws.onmessage and added to the web page.
The console shows information received and the web page is updated:

Playlists

Heos allows you to setup your own playlists.  These are stored in the cloud under you Heos user name so that they can be selected from any device.  To put an album or a selection of tracks on a playlist, first place the tracks on the queue and then save the queue to a named playlist.  I dont expect to use them much but it will be helpful to be able play predefined playlists.

As usual we ask Denon to provide the information to us. The system identifier for playlists is sid=1025.
We issue a browse command for sid=1025 and a list of playlists is returned.  We can then play that list by quoting the container id e.g. cid=555631
I set up two sample playlists and hard-coded their cids so that I could use a python command to start playing them:

Heos Commands to play an album

The main use of Heos is to play albums.  I have migrated my commonly used albums to a DLNA shared folder on the router called RouterShare.  I have defined this folder to Denon so that we can access an play those albums.  My albums on RouterShare are ordered on three levels of folders, at the top level are alphabetical groups of artists, the middle level has a list of the artists in that group  and the bottom level has albums by the artist.  The files in bottom level folders are the album tracks.
For example "Taylor Swift" is a folder in the T-Z group and includes the album "evermore (2020)".

The Heos Browse command is used to determine the content of DLNA shares, which are defined as system identifier sid=1024.  The telnet output below shows that there are two DLNA shares and RouterShare has sid= -1779289667.  Browsing sid=-177928667 shows that "Browse Folders" has a container id cid=64.  
If we look at container cid=64 we see that cid=64$2 contains our music and cid=64$2$5 is the folder
T-Z

Going down a couple more levels shows us that "evermore (2020)" has cid=64$2$5$0$0 and browsing this cid shows us the identifiers of individual tracks, for example the first track "willow" has mid=64$2$5$0$0$0.

We can play the album by adding cid=64$2$5$0$0 to the Heos with the Heos Command
where pid is the physical id of the smart speaker, sid is the device containing the music and cid is the container whose contents we want to add.  The last parameter aid=4 says that we want to replace whatever is on the queue with our new content.  Optionally this track list could be saved as a playlist.

A summary of these commands is shown below.

Album Play Code

Python code is quite straightforward, we add two new functions album_list (show contents of a container) and album_list (add a container contents to a queue).

The "-g" command displays the contents of a container and "-a" adds the contents of a container to the play queue. "-g root" displays contents of the top level container "64$2".

We add some extra fields to the web page "heos5" through "heos9" so we can display album and track playing information.
When the page is loaded the top level folder structure is shown as buttons in cell in heos5.  Clicking on one of these buttons causes a list of artists to be displayed in cell heos6 and clicking on an artist displays their albums in cell 7.  Clicking on the album causes it to be placed on to the Heos queue and start playing.
The web page below shows part of the html when the "T-Z" button has been pressed.  It shows that if the "Taylor Swift" button is pressed a web socket command is made to obtain the contents of container 64$2$5$0, which comprises a list of Taylor Swift albums.
A javascript loop is used to format each array element to a button for the next level.  At this stage the javascript becomes somewhat incomprehensible so I wont include the details.

Current track and Queue


The final piece of the picture for a Heos Music Player is to display the track which is currently playing and a list of tracks in the queue.
The appropriate Heos commands to display the current queue and display the current track are shown below.
In fact, at the time of writing, the queue command only passes the first six tracks to display on the web page.  I will fix the problem in due course.    Current track is stored in heos9 and the queue is inserted as field heos8.

Endnote

We now have a fully functional Heos music player which is tailored to my requirements.  You are quickly able to select and play any album from the shared folder on the router.  This has greatly exceeded my expectations.  It provides the functionality of my existing AMUSE music player, it uses the Router "NAS" folder and can be integrated with Denon controls on a single page.  In fact, once I have tidied it up it will probably replace the RPI mpd solution completely.





Monday, 20 November 2023

Denon webpage - xml

Intro

Using a github python package from ol-iver I have been able to implement the main amplifier-related Denon functions I want on a web page.


Unfortunately I want more!

It must be possible to obtain from Denon, all the information which is available in the Denon iPad/Android app.   In particular, the app shows a list of all radio station presets from which you can select.  In the protocol specification I did find a telnet command to change preset but there isn't a command to list all presets.

There is a mention in ol-ivers github page of how he used  wireshark to identify XML commands which retrieve information from his Denon AVR.  I took this as a hint that I must use Network packet capture to discover further commands which we can use.  There was also a reference in a Home Assistant Denon forum to the "unofficial port 80 HTTP protocol" which may provide XML using an http request. 



Packet Capture

If I start up wireshark and monitor all packets coming to / from Denon I see nothing useful.  Both the iPad and Denon are on the wifi network and wireshark is running on the PC with an ethernet card or wifi.  Windows isn't very good at putting network cards into monitor / promiscuous mode so I wasn't sure it was capable of doing the capture I want.

Previously I have had better success with a packet analyser called Fiddler.  In particular, it is possible to set up Fiddler on the PC as a proxy server and then configure the iPad to send all its packets via Fiddler.
My copy of Fiddler hasn't been used for a few years and doesn't seem to work.  "Fiddler Everywhere" is a new version of their software which is available for a 10-day free trial, it can also capture https traffic.
I installed Fiddler Everywhere and found it is quite easy to use and works very well.  In fact I will subscribe for a couple of months ($12pcm) as I think it is worthwhile.

Fiddler shows exactly what I want to see, a lot of http requests/responses to/from Denon together with the appropriate message headers and bodies.  The message body is in XML format.  

Packet Analysis

Most of the packets are in POST format but a few are GET requests.  The good thing about GET requests is that you can enter them directly into the browser.  I tried submitting a sample request and Denon responded with an answer written in XML 😀😀

POST requests typically need to be sent using a browser HTML form, but I use an excellent little utility, postman which submits requests and receives responses.  Now I can submit a POST request and receive a response.

Looking through the (hundreds of) requests I saw that most of them are covered by ol-iver's functions.  The POST request which I really want is "GetTunerStatus" which I will describe further below.

Some of the GET requests are interesting.  As shown on the screen shot below, commands within the Denon Protocol specification can be submitted as  a GET request, which is potentially much simpler than ol-ivers telnet interface.  Packet 1449 shows a HTTP GET request to change the radio station preset to number 6.

I can easily add some buttons on the webpage to change the preset value:


Packet 1238 shows a POST request to list all presets and the xml response which is provided

Web page

When I tried any POST requests in my web page I received messages to say that they were blocked owing to CORS (Cross Origin Resource Sharing) security weakness.
If I wanted to I could circumvent this issue by starting chrome on windows with a switch to allow CORS, but I felt this wasnt the way to go and I abandoned the idea.

Instead I returned to python, found a geeksforgeeks explanation for submitting HTTP POST requests and tested whether this would resolve the problem.  Success 😀😀, Python responds with a chunk of XML containing the information I need.
Initially I imported XML to JSON (xmltodict) to make the data accessible but I ended up just searching on the <name> tag to extract the current radio station.

CGI

Rather than submitting web requests using the websocket interface I felt it would be more sensible to use the lighttpd web server python CGI interface.  My python script needed some tweaking to work in CGI (xmltodict didn't work) but I was soon able to report the tuner preset back to the web page.  This was inserted in my draft web page into an iframe.  An early draft is shown below (before formatting).

Result

We now have a properly functional single, simple web page which has all my Denon controls easily accessible and controllable. 










Denon webpage - using python


Aim

I am very happy with my recent purchase of a Denon Receiver to replace my old Sony amplifier which was creaking with age.  The sound quality is great and it will play music from my own collection, radio and CD.  I am used to choosing music to play from my AMUSE (A MUSic SErver) web page which controls an RPI music player (MPD) and I can continue to do this.

In addition to a traditional IR remote, Denon provide an iPad/phone app which allows me to remotely control the power, volume, sound source and radio station.  The app isn't brilliant but does the job.
There is also a simple web interface which can be used to configure Denon, but doesn't allow control of the various functions.

I would prefer to have my own web page to control Denon functions (e.g. power, volume, sound source, radio station).  This should work in parallel with the AMUSE web page, and it may even be possible to combine them.  It is slightly simpler to load a web page than an app and I put all the operations I need on the same page making it quicker and more convenient to use.

Research

Github is the easiest place to find suitable projects / programs on which to base my own solution.  I found about twenty hits for Denon; node.js and python programs are of interest but I ignore others using languages with which I am unfamiliar, Go, Typescript et al.

A python solution "denonavr" by ol-iver  looks to be well-used and popular.  It is a command line tool but I can incorporate python CLI using my websocket interface as I do for other programs.
Denonavr uses a python package asyncio for asynchronous communication.  You submit a request, and when the response is received a function is invoked to process it.
Denonavr appeared to have a comprehensive set of commands to control the amplifier power, volume, source etc although there isn't anything to change readio stations - presumably because the author has an amplifier without a built-in tuner.

Denon.py

I installed the denonavr package in a python virtual environment and ran commands interactively to see if it worked.  Following the excellent documentation provided by ol-iver I was quickly able to make first contact with Denon.


The example above shows connection to Denon using its ip address 192.168.0.189 followed by a request which lists the sound sources which Denon can provide.  These commands are "synchronous", so each communication to Denon waits for a response.
Asynchronous communication is preferred and in future will be the only access supported.  Its advantage is that resources aren't tied up waiting for a response from the server.  A sample async interactive session is shown below.

Having satisfied myself that the package was useful I could write a skeleton script, using the argparse package to specify permitted commands.

It is then a straightforward task to use denonavr functions to implement my own commands.

Radio Stations

I already knew that ol-iver didn't implement radio station functions.  Looking through the Denon Protocol specification I could see a very small number of Telnet commands which could be useful for changing the radio channel.

Although ol-iver uses the telnet protocol for his package it wasn't obvious to me if / how you could adapt it for these extra commands.  I checked with a Putty telnet session and the commands seem to work fine. The "TPAN?" command returns the number of the current radio station.  TPAN01 would change the station to channel 1.


I looked for a generic python telnet package I could use to put these commands into a program.  The current python library is telnetlib3 and I found an excellent geeks for geeks tutorial to try it out.  The sample script enables me to execute a "PW?" command to establish that Denon is turned off.  I could then turn the amp on with a "PWON" command.
This gives me a generic telnet capability to add commands that I need.

Sample Webpage

Based on previous projects I can simply code my python commands into a web socket request.  The command is then executed on the server and output is returned to the webpage:

The picture below shows my draft Denon Webpage:

Buttons to control Denon are grouped in a table for convenience and at the top a table shows current Denon status. This information is available when Denon is "turned off" and of course you can issue a command to turn the device on.

Conclusion

We have made an excellent start in setting up a Denon web page.  It is easy to use and has all the functions shown on a single page.  I have started to use it on iPad, PC and phone to choose what I listen to.  Some investigation is required to add Radio Stations, which I will look at next.