📜 ⬆️ ⬇️

Onkyo's ISCP / eISCP protocol: control of Onkyo devices over the network

I am sure that many of Habr's readers know, or at least heard about, Onkyo audio equipment. Modern network players and A / V receivers have onboard Linux, as well as the possibility of wired / wireless connection to the network. Onkyo provides its proprietary mobile application for remote control of such a device - Onkyo Controller . There is practically no information about how this application works - there are crumbs on the forums, as well as several projects on github.



But you can find the description of the Integra Serial Communication Protocol over Ethernet (eISCP) protocol, which is the basis of this application. The protocol is interesting. On Habré no article on this protocol could not be found. On the one hand, there is nothing tragic about this, since this proprietary is nowhere, except Onkyo, seems to be used. On the other hand, there is a chance that there will be enthusiasts who want to independently steer their player or Onkyo receiver. Also, the article may be of interest to those who, out of theoretical curiosity, collect knowledge from various network protocols. If interested, please under the cat.

Official information on the article is not enough. Therefore, I will rely not only on the found documentation, since it describes only the protocol commands, but says nothing about the features of their use. Much information was obtained from both the analysis of network traffic using tcpdump / wireshark and the study of the device firmware. This is where I begin.
')
The specific model of my device is not important. I can only say that this is a network player, similar to the one in the picture to attract attention. It can play music not only from external USB-carriers, but also from a music server (DLNA), and also supports Internet radio and streaming services such as Spotify, Deezer and something else. Naturally, the protocol should support all this diversity.

Port Analysis


In order to start asking the right questions in a search engine, I had to first understand what kind of protocol was being used. That is, the first step is to analyze the ports. So, the device is online, its address is 192.168.1.80. Scan the entire range of ports:

> nmap -sS -p0-65535 -T5 192.168.1.80 PORT STATE SERVICE 80/tcp open http 4545/tcp open worldscores 5000/tcp open upnp 8008/tcp open http 8009/tcp open ajp13 8080/tcp open http-proxy 8888/tcp open sun-answerbook 10001/tcp open scp-config 60128/tcp open unknown 

A lot of interesting things have been discovered:


Traffic analysis


Now let's check which port and how the official application communicates with the device. The easiest way to do this is on some root Android (but not in the emulator, since the official application requires a managed device on the same local subnet). For this:


And indeed, communication goes on port 60128. For example, the application sends a request to the player:



And he answers it:



Here we come to the very essence of the article, namely: what is it in the pictures above for letters such - ISCP? This abbreviation means Integra Serial Control Protocol, originally developed to control Onkyo devices through the RS-232 port (there is an interesting old article about this). Later it was expanded by adding the prefix “e” and it turned out eISCP - Integra Serial Communication Protocol over Ethernet. Both versions of the protocol are described in the “Technical Documentation: Integrated Serial Communication Protocol for AV Receiver” document. The first version of the document is dated October 31, 2012, the last one I managed to find was September 4, 2017. All the versions I found are collected in my repository of the demonstration project , which I will discuss later. Further presentation will be based both on this document and on experiments with my player (which, however, is not explicitly mentioned in this document).

Message Specification


The client (for example, a mobile application) and the device exchange short text messages. If it contains numbers, they are usually presented in hexadecimal.

The format of the message from the client to the device is very simple:



The message begins with a “!” Character, then the target device code, followed by three letters of the command, and then a string of parameters of arbitrary length. Ends with the symbol CR (0x0D) or LF (0x0A), or a combination of CR + LF.

Depending on the command, the device responds either with the same message type (if it was a request for a parameter), or with a different type or even a combination of messages (if it was a command for a complex action such as switching a track). The format of the messages sent by the device to the client is the same. The only difference is in the last byte:



The protocol description document contains more than a hundred different commands, my device supports a little more than 30 commands from this document. That is, both the instruction set and the valid parameters depend on the specific device.

Commands can be grouped into logical groups. As an example, I would single out the following:

  1. General device control:
    • NDN: device name.
    • UPD: check and install the firmware update.
    • PWR: power on / off.
    • NRI: extended device information.
    • NTC: commands of the standard remote control (including playback control).
    • CAP: commands to control an external amplifier connected to the RI connector.
  2. Information about the track being played:
    • NAL: album name.
    • NAT: the name of the artist.
    • NTI: track name.
    • NFI: track file information (format, bitrate).
    • NJA: a picture attached to a track (for example, the emblem of a radio station, if Internet radio is chosen).
    • NTM: current time position in the track.
    • NTS: status, allowed to rewind or not (for Internet radio, for example, is not allowed).
    • NST: repeat and random playback control.
  3. Navigating through the library and managing it:

    • SLI: source selection (for example, USB, network services).
    • NSV: select a specific network service (for example, Internet radio, music server). The playlist on my device also belongs to network services, although this is not quite obvious from the point of view of the user interface. And when you turn off the power (pulling out from the outlet), this playlist is deleted!
    • NLT, NLA: navigation through sections (folders) of the library.
    • PQA, PQR, PQO: playlist management: add, delete, reorder.

Naturally, this list is far from complete, I brought it only in order to show the scope and capabilities of this protocol.

In terms of parameters, all messages can be divided into two groups. The first group includes most of the messages. For this group, the parameter string contains data in alphabetic or hexadecimal form and is disassembled byte-by-byte. For example, when switching to the TuneIn Radio service, the player sends an NLT message - header information of the current list with the parameter “0E01000000090100FF0E00TuneIn Radio”, which, when decoded in accordance with the specification, gives the following information:

 SERVICE=TUNEIN_RADIO; UI=LIST; LAYER=SERVICE_TOP; CURSOR=0; ITEMS=9; LAYERS=1; START=NOT_FIRST; LEFT_ICON=NONE; RIGHT_ICON=TUNEIN_RADIO; STATUS=NONE; title=TuneIn Radio 

Almost all messages have the “QSTN” parameter, for example, “! 1NLTQSTN”. This request means a request to the player to return the current status information corresponding to this type of message. It works almost always, but there are rare exceptions, when the player, depending on his inner mood, ignores such requests.

The second group is messages, where the parameter is XML that needs to be parsed using an XML parser. From the example above, from the TuneIn Radio section, you can send an NLA request, to which the answer will receive information about the active list in XML format:

 <?xml version="1.0" encoding="utf-8"?> <response status="ok"> <items offset="0" totalitems="9"> <item icontype="F" iconid="29" title="My Presets" selectable="1" /> <item icontype="F" iconid="29" title="Local Radio" selectable="1" /> <item icontype="F" iconid="29" title="Music" selectable="1" /> <item icontype="F" iconid="29" title="Talk" selectable="1" /> <item icontype="F" iconid="29" title="Sports" selectable="1" /> <item icontype="F" iconid="29" title="By Location" selectable="1" /> <item icontype="F" iconid="29" title="By Language" selectable="1" /> <item icontype="F" iconid="29" title="Podcasts" selectable="1" /> <item icontype="F" iconid="29" title="Login" selectable="1" /> </items> </response> 

That is, the player not only provides text information (which, by the way, is currently displayed on the player’s display), but also advises an adequate icon (folder, music track, track currently playing).

In some cases, the player wants to show a text message in the client application or request additional parameters such as the username. To do this, the player sends a universal NCP message (universal dialog), where the structure of what needs to be shown to the user is described in XML:

 <?xml version="1.0" encoding="utf-8"?> <popup title="Try Deezer Premium+" align="center" type="custom" time="0" uri="resource:///popup"> <label title="" align="center" total="1" uri="resource:///popup/label:0"> <line text="Listening is limited to 30-second clips. Subscribe to enjoy unlimited music!" align="left" uri="resource:///popup/label/line:0" order="0" /> </label> <buttongroup title="" align="center" total="1" uri="resource:///popup/buttongroup:0"> <button text="OK" align="center" uri="/button:0" selected="false" index="0" www="" order="1" /> </buttongroup> </popup> 

