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

  • Tutorial
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 in the network a description of the Integra Serial Communication Protocol over Ethernet (eISCP) protocol, which underlies 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 will want to independently steer their player or Onkyo receiver. Also, the article may be interesting 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 We scan the entire range of ports:

> nmap -sS -p0-65535 -T5
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:

  • 80 / tcp is clear - this is the device configuration page. In my model, here is just network setup and firmware upgrade. There is no playback control. Through it, the dynamic links of the form “” allow you to access the picture of the track that is currently playing.
  • 4545 / tcp - appeared after the most recent firmware update. Nmap doesn't know anything about him. When you try to connect, it immediately sends json with the current playback status and sends an update every second.

    Data block with playback status
      "data": {
        "fireCast": false,
        "status": {
          "duration": 224893,
          "playBytes": 0
        "error": "",
        "matchingMediaRoles": [],
        "controls": {
          "previous": true,
          "next_": true,
          "seekBytes": true,
          "seekTime": true,
          "pause": true,
          "seekTrack": true
        "mediaRoles": {
          "title": "",
          "asciiTitle": ""
        "playId": {
          "systemMemberId": "Onkyo NS-6130",
          "timestamp": 447085
        "state": "playing",
        "trackRoles": {
          "mediaData": {
            "metaData": {
              "artist": "Ottawan",
              "album": "Greatest Hits",
              "serviceID": "Storage_usb2"
          "title": "Shalala-Song",
          "flags": {
            "file": true
          "path": "storage_file_usb2:sda-94DB-FB8F/flac/Disco/Ottawan/Greatest Hits (2007)/05-Shalala-Song.flac",
          "optPlayingConentInfo": {
            "playingTrackTotal": 17,
            "playingTrackNo": 4
          "icon": "file:///tmp/temp_data_albumArt_3c70a403584dc761cabc88ac0dfbb95c",
          "type": "audio"
      "playTime": {
        "i64_": 139021,
        "type": "i64_"
      "senderVolume": {},
      "senderMute": {},
      "sender": "Onkyo-NS-6130-E1EE7F"

    As I said, this port appeared with the latest update. There is no documentation from the word at all. It may be useful to develop a lightweight control panel. But in this direction I have not dug.
  • 5000 / tcp - nmap defines it as Apple AirTunes. It seems to be true, since support for this protocol is stated in the documentation.
  • 8008 / tcp, 8009 / tcp - the purpose is unclear, nmap does not know anything about them.
  • 8080 / tcp - http-proxy, the purpose of which in the context of this player is not entirely clear.
  • 8888 / tcp - port of Universal Plug and Play (UpnP) protocol . With the gupnp-universal-cp utility from the gupnp-tools package, you can view its description:

    At first it was thought that the control in the official application was implemented on the basis of this particular protocol. As it turned out later, I was mistaken. Also tried several UpnP clients, both mobile and desktop. All are practically inoperable: some control commands work, some not, and it is completely chaotic.
  • 10001 / tcp is similar to the SCP configuration port, but how to use is not clear.
  • 60128 / tcp - and, finally, the main character of this article, the port of the eISCP protocol. Nmap doesn't know anything about him either. Let's open the veil of secrecy.

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:

  • Install Android tcpdump on an Android device where the Onkyo Controller application is already installed
  • We go to the Android device via adb as root:

    > adb root && adb shell
  • Go to any directory of the built-in SD card:

    root@fiber-bs1078:/> cd /sdcard/work
  • run tcpdump (with write to file)

    root@fiber-bs1078:/sdcard/work> tcpdump -vX -i any -w onkyo.dump host
    tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
  • Run the application Onkyo, and from there we start playing music
  • When several hundreds of packages are typed, we stop tcpdump by Cttl + C
  • We return to the terminal, from where we launched ADB and copy the file to the working computer

    root@fiber-bs1078:/sdcard/work> exit
    > adb pull /sdcard/work/onkyo.dump .
    [100%] /sdcard/work/onkyo.dump
  • Run wireshark and see what happens there.

    > wireshark onkyo.dump & 

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

And he responds to it:

Here we come to the essence of the article, namely: what are the pictures above for the letters like these - ISCP? This abbreviation means Integra Serial Control Protocol, originally developed to control Onkyo devices through the RS-232 port (there is an interesting old articleon this occasion). 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. The 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 starts with a “!” Character, then the target device code, followed by three letters of the command, and then a string of parameters of arbitrary length. It 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 highlight such:

  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 tied to a track (for example, the emblem of a radio station if Internet radio is selected).
    • 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 and managing your music library:

    • 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 of the socket), 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 used on 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 a message to the NLT - header information of the current list with the parameter “0E01000000090100FF0E00TuneIn Radio”, which, when decoded in accordance with the specification, gives the following information:


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"?><responsestatus="ok"><itemsoffset="0"totalitems="9"><itemicontype="F"iconid="29"title="My Presets"selectable="1" /><itemicontype="F"iconid="29"title="Local Radio"selectable="1" /><itemicontype="F"iconid="29"title="Music"selectable="1" /><itemicontype="F"iconid="29"title="Talk"selectable="1" /><itemicontype="F"iconid="29"title="Sports"selectable="1" /><itemicontype="F"iconid="29"title="By Location"selectable="1" /><itemicontype="F"iconid="29"title="By Language"selectable="1" /><itemicontype="F"iconid="29"title="Podcasts"selectable="1" /><itemicontype="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 user name. For 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"?><popuptitle="Try Deezer Premium+"align="center"type="custom"time="0"uri="resource:///popup"><labeltitle=""align="center"total="1"uri="resource:///popup/label:0"><linetext="Listening is limited to 30-second clips. Subscribe to enjoy unlimited music!"align="left"uri="resource:///popup/label/line:0"order="0" /></label><buttongrouptitle=""align="center"total="1"uri="resource:///popup/buttongroup:0"><buttontext="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 the 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"?><responsestatus="ok"><deviceid="NS-6130"><brand>ONKYO</brand><category>NAP-O</category><year>2016</year><model>NS-6130</model><destination>xx</destination><macaddress>0009B0E1EE7F</macaddress><modeliconurl></modeliconurl><friendlyname></friendlyname><firmwareversion>2110-0000-0000-0010-0000</firmwareversion><ecosystemversion>200</ecosystemversion><netservicelistcount="9"><netserviceid="0e"value="1"name="TuneIn Radio"account="Username"password="Password"zone="01"enable="01" /><netserviceid="0a"value="1"name="Spotify"zone="01"enable="01" /><netserviceid="12"value="1"name="Deezer"account="Email address"password="Password"zone="01"enable="01" /><netserviceid="18"value="1"name="AirPlay"zone="01"enable="01" /><netserviceid="1b"value="1"name="TIDAL"account="Username"password="Password"zone="01"enable="01" /><netserviceid="00"value="1"name="Music Server"zone="01"enable="01"addqueue="1"sort="1" /><netserviceid="43"value="1"name="FlareConnect"zone="07"enable="0e" /><netserviceid="40"value="1"name="Chromecast built-in"zone="01"enable="01" /><netserviceid="1d"value="1"name="Play Queue"zone="01"enable="01" /></netservicelist><zonelistcount="4"><zoneid="1"value="1"name="Main"volmax="0"volstep="0"src="1"dst="1"lrselect="0" /><zoneid="2"value="0"name="Zone2"volmax="0"volstep="0"src="0"dst="0"lrselect="0" /><zoneid="3"value="0"name="Zone3"volmax="0"volstep="0"src="0"dst="0"lrselect="0" /><zoneid="4"value="0"name="Zone4"volmax="0"volstep="0"src="0"dst="0"lrselect="0" /></zonelist><selectorlistcount="3"><selectorid="2b"value="1"name="NET"zone="01"iconid="2b" /><selectorid="29"value="1"name="USB(F)"zone="01"iconid="29"addqueue="1" /><selectorid="2a"value="1"name="USB(R)"zone="01"iconid="2a"addqueue="1" /></selectorlist><presetlistcount="40"><presetid="01"band="0"freq="0"name="" /><presetid="02"band="0"freq="0"name="" /><presetid="03"band="0"freq="0"name="" /><presetid="04"band="0"freq="0"name="" /><presetid="05"band="0"freq="0"name="" /><presetid="06"band="0"freq="0"name="" /><presetid="07"band="0"freq="0"name="" /><presetid="08"band="0"freq="0"name="" /><presetid="09"band="0"freq="0"name="" /><presetid="0a"band="0"freq="0"name="" /><presetid="0b"band="0"freq="0"name="" /><presetid="0c"band="0"freq="0"name="" /><presetid="0d"band="0"freq="0"name="" /><presetid="0e"band="0"freq="0"name="" /><presetid="0f"band="0"freq="0"name="" /><presetid="10"band="0"freq="0"name="" /><presetid="11"band="0"freq="0"name="" /><presetid="12"band="0"freq="0"name="" /><presetid="13"band="0"freq="0"name="" /><presetid="14"band="0"freq="0"name="" /><presetid="15"band="0"freq="0"name="" /><presetid="16"band="0"freq="0"name="" /><presetid="17"band="0"freq="0"name="" /><presetid="18"band="0"freq="0"name="" /><presetid="19"band="0"freq="0"name="" /><presetid="1a"band="0"freq="0"name="" /><presetid="1b"band="0"freq="0"name="" /><presetid="1c"band="0"freq="0"name="" /><presetid="1d"band="0"freq="0"name="" /><presetid="1e"band="0"freq="0"name="" /><presetid="1f"band="0"freq="0"name="" /><presetid="20"band="0"freq="0"name="" /><presetid="21"band="0"freq="0"name="" /><presetid="22"band="0"freq="0"name="" /><presetid="23"band="0"freq="0"name="" /><presetid="24"band="0"freq="0"name="" /><presetid="25"band="0"freq="0"name="" /><presetid="26"band="0"freq="0"name="" /><presetid="27"band="0"freq="0"name="" /><presetid="28"band="0"freq="0"name="" /></presetlist><controllistcount="61"><controlid="Bass"value="0"zone="1"min="-10"max="10"step="2" /><controlid="Treble"value="0"zone="1"min="-10"max="10"step="2" /><controlid="Center Level"value="0"zone="1"min="-12"max="12"step="1" /><controlid="Subwoofer Level"value="0"zone="1"min="-15"max="12"step="1" /><controlid="Subwoofer1 Level"value="0"zone="1"min="-15"max="12"step="1" /><controlid="Subwoofer2 Level"value="0"zone="1"min="-15"max="12"step="1" /><controlid="Phase Matching Bass"value="0" /><controlid="LMD Movie/TV"value="0"code="MOVIE"position="1" /><controlid="LMD Music"value="0"code="MUSIC"position="2" /><controlid="LMD Game"value="0"code="GAME"position="3" /><controlid="LMD THX"value="0"code="04"position="4" /><controlid="LMD Stereo"value="0"code="00"position="4" /><controlid="LMD Direct"value="0"code="01"position="1" /><controlid="LMD Pure Audio"value="0"code="11"position="2" /><controlid="LMD Pure Direct"value="0"code="11"position="1" /><controlid="LMD Auto/Direct"value="0"code="AUTO"position="2" /><controlid="LMD Stereo G"value="0"code="STEREO"position="3" /><controlid="LMD Surround"value="0"code="SURR"position="4" /><controlid="TUNER Control"value="0" /><controlid="TUNER Freq Control"value="0" /><controlid="Info"value="2" /><controlid="Cursor"value="1" /><controlid="Home"value="0"code="HOME"position="2" /><controlid="Setup"value="1"code="MENU"position="2" /><controlid="Quick"value="0"code="QUICK"position="1" /><controlid="Menu"value="0"code="MENU"position="1" /><controlid="AMP Control(RI)"value="1" /><controlid="CD Control(RI)"value="1" /><controlid="CD Control"value="0" /><controlid="BD Control(CEC)"value="0" /><controlid="TV Control(CEC)"value="0" /><controlid="NoPowerButton"value="0" /><controlid="DownSample"value="0" /><controlid="Dimmer"value="1" /><controlid="time_hhmmss"value="1" /><controlid="Zone2 Control(CEC)"value="0" /><controlid="Sub Control(CEC)"value="0" /><controlid="NoNetworkStandby"value="0" /><controlid="NJAREQ"value="1" /><controlid="Music Optimizer"value="0" /><controlid="NoVideoInfo"value="1" /><controlid="NoAudioInfo"value="1" /><controlid="AV Adjust"value="0" /><controlid="Audio Scalar"value="0" /><controlid="Hi-Bit"value="0" /><controlid="Upsampling"value="0" /><controlid="Digital Filter"value="1" /><controlid="DolbyAtmos"value="0" /><controlid="DTS:X"value="0" /><controlid="MCACC"value="0" /><controlid="Dialog Enhance"value="0" /><controlid="PQLS"value="0" /><controlid="CD Control(NewRemote)"value="0" /><controlid="NoVolume"value="1" /><controlid="Auto Sound Retriever"value="0" /><controlid="Lock Range Adjust"value="0" /><controlid="P.BASS"value="0" /><controlid="Tone Direct"value="0" /><controlid="DetailedFileInfo"value="1" /><controlid="NoDABPresetFunc"value="0" /><controlid="S.BASS"value="0" /></controllist><functionlistcount="10"><functionid="UsbUpdate"value="0" /><functionid="NetUpdate"value="1" /><functionid="WebSetup"value="1" /><functionid="WifiSetup"value="1" /><functionid="Nettune"value="0" /><functionid="Initialize"value="0" /><functionid="Battery"value="0" /><functionid="AutoStandbySetting"value="0" /><functionid="e-onkyo"value="0" /><functionid="UsbDabDongle"value="0" /></functionlist><tunerscount="0"></tuners></device></response>

The set of commands that will have to be used to control the device depends largely on what is 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 of this connector they began to use a network connection (wired or wireless), they had to wrap these messages in a wrapper for transmission over TCP / IP. This is how the eISCP protocol appeared, where the ISCP message is wrapped in such a package:

Under the spoiler, the procedure code that completely forms such a package for a message with a given code (code variable), formed by a parameter string (parameters variable) and a given protocol version (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
privatefinalstaticint MIN_MSG_LENGTH = 22;
privatefinalstatic String MSG_START = "ISCP";
privatefinalstatic Character START_CHAR = '!';
privatefinalstaticint LF = 0x0A;
byte[] getBytes()
    if (headerSize + dataSize < MIN_MSG_LENGTH)
    finalbyte[] bytes = newbyte[headerSize + dataSize];
    Arrays.fill(bytes, (byte) 0);
    // Message headerfor (int i = 0; i < MSG_START.length(); i++)
        bytes[i] = (byte) MSG_START.charAt(i);
    // Header sizebyte[] 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);
    // Parametersfor (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,

Also popular now: