I am fortunate enough to own Devialet Phantom speakers. These are crazy powerful speakers in a small size. I use a pair with my TV, which has an AppleTV and a gaming PC attached. The speakers support AirPlay 2, as well as an optical digital line in (it supports other platforms, but I don’t use them). So when I am using my AppleTV, I use Airplay (which will automatically set the speaker to the correct setting, but when I switch to the gaming PC, I have to pull out my phone, and switch to the line in, and set the volume where I want it, which to a lazy engineer is just too much hassle.
So, like any good engineer, I decided that I would work really hard to not have to work. So I went out and found the API for the Devialet products. It is a good starting point. However, like most technical documentation, it is written with base assumptions that you know a lot more than you probably do. And they give incomplete, or flat out incorrect examples. So after poking and prodding at my speaker for a while, I started to get the hang of it. My approach is to use standard shell and command line tools vs the dependency hell of something like python or perl. The curl tool will do everything you need in one line for many functions, and two for most others. It is also already installed on Macs, and most linux distros by default. I also use the jq command to parse the javascript output from the speaker (installed by default on the Mac, available on most linux distros).
The speakers talk via HTTP GET and POST commands (not HTTPS it should be noted). In fact, there is no security facility of any kind, so be aware if you have them on your network anyone with basic access to your network can control your speaker. And since these speakers can get loud enough to cause real harm and pain, it is something to keep in mind.
The first thing you will need to do is find the speakers on your network. They do use mDNS, so it is not difficult. Rather than go into a lengthy explanation, I’ll just give working examples.
This one is for the Mac, that uses the dns-sd tool as its default command line mDNS browser. This is a v1 ugly hack that happens to work for my specific use. However if Devialet changes their naming, or if dns-sd output formatting changes, this will likely blow up. I will clean it up and put some polish on it as I work out a more comprehensive tool. For now, I just want to get the methods down before I forget what I did. And, it might be useful for other people.
# Search for Devialet speakers on the network
# Possible device/hostnames
DEVIALET_DEVICES="PhantomIDarkChrome|PhantomII98dB"
# This is UGLY and depends on teh dns-sd command putting out rigid format. Works for now.
# REALLY needs to be polished.
# Loop through all devices that respond as an HTTP server
dns-sd -m -B _http._tcp | grep "local. _http._tcp." | cut -b 74- | sort | uniq | while read DEVICE
do
# Look for devices that are in the DEVIALET_DEVICES variable
dns-sd -m -L "${DEVICE}" _http._tcp. | grep "can be reached" | awk '{ print $7; }' | sort | uniq | egrep "${DEVIALET_DEVICES}" | sed -e "s/.:80$//"
done
On linux, avahi-browse would be the tool to use. I have not (yet) written an equivalent tool for that.
It returns the list of hostnames that the devices broadcast in the .local domain, so should resolve regardless of your DNS server settings.
Now that you have the hostnames, you will want to know information about your speaker:
HOST=$1
curl -s -H 'Content-Type:' -X GET -d '{}' -g -6 "http://${HOST}:80/ipcontrol/v1/systems/current" | jq
This outputs JSON information about the speaker (or speaker pair if you have a stereo pair set up).
To get the current volume of the speaker (pair):
HOST=$1
curl -s -H 'Content-Type:' -X GET -d '{}' -g -6 "http://${HOST}:80/ipcontrol/v1/groups/current/sources/current/soundControl/volume" | jq .volume
To set the volume to your desired setting 0-100 percent (be careful with this one); 50 is fairly safe:
HOST=$1
curl -s -H 'Content-Type: application/json' -X POST -d '{ "volume": 50 }' -g "http://${HOST}:80/ipcontrol/v1/systems/current/sources/current/soundControl/volume" | jq
You will notice that the volume is set in the -d block (i.e. the data). This has to be valid JSON formatted, and the quotes around “volume” are significant (they are not shown in the documentation everywhere).
To pause the playback of the current source (if applicable; line-in will just mute):
HOST=$1
curl -s -H 'Content-Type:' -X POST -d '{}' -g -6 "http://${HOST}:80/ipcontrol/v1/groups/current/sources/current/playback/pause" | jq
To (re) start playing the current track:
HOST=$1
curl -s -H 'Content-Type:' -X POST -d '{}' -g -6 "http://${HOST}:80/ipcontrol/v1/groups/current/sources/current/playback/play" | jq
To change the input of the speaker is a little more involved, and this is where their documentation really causes confusion. On the Phantom II, the line-in is called “opticaljack”, and they mention using that as the source name several times. However when I used it, nothing happened. The trick is, apparently, to use the UUID called “sourceID” that belongs to the line in port. So a little jq magic can pull out the correct one. In a stereo pair, there will be two of the ports listed. This picks the one with an active connection, and switches to it. At the moment, I have no checks for when both ports have an active source. When I do more testing, I’ll update the script accordingly. If you are referring to a Phantom I or an Arch device, you would just need to tweak the JSON parsing accordingly. The basic structure should be the same, just the names will change to protect the innocent.
#!/bin/bash
HOST=$1
# Here we have to get the line in (a.k.a. "opticaljack") of the Phantom II. The documentation
# is pretty bad, and keeps telling you to refer to it as "opticaljack" when, in fact, they mean
# the source ID of the opticaljack; i.e. a UUID
# This grabs the available inputs, and uses jq to parse out the JSON to get the opticaljack of
# the one that is active; I assume that "streamLockAvailable" means that it detects a source;
# since both jacks are listed in the available sources (i.e. the left and right speaker's jacks)
OPTICALIN=$( curl -s -H 'Content-Type:' -X GET -d '{}' -g http://${HOST}:80/ipcontrol/v1/groups/current/sources/ | jq '.sources[] | select (.type=="opticaljack" and .streamLockAvailable==true) | .sourceId' | tr -d '"' )
if [ "$OPTICALIN" ]
then
curl -s -H 'Content-Type:' -X POST -d '{}' -g "http://${HOST}:80/ipcontrol/v1/groups/current/sources/${OPTICALIN}/playback/play" | jq
else
echo "No line in jack found on ${HOST}."
fi
So to bring it home, I will show the script that I ended up using for my need in switching to the optical input coming from my computer, and also set the volume to the setting I want, instead of the firmware default of 35 (which cannot be changed) that it goes to any time the input is changed. I put this into a MacOS shortcut along with the Homekit setting to change the input of my TV, and voilĂ , my gaming PC appears with the speakers setup and ready to go.
#!/bin/bash
HOST=PhantomII98dB-XXXXXXXXXXXXX.local
# Here we have to get the line in (a.k.a. "opticaljack") of the Phantom II. The documentation
# is pretty bad, and keeps telling you to refer to it as "opticaljack" when, in fact, they mean
# the source ID of the opticaljack; i.e. a UUID
# This grabs the available inputs, and uses jq to parse out the JSON to get the opticaljack of
# the one that is active; I assume that "streamLockAvailable" means that it detects a source;
# since both jacks are listed in the available sources (i.e. the left and right speaker's jacks)
OPTICALIN=$( curl -s -H 'Content-Type:' -X GET -d '{}' -g http://${HOST}:80/ipcontrol/v1/groups/current/sources/ | jq '.sources[] | select (.type=="opticaljack" and .streamLockAvailable==true) | .sourceId' | tr -d '"' )
if [ "$OPTICALIN" ]
then
curl -s -H 'Content-Type:' -X POST -d '{}' -g "http://${HOST}:80/ipcontrol/v1/groups/current/sources/${OPTICALIN}/playback/play" | jq
else
echo "No line in jack found on ${HOST}."
fi
curl -s -H 'Content-Type: application/json' -X POST -d '{ "volume": 60 }' -g "http://${HOST}:80/ipcontrol/v1/systems/current/sources/current/soundControl/volume" | jq
There are other commands that can be sent to the speakers, but these examples should at least give you the structure of how to send them. If I expand the logic into a tool I will, of course, post it here.