In response, the player expects the same message with filled fields (or a pressed button).

Also in XML format is presented a rather important message NRI - general information about the player. The message is quite large, so I hide it under the spoiler.

General information about the player
 <?xml version="1.0" encoding="utf-8"?> <response status="ok"> <device id="NS-6130"> <brand>ONKYO</brand> <category>NAP-O</category> <year>2016</year> <model>NS-6130</model> <destination>xx</destination> <macaddress>0009B0E1EE7F</macaddress> <modeliconurl>http://192.168.1.80/icon/OAVR_120.jpg</modeliconurl> <friendlyname></friendlyname> <firmwareversion>2110-0000-0000-0010-0000</firmwareversion> <ecosystemversion>200</ecosystemversion> <netservicelist count="9"> <netservice id="0e" value="1" name="TuneIn Radio" account="Username" password="Password" zone="01" enable="01" /> <netservice id="0a" value="1" name="Spotify" zone="01" enable="01" /> <netservice id="12" value="1" name="Deezer" account="Email address" password="Password" zone="01" enable="01" /> <netservice id="18" value="1" name="AirPlay" zone="01" enable="01" /> <netservice id="1b" value="1" name="TIDAL" account="Username" password="Password" zone="01" enable="01" /> <netservice id="00" value="1" name="Music Server" zone="01" enable="01" addqueue="1" sort="1" /> <netservice id="43" value="1" name="FlareConnect" zone="07" enable="0e" /> <netservice id="40" value="1" name="Chromecast built-in" zone="01" enable="01" /> <netservice id="1d" value="1" name="Play Queue" zone="01" enable="01" /> </netservicelist> <zonelist count="4"> <zone id="1" value="1" name="Main" volmax="0" volstep="0" src="1" dst="1" lrselect="0" /> <zone id="2" value="0" name="Zone2" volmax="0" volstep="0" src="0" dst="0" lrselect="0" /> <zone id="3" value="0" name="Zone3" volmax="0" volstep="0" src="0" dst="0" lrselect="0" /> <zone id="4" value="0" name="Zone4" volmax="0" volstep="0" src="0" dst="0" lrselect="0" /> </zonelist> <selectorlist count="3"> <selector id="2b" value="1" name="NET" zone="01" iconid="2b" /> <selector id="29" value="1" name="USB(F)" zone="01" iconid="29" addqueue="1" /> <selector id="2a" value="1" name="USB(R)" zone="01" iconid="2a" addqueue="1" /> </selectorlist> <presetlist count="40"> <preset id="01" band="0" freq="0" name="" /> <preset id="02" band="0" freq="0" name="" /> <preset id="03" band="0" freq="0" name="" /> <preset id="04" band="0" freq="0" name="" /> <preset id="05" band="0" freq="0" name="" /> <preset id="06" band="0" freq="0" name="" /> <preset id="07" band="0" freq="0" name="" /> <preset id="08" band="0" freq="0" name="" /> <preset id="09" band="0" freq="0" name="" /> <preset id="0a" band="0" freq="0" name="" /> <preset id="0b" band="0" freq="0" name="" /> <preset id="0c" band="0" freq="0" name="" /> <preset id="0d" band="0" freq="0" name="" /> <preset id="0e" band="0" freq="0" name="" /> <preset id="0f" band="0" freq="0" name="" /> <preset id="10" band="0" freq="0" name="" /> <preset id="11" band="0" freq="0" name="" /> <preset id="12" band="0" freq="0" name="" /> <preset id="13" band="0" freq="0" name="" /> <preset id="14" band="0" freq="0" name="" /> <preset id="15" band="0" freq="0" name="" /> <preset id="16" band="0" freq="0" name="" /> <preset id="17" band="0" freq="0" name="" /> <preset id="18" band="0" freq="0" name="" /> <preset id="19" band="0" freq="0" name="" /> <preset id="1a" band="0" freq="0" name="" /> <preset id="1b" band="0" freq="0" name="" /> <preset id="1c" band="0" freq="0" name="" /> <preset id="1d" band="0" freq="0" name="" /> <preset id="1e" band="0" freq="0" name="" /> <preset id="1f" band="0" freq="0" name="" /> <preset id="20" band="0" freq="0" name="" /> <preset id="21" band="0" freq="0" name="" /> <preset id="22" band="0" freq="0" name="" /> <preset id="23" band="0" freq="0" name="" /> <preset id="24" band="0" freq="0" name="" /> <preset id="25" band="0" freq="0" name="" /> <preset id="26" band="0" freq="0" name="" /> <preset id="27" band="0" freq="0" name="" /> <preset id="28" band="0" freq="0" name="" /> </presetlist> <controllist count="61"> <control id="Bass" value="0" zone="1" min="-10" max="10" step="2" /> <control id="Treble" value="0" zone="1" min="-10" max="10" step="2" /> <control id="Center Level" value="0" zone="1" min="-12" max="12" step="1" /> <control id="Subwoofer Level" value="0" zone="1" min="-15" max="12" step="1" /> <control id="Subwoofer1 Level" value="0" zone="1" min="-15" max="12" step="1" /> <control id="Subwoofer2 Level" value="0" zone="1" min="-15" max="12" step="1" /> <control id="Phase Matching Bass" value="0" /> <control id="LMD Movie/TV" value="0" code="MOVIE" position="1" /> <control id="LMD Music" value="0" code="MUSIC" position="2" /> <control id="LMD Game" value="0" code="GAME" position="3" /> <control id="LMD THX" value="0" code="04" position="4" /> <control id="LMD Stereo" value="0" code="00" position="4" /> <control id="LMD Direct" value="0" code="01" position="1" /> <control id="LMD Pure Audio" value="0" code="11" position="2" /> <control id="LMD Pure Direct" value="0" code="11" position="1" /> <control id="LMD Auto/Direct" value="0" code="AUTO" position="2" /> <control id="LMD Stereo G" value="0" code="STEREO" position="3" /> <control id="LMD Surround" value="0" code="SURR" position="4" /> <control id="TUNER Control" value="0" /> <control id="TUNER Freq Control" value="0" /> <control id="Info" value="2" /> <control id="Cursor" value="1" /> <control id="Home" value="0" code="HOME" position="2" /> <control id="Setup" value="1" code="MENU" position="2" /> <control id="Quick" value="0" code="QUICK" position="1" /> <control id="Menu" value="0" code="MENU" position="1" /> <control id="AMP Control(RI)" value="1" /> <control id="CD Control(RI)" value="1" /> <control id="CD Control" value="0" /> <control id="BD Control(CEC)" value="0" /> <control id="TV Control(CEC)" value="0" /> <control id="NoPowerButton" value="0" /> <control id="DownSample" value="0" /> <control id="Dimmer" value="1" /> <control id="time_hhmmss" value="1" /> <control id="Zone2 Control(CEC)" value="0" /> <control id="Sub Control(CEC)" value="0" /> <control id="NoNetworkStandby" value="0" /> <control id="NJAREQ" value="1" /> <control id="Music Optimizer" value="0" /> <control id="NoVideoInfo" value="1" /> <control id="NoAudioInfo" value="1" /> <control id="AV Adjust" value="0" /> <control id="Audio Scalar" value="0" /> <control id="Hi-Bit" value="0" /> <control id="Upsampling" value="0" /> <control id="Digital Filter" value="1" /> <control id="DolbyAtmos" value="0" /> <control id="DTS:X" value="0" /> <control id="MCACC" value="0" /> <control id="Dialog Enhance" value="0" /> <control id="PQLS" value="0" /> <control id="CD Control(NewRemote)" value="0" /> <control id="NoVolume" value="1" /> <control id="Auto Sound Retriever" value="0" /> <control id="Lock Range Adjust" value="0" /> <control id="P.BASS" value="0" /> <control id="Tone Direct" value="0" /> <control id="DetailedFileInfo" value="1" /> <control id="NoDABPresetFunc" value="0" /> <control id="S.BASS" value="0" /> </controllist> <functionlist count="10"> <function id="UsbUpdate" value="0" /> <function id="NetUpdate" value="1" /> <function id="WebSetup" value="1" /> <function id="WifiSetup" value="1" /> <function id="Nettune" value="0" /> <function id="Initialize" value="0" /> <function id="Battery" value="0" /> <function id="AutoStandbySetting" value="0" /> <function id="e-onkyo" value="0" /> <function id="UsbDabDongle" value="0" /> </functionlist> <tuners count="0"></tuners> </device> </response> 


The set of commands that will have to be used to control the device depends largely on what is located in the zonelist and controllist sections of this message.

ISCP over Ethernet (eISCP)


Messages as I wrote above are intended to be transmitted over cable (RS-232). Older models of receivers were equipped for this 9-pin RS-232 connector. When, instead, this connector began to use a network connection (wired or wireless), we had to wrap these messages in a wrapper for transmission over TCP / IP. This is how eISCP appeared, where the ISCP message is wrapped in this package:



Under the spoiler, a procedure code that completely forms such a packet for a message with a given code (code variable), a generated parameter line (the parameters variable) and a specified protocol version (the version variable). Since the procedure is quite simple, it seems to me that the code in Java will say a lot more than a thousand words.

The procedure for the formation of eISCP messages
 private final static int MIN_MSG_LENGTH = 22; private final static String MSG_START = "ISCP"; private final static Character START_CHAR = '!'; private final static int LF = 0x0A; ... byte[] getBytes() { if (headerSize + dataSize < MIN_MSG_LENGTH) { return null; } final byte[] bytes = new byte[headerSize + dataSize]; Arrays.fill(bytes, (byte) 0); // Message header for (int i = 0; i < MSG_START.length(); i++) { bytes[i] = (byte) MSG_START.charAt(i); } // Header size byte[] size = ByteBuffer.allocate(4).putInt(headerSize).array(); System.arraycopy(size, 0, bytes, 4, size.length); // Data size size = ByteBuffer.allocate(4).putInt(dataSize).array(); System.arraycopy(size, 0, bytes, 8, size.length); // Version bytes[12] = (byte) version; // CMD bytes[16] = (byte) START_CHAR.charValue(); bytes[17] = (byte) '1'; for (int i = 0; i < code.length(); i++) { bytes[i + 18] = (byte) code.charAt(i); } // Parameters for (int i = 0; i < parameters.length(); i++) { bytes[i + 21] = (byte) parameters.charAt(i); } // End char bytes[21 + parameters.length()] = (byte) LF; return bytes; } 


If anyone is interested, here is my example of the protocol implementation for the specification version 1.40 . I will also give a link to this repository . It implements a message library and a command line utility on Python, as well as links to other similar projects.

Implementation of the exchange of information


The messages themselves, originally designed for transmission over low-speed cable, are quite small. Moreover, the player itself is also quite modest - against the background of a huge amount of statistics sent somewhere to the server of the conventional “Amazon”, the amount of information that the player voluntarily gives to the client via ISCP is simply miserable. There is not a word in the protocol specification about when and under what conditions the player sends this or that information. Therefore, I had to experiment here myself for quite a long time so that the mobile client always had all the necessary information about the current state of the device.

In general, communication with the player is based on the request / response scheme. And in certain situations it will not be possible to limit one request. For my player, there are several key events that need to be handled:



- Onkyo NS-6130. , Onkyo NS-6170. - Onkyo , , , , . - , .

, . :


, 10 , , . , , . , , .

- Onkyo , , .

Thanks for attention!

Source: https://habr.com/ru/post/427985/


All Articles