## 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](../computers/Kotlin%20script.md) 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")
}
```