AXN10

About

Hacking the Cambridge Audio AXN10 Streamer.

Url format is ip-address/smoip/endpoint. My guess is SMOIP stands for Stream Magic Over IP, a play on VOIP.

Endpoints

system/info

http://192.168.XX.XX/smoip/system/info

{
  "data": {
    "name": "AXN10",
    "timezone": "Europe/London",
    "locale": "en_GB",
    "usage_reports": true,
    "setup": true,
    "sources_setup": true,
    "versions": [
      {
        "component": "arc",
        "version": ""
      },
      {
        "component": "M4",
        "version": "1.0+gitAUTOINC+0a0e6baee3"
      },
      {
        "component": "cast",
        "version": "1.52.272222"
      },
      {
        "component": "application",
        "version": "1.0+gitAUTOINC+9979df4655"
      },
      {
        "component": "service-pack",
        "version": "v132-b-007"
      }
    ],
    "udn": "e2ba4eea-d744-4621-8234-d5b469b43c23",
    "hcv": 4005,
    "board_id": "08080",
    "model": "AXN10",
    "unit_id": "0040553f",
    "max_http_body_size": 65536,
    "api": "1.6",
    "cpu": {
      "bus_freq": "high",
      "temp": 37000,
      "cores": [
        {
          "load": 0,
          "freq": 200000
        },
        {
          "load": 3,
          "freq": 200000
        },
        {
          "load": 1,
          "freq": 200000
        },
        {
          "load": 2,
          "freq": 200000
        }
      ]
    }
  }
}

system/power

http://192.168.XX.XX/smoip/system/power

{
  "data": {
    "power": "ON",
    "standby_mode": "NETWORK",
    "auto_power_down": 1800
  }
}

system/sources

http://192.168.XX.XX/smoip/system/sources

{
  "data": {
    "sources": [
      {
        "id": "IR",
        "name": "Internet Radio",
        "default_name": "Internet Radio",
        "class": "stream.radio",
        "nameable": false,
        "ui_selectable": true,
        "description": "Internet Radio",
        "description_locale": "Internet Radio",
        "preferred_order": 7
      },
      {
        "id": "MEDIA_PLAYER",
        "name": "Media Library",
        "default_name": "Media Library",
        "class": "stream.media",
        "nameable": false,
        "ui_selectable": true,
        "description": "Media Player",
        "description_locale": "Media Player",
        "preferred_order": 8
      },
      {
        "id": "AIRPLAY",
        "name": "AirPlay",
        "default_name": "AirPlay",
        "class": "stream.service.airplay",
        "nameable": false,
        "ui_selectable": true,
        "description": "AirPlay",
        "description_locale": "AirPlay",
        "preferred_order": 2
      },
      {
        "id": "SPOTIFY",
        "name": "Spotify",
        "default_name": "Spotify",
        "class": "stream.service.spotify",
        "nameable": false,
        "ui_selectable": false,
        "description": "Spotify",
        "description_locale": "Spotify",
        "preferred_order": 4
      },
      {
        "id": "BLUETOOTH",
        "name": "Bluetooth",
        "default_name": "Bluetooth",
        "class": "bluetooth",
        "nameable": false,
        "ui_selectable": true,
        "description": "Bluetooth",
        "description_locale": "Bluetooth",
        "preferred_order": 1
      },
      {
        "id": "CAST",
        "name": "Chromecast built-in",
        "default_name": "Chromecast built-in",
        "class": "stream.service.cast",
        "nameable": false,
        "ui_selectable": false,
        "description": "Chromecast built-in",
        "description_locale": "Chromecast built-in",
        "preferred_order": 6
      },
      {
        "id": "ROON",
        "name": "Roon Ready",
        "default_name": "Roon Ready",
        "class": "stream.service.roon",
        "nameable": false,
        "ui_selectable": false,
        "description": "Roon Ready",
        "description_locale": "Roon Ready",
        "preferred_order": 3
      },
      {
        "id": "TIDAL",
        "name": "TIDAL Connect",
        "default_name": "TIDAL Connect",
        "class": "stream.service.tidal",
        "nameable": false,
        "ui_selectable": false,
        "description": "TIDAL",
        "description_locale": "TIDAL",
        "preferred_order": 5
      }
    ]
  }
}

system/sources/USB_AUDIO

http://192.168.XX.XX/smoip/system/sources/USB_AUDIO

{
  "code": 114,
  "message": "'USB_AUDIO' not available"
}

system/upnp

http://192.168.XX.XX/smoip/system/upnp

{
  "data": {
    "devices": [
      {
        "model": "AXN10",
        "name": "AXN10",
        "manufacturer": "Cambridge Audio",
        "udn": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "description_url": "http://192.168.XX.XX:8050/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/description.xml"
      }
    ]
  }
}

UPnP Description XML: http://192.168.XX.XX:8050/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/description.xml ### presets/list

http://192.168.XX.XX/smoip/presets/list

{
  "data": {
    "start": 1,
    "end": 99,
    "max_presets": 99,
    "presettable": false,
    "presets": [
      {
        "id": 1,
        "name": "Netil",
        "type": "Stream",
        "class": "stream.radio",
        "state": "OK",
        "is_playing": false
      },
      {
        "id": 2,
        "name": "NTS Radio",
        "type": "Radio",
        "class": "stream.radio",
        "state": "OK",
        "is_playing": false,
        "art_url": "https://static.airable.io/91/52/156717.png",
        "airable_radio_id": 2503123992588371
      },
      {
        "id": 3,
        "name": "NTS Radio 2",
        "type": "Radio",
        "class": "stream.radio",
        "state": "OK",
        "is_playing": false,
        "art_url": "https://static.airable.io/68/08/783550.png",
        "airable_radio_id": 4650421128142625
      }
    ]
  }
}

info/spec

http://192.168.XX.XX/smoip/system/info/spec

{
  "data": {
    "timezone": {
      "enum": [
      ...
      ]
    },
    "locale": {
      "enum": [
      ...
      ]
    }
  }
}

system/services

http://192.168.XX.XX/smoip/system/services

{
  "data": {
    "services": [
      {
        "service_id": "qobuz",
        "supported": true,
        "app_visible": false,
        "version": 2
      },
      {
        "service_id": "tidal",
        "supported": true,
        "app_visible": false,
        "version": 2
      },
      {
        "service_id": "deezer",
        "supported": true,
        "app_visible": false,
        "version": 1
      }
    ],
    "qobuz": true,
    "tidal": true,
    "deezer": true
  }
}

system/sources/CAST

http://192.168.XX.XX/smoip/system/sources/CAST

{
  "data": {
    "cast_setup_state": "enabled",
    "tutorials_seen": true,
    "share_usage_data": true
  }
}

system/update

http://192.168.XX.XX/smoip/system/update

{
  "data": {
    "early_update": false,
    "update_available": false,
    "updating": false
  }
}

queue/info

http://192.168.XX.XX/smoip/queue/info

{
  "data": {
    "total": 0,
    "id_array_token": 0
  }
}

Control

zone/play_control

http://192.168.XX.XX/smoip/zone/play_control

@Query("zone") String default: ZONE1
@Query("action") play, stop, toggle, pause, skip, seek, shuffle, repeat

http://192.168.XX.XX/smoip/stream/radio?zone=ZONE1&action=pause ### stream/radio

http://192.168.XX.XX/smoip/stream/radio

@Query("zone") String default: ZONE1
@Query("name") String 
@Query("url") String

http://192.168.XX.XX/smoip/stream/radio?zone=ZONE1&name=Netil&url=https://netilradio.out.airtime.pro/netilradio_a

No Json response on success, just a 200.

zone/state

http://192.168.XX.XX/smoip/zone/state

@Query("zone") String
@Query("volume_percent_change") Int

http://192.168.XX.XX/smoip/zone/state

@Query("zone") String
@Query("volume_step_change") Int

KotlinScript CLI

See Kotlin script for setup notes, filename is cacli.main.kts - the .main is important, cacli is Cambridge Audio CLI. You need your device IP address. eg. ./cacli.main.kts 192.168.XX.XX toggle to toggle streaming radio from play <> pause.

#!/usr/bin/env kotlin  
  
import java.net.URL  
import java.net.HttpURLConnection  
  
fun String.get(): String? {  
   val url = this  
   with(URL(url).openConnection() as HttpURLConnection) {  
      log("GET $url")  
      log("Response Code: $responseCode")  
      val sb = StringBuilder()  
      inputStream.bufferedReader().use {  
         it.lines().forEach { line ->  
            sb.append("$line\n")  
         }  
      }  
      return sb.toString()  
   }  
}  
  
fun info(ip: String){  
   log("info()")  
   "http://$ip/smoip/system/info".get()  
}  
  
fun pause(ip: String){  
   log("pause()")  
   "http://$ip/smoip/zone/play_control?zone=ZONE1&action=pause".get()  
}  
  
fun play(ip: String, streamUrl: String?){  
   log("play()")  
   "http://$ip/smoip/stream/radio?zone=ZONE1&name=Radio&url=$streamUrl".get()  
}  
  
fun stop(ip: String){  
   log("stop()")  
   "http://$ip/smoip/zone/play_control?zone=ZONE1&action=stop".get()  
}  
  
fun toggle(ip: String){  
   log("stop()")  
   "http://$ip/smoip/zone/play_control?zone=ZONE1&action=toggle".get()  
}  
  
fun log(message: String){  
   println(message)  
}  
  
log("\nCambridge Audio Streamer CLI\n")  
  
if(args.isNotEmpty()) {  
   when (args.size) {  
      1 -> info(args[0])  
      else -> when (args[1]) {  
         "stop" -> stop(args[0])  
         "pause" -> pause(args[0])  
         "toggle" -> toggle(args[0])  
         "play" -> when {  
            args.size < 3 -> log("Play requires a stream url: ./cacli.main.kts ipAddress play streamUrl")  
            else -> play(args[0], args[2])  
         }  
      }  
   }  
}else{  
   log("No commands")  
}