This page last changed on Dec 15, 2013 by rich.sperrin.

I have a pair of these Philips speakers which sound wonderful and even come with a suitable app for connecting to music on my network, internet radio and a raft of source types. The only thing it doesn't do is allow me to integrate with OpenRemote. As far as I understand I would need an API or a developers pack to achieve this which Philips don't do.

Is there another way of detecting what commands the speakers are responding to? For instance, I have iLearner for the iTach IP2IR - it would be great if I could run some other software that detect commands by listening on the relevant IP address.

I've emailed Philips on the subject but not expecting too much from them on the software side of things.


CurrentVolumeCommand.png (image/png)
CurrentVolumeCommand.jpg (image/jpeg)
VolumeSensor.jpg (image/jpeg)
SetVolume.jpg (image/jpeg)
VolumeSlider.jpg (image/jpeg)
VolumeControl.jpg (image/jpeg)
VolumeControl2.jpg (image/jpeg)
IMG_20140209_154956.jpg (image/jpeg)
OpenRemoteRadio.png (image/png)

When the vendor doesn't give you the details there isn't much you can do but to attempt to reverse engineer their protocol. If you're not already familiar with it, Wireshark is a very popular tool to monitor network traffic. It takes a little learning to get started but there's plenty of resources and help available on the Internet, it being very widely used tool.

Posted by juha at Dec 16, 2013 06:40

Recieved polite reply from Philips....

Dear Mr Sperrin,

I am sorry but we are unable to provide you with the information you asked for. It is patented trademark and we do not share it with our customers.

Ultimately as we are constantly working towards improving our customer service experience, you may receive a short survey about your customer experience pertaining to my specific response. I would greatly appreciate your thoughts and feedback as I am always looking to improve the service that I provide to my customers.

Kind regards,
Michael Miller

Philips Customer Care

Posted by rich.sperrin at Dec 16, 2013 19:30

I gave Wireshark a try - crikey! I'll have to give it another go when I'm feeling brave. It seemed to attack my poor old desktop and the HD was struggling to cope. I'll think about putting it on my fast lappy but a little concerned about what Wirshark actually does. I get the impression I can filter by the IP of the speakers which should reduce the flood of data. I'm guessing streaming data to the speakers is a bad idea whilst monitoring!!

Posted by rich.sperrin at Dec 16, 2013 19:41

Sadly, some companies never learn...

Posted by juha at Dec 17, 2013 08:06

This is more of a blog style update more than anything today - helps me remember where I'm at but any thoughts and ideas welcome.

Wireshark is up and running on my laptop and capturing 100% of LAN traffic. The secret is going to be how to open a windows7 streaming ("play to") window without actually streaming music. When it is streaming an MP3 there are 80k packets per minute so wont stand a chance of filtering out any pause/play/vol+- commands. I'm hoping I can do something similar with a different app called Philips Media Manager where I dont have to stream any data. Meanwhile there are a couple of interesting comments on the Philips sound AWxxxx forum about IP commands and URL calls.

Posted by rich.sperrin at Dec 24, 2013 13:31

I'm not getting anywhere with wireshark - facinating subject but think i'm out of my depth

However the controls for Philips AW9000 speakers are encapsulated in their app called AirStudio and when I run this in IE I can view the source code. I'm thinking I could use the source code in my own set of OpenRemote commands - is this a path worth persuing?

Here is an example for the code from the volume control page....

PS apologies for macro messages - something to do with '{' ???


<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' ''>
<html xmlns='' xml:lang='en' lang='en'>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<meta name="viewport" content="initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<meta name='apple-mobile-web-app-capable' content='yes' />
<meta http-equiv="expires" content="Fri, 31 Dec 2021 18:18:18 GMT" />
<meta http-equiv="cache-control" content="max-age=86400, must-revalidate" />
<meta http-equiv="last-modified" content="Tue, 13 Jan 2012 13:58:59 GMT" />
<script src="res/jquery.min.js"></script>
<link rel="stylesheet" href="res/WKSlider.css"/>
<script type="text/javascript" src="res/WKSlider.js"></script>
<link href="res/now_playing.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="res/iscroll.min.js"></script>
<script src='res/jquery.alerts.js'></script>
<link href='res/jquery.alerts.css' rel='stylesheet' type='text/css' />
<script language='javascript'>
$(document).bind('touchstart', function preventZoom(e) {
var t2 = e.timeStamp;
var t1 = $(document).data('lastTouch') || t2;
var dt = t2 - t1;
var fingers = e.originalEvent.touches.length;
$(document).data('lastTouch', t2);
if (!dt || dt > 500 || fingers > 1)

Unknown macro: { return;}


<script type="text/javascript">
var isWaitforResponse = 0;
var progressSelected = 0 ;
http = new XMLHttpRequest();
function timedCount()
{"GET", "/ELAPSE", true);
http.onreadystatechange=function() {
if(http.readyState == 4) {
if( http.responseText != "")
var obj= eval('(' + http.responseText + ')');

if(obj.command == 'HOME' ||obj.command == 'STOP' || obj.command == 'NOWPLAY')

Unknown macro: { clearInterval(t); CalculateAmountOnClick(); window.location = '/nowplay'; }

else if(obj.command == 'ELAPSE'){
if(obj.mute != playItem.muteStatus)
if(obj.mute == 0)
Unknown macro: { document.getElementById('id-img-mute').src = 'res/Btn_Mute.png'; playItem.muteStatus = 0;}

else if(obj.mute == 1)
Unknown macro: { document.getElementById('id-img-mute').src = 'res/Btn_Mute_on.png'; playItem.muteStatus = 1;}

if(obj.volume != playItem.volume){
Unknown macro: { gVolumebarChanged=0;}
Unknown macro: { playItem.volume = obj.volume; changepos_X(obj.volume);}


function begin()

Unknown macro: { progressSelected = 1; }

var myScroll;
function loaded() {
myScroll = new iScroll('album_art_table',

Unknown macro: {desktopCompatibility}
if(!document.addEventListener) {
document.attachEvent('touchmove,', function (e)
Unknown macro: {e.preventDefault();}
, false);
document.attachEvent('DOMContentLoaded',function(){ setTimeout(function()
Unknown macro: { loaded(); }

else {
document.addEventListener('touchmove', function (e)

Unknown macro: { e.preventDefault(); }
document.addEventListener('DOMContentLoaded',function(){ setTimeout(function()

function animatePG()
if(progressSelected == 0)
Unknown macro: { marginTop}
progressSelected = 0;
<title>Play screen</title>
<script type='text/javascript'>
var playItem =
Unknown macro: { 'defaultAlbum'}
; var previousPage =
Unknown macro: {'url'}

function gotoDevice()

Unknown macro: {clearInterval(t); window.location = 'philips}

function gotoHome()
Unknown macro: { gotoPage('/index'); }

function gotoNowPlaying(){}

function gotoSettings()

Unknown macro: { gotoPage('/settings$01$01$01$0'); }

function goback()
Unknown macro: { gotoPage(previousPage.url); }
function toggleMute(){
Unknown macro: { document.getElementById('id-img-mute').src = 'res/Btn_Mute.png'; playItem.muteStatus = 0; submitPost('nowplay','/VOLUME$UNMUTE'); }

Unknown macro: { document.getElementById('id-img-mute').src = 'res/Btn_Mute_on.png'; playItem.muteStatus = 1; submitPost('nowplay','/VOLUME$MUTE'); }


function goHome()

Unknown macro: { gotoPage('/index'); }
function CalculateAmountOnClick () {
var curtain = document.body.appendChild( document.createElement('div') ); = 'curtain'; = window.outerHeight + 'px';
curtain.onkeypress = curtain.onclick = function()
Unknown macro: { return false; }


function submitPost(url,val)
var xmlhttp;

Unknown macro: { xmlhttp = new XMLHttpRequest(); }

Unknown macro: { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); }

if (xmlhttp.readyState==4 && xmlhttp.status==200)


function Right(str, n){
if (n <= 0)
return "";
else if (n > String(str).length)
return str;

Unknown macro: { var iLen = String(str).length; return String(str).substring(iLen, iLen - n); }

}function gotoPage(url)
Unknown macro: { clearInterval(t); CalculateAmountOnClick(); checkAndClearPrevRequest(); window.location = url; }

function checkAndClearPrevRequest(){
if(http.readyState != 4 && http.readyState != 0)

Unknown macro: { http.abort(); //sleep(500); }

</script> <style type='text/css' media='all'>

float: Please specify which side the content will be floating on (left or right).

Unknown macro: {border-right}


<div id="outer_panel" >
<div id="top_panel" style="border-bottom:1px solid #4d4d4d;">
<table width="100%" height="100%" border="0" cellspacing="0" cellpadding="0">
<td width="20%" align='left' style="padding-left:6px;"></td>
<td width="60%"><div align="center">Aux-in</div></td>
<td width="20%" align='right' style="padding-right:6px;">
<div id="middle_panel_aux">
<table id="album_art_table" border="0" cellspacing="0" cellpadding="0" width="100%%">
<tr align="center">
<script type='text/javascript'>
if(playItem.defaultAlbum == 0)
document.write('<td><img src="'playItem.albumArt'"/></td>');
document.write('<td><img src="'playItem.defaultAlbumArt'"/></td>');
<div id='bottom_panel_1'>
<table width="100%"align="center"><tr><td width="15%" align='right'>
<script type='text/javascript'>
if(playItem.muteStatus == 0)
document.write('<img id="id-img-mute" width="40px" height="32px" onclick="toggleMute()" src="res/Btn_Mute.png"/>');
document.write('<img id="id-img-mute" width="40px" height="32px" onclick="toggleMute()" src="res/Btn_Mute_on.png"/>');
<td width="70%" align='center'>
<div id="slidercontainer">
<div id="mySlider1" class="slider">
<div class="track"></div>
<div class="knob" role="slider" aria-valuemin="0" aria-valuemax="100"></div>
</td><td width="15%" align='left'><img src='res/Button_EQ.png' height=32px width=40px onclick="gotoPage('/settings$15$02$01$0');"></td></tr></table><br><br>
<table id="table_new" height='100%%' width="100%%" style="border-top:1px solid #4d4d4d;">
<tr align='center'><td id='td_2' class='bottom_td' onclick='javascript:gotoDevice()'><div><img src="res/Tab_Home.png" height=48px width=72px style="-o-transform: scale(1);-moz-transform: scale(1);padding-right:4px;" /></div></td>
<td id='td_3' class='bottom_td' onclick='javascript:gotoNowPlaying()'><div><img src="res/Tab_NowPlaying_hl.png" height=48px width=72px style="-o-transform: scale(1);-moz-transform: scale(1);padding-right:4px;" /></div></td>
<script type='text/javascript'>
var perc = (playItem.volume*100)/playItem.maxvolume;
var volSlider = new WKSlider('mySlider1',perc);
var gVolumebarChanged=0;
volSlider.callback = function (percentage)

Unknown macro: { var vol = Math.round((playItem.maxvolume * percentage)/100); playItem.volume = vol; gVolumebarChanged = 1; submitPost('nowplay','/VOLUME$VAL$'+vol); }

function changepos_X(vol)

Unknown macro: { var perc = (vol*100)/playItem.maxvolume; volSlider.changePos(perc); }



Posted by rich.sperrin at Jan 18, 2014 18:48

Looking at the source code might indeed be the easiest way to find out the URLs used and their parameters.
You could then document the protocol and use HTTP commands in OR to control your device.

Posted by ebariaux at Jan 20, 2014 08:02

Hi Eric

The home page for speakers is

Then using firefox dev tools it exposes subsequent context commands. So for instance:- ; this connects speakers to auxillary and shows volume control ( where TV is plugged in)$15$02$01$0 ; from volume control this shows equaliser (although cant do much in there)$VAL$35 ; this acts a bit differently and successfully changes the volume but just returns a command success message

So the question is how to use these in OR. I'm guessing the AUX command is straightforward HTTP command?

Volume: Can I build a slider that will generate the URLs for the volume? I see the slider requires a sensor but would rather it uses the min/max index to send the URL - is this possible?

Feels like progress!

Posted by rich.sperrin at Feb 04, 2014 21:45

Aux would be straightforward HTTP command that would do the "source switching" and you would just ignore the return value.

For volume, not sure what the question is?

You can use a slider and use ${param} instead of 35 in your example URL to use the slider value as the volume value.
The slider does require a sensor so that it can update itself based on the value change, e.g. if volume is change from outside of OR. So you will need an HTTP command that can query the volume.

Not sure what you mean with min/max index ?

Posted by ebariaux at Feb 07, 2014 13:48

The following returns volume info:-

and returns the result:-


how would I capture the volume value for the sensor? It looks like JSon but havent figured the syntax. Currently have tried using JSONPath Expressions:-


and getting:-

INFO 2014-02-08 18:27:03,024 (HTTP): received message: {'command':'ELAPSE',
ERROR 2014-02-08 18:27:03,024 (HTTP): Could not perform jsonpath evaluation
com.jayway.jsonpath.InvalidPathException: invalid path
at com.jayway.jsonpath.internal.filter.FieldFilter.filter(

(Also, by min/max i meant was there a way i could set a min and max value for the slider control and use the sensor position to set the volume. but as you point out its important to use a sensor to read the volume as well as set it.)

Posted by rich.sperrin at Feb 08, 2014 15:01

Had a fresh look this morning - must be case sensitive?

I used $.volume - sorted

Also here are HTTP commands and sensors figured (so far) for Philips AWxxxx range of Fidelio Wireless Speakers:-






Connect to Auxillary

Radio Mode:-

Play Radio Presets (1 to 5)

Play Radio Faves (1 to n)
Same as Presets except from different context:-
This takes you to Radio Favourites:-
This takes you to Radio Presets:-

NB Favourites is better because Presets is limited to 5
whereas Favourites is unlimited (so far)


For example to play Radio Favourite No 5 create a macro that runs a sequence of commands from a known starting point:-
Startscreen - GET,
Delay 1000ms (this gives speakers time to respond)
Internet Radio - GET,$03$01$001$0
Delay 1000ms
Radio Favourites - GET,$03$02$002$0
Delay 1000ms
Radio Index 5 - GET,$03$03$005$1
(note use of index rather than specifically Radio Favourite as this is the same command as Preset except in Favourites context and not Presets context)



'value':66, (tbc)
'mute':0, (mute boolean)
'shuffle':0, (shuffle boolean)
'repeat':0, (repeat boolean
'play':1, (tbc)
'volume':30, (volume 0-64)
'TotalTime':0, (tbc)
'favstatus':0} (tbc)

VOLUME SENSOR (case sensitive):-


'value':0, (1=standby, 0=on)


I've now got a working radio player on OpenRemote that looks [somewhat] like this - very pleased (all very fine radio stations!)

Posted by rich.sperrin at Feb 09, 2014 12:08

Ok - so my Speaker volume control is essentially working albeit a little unpredictable.

Sensor and Slider is looking good - I can change the volume from a browser after which volume label and volume slider update shortly after using the 2 second interval - happy with that.

The problem seems to be the slider control command. Whichever position I set it to the results always seem to be somewhere between 0-9. I think I've set it up correctly:-

I started with a command to grab the current volume;

Then created a Slider Sensor;

I tested this with a label that successfully returns the current volume when changed externally.

So next I created a command to set the volume;

and create a new Slider Command;

and topped it off with a Slider Control;

I think I've got everything in place OK.

Anybody spot a any errors? I think the next step is to figure what the controller is sending - heres the HTTP log with the last 4 entries (1 set & 3 gets) :-

INFO 2014-02-09 13:24:46,992 (HTTP): received message: {'command':'ELAPSE',
INFO 2014-02-09 13:24:49,644 (HTTP): received message: {'command':'ELAPSE',
INFO 2014-02-09 13:24:50,034 (HTTP): received message: {'command':'SUCCESS',
INFO 2014-02-09 13:24:51,703 (HTTP): received message: {'command':'ELAPSE',
INFO 2014-02-09 13:24:53,871 (HTTP): received message: {'command':'ELAPSE',

And here is the DEV log following a volume change:-

2014-02-09 13:29:22,956 TRACE [Polling thread for sensor: VolumeSensor]: Processed '7', received '7'
2014-02-09 13:29:24,267 DEBUG [HTTP-Thread-30]: Building HttGetCommand
2014-02-09 13:29:24,267 DEBUG [HTTP-Thread-30]: HttpGetCommand: method = GET
2014-02-09 13:29:24,267 DEBUG [HTTP-Thread-30]: HttpGetCommand: url =$VAL28
2014-02-09 13:29:24,298 INFO [HTTP-Thread-30]: received message: {'command':'SUCCESS',
2014-02-09 13:29:25,093 INFO [Polling thread for sensor: VolumeSensor]: received message: {'command':'ELAPSE',
2014-02-09 13:29:25,093 TRACE [Polling thread for sensor: VolumeSensor]: Processed '8', received '8'
2014-02-09 13:29:25,093 INFO [HTTP-Thread-49]: Had waited the skipped sensor ids of statuses in ChangedStatusRecord:861229014155033-[1196] sensorID:[1196] statusChangedSensorID:[1196]
2014-02-09 13:29:25,093 INFO [HTTP-Thread-49]: Querying changed data from StatusCache...
2014-02-09 13:29:25,093 INFO [HTTP-Thread-49]: Have queried changed data from StatusCache.
2014-02-09 13:29:25,093 INFO [HTTP-Thread-49]: Return the polling status.
2014-02-09 13:29:25,093 INFO [HTTP-Thread-49]: Finished polling at 2014-02-09 13:29:25

2014-02-09 13:29:25,515 INFO [HTTP-Thread-49]: Querying changed state from ChangedStatus table...
2014-02-09 13:29:25,515 INFO [HTTP-Thread-49]: Found: [device => 861229014155033, sensorIDs => [1196]] in ChangedStatus table.
2014-02-09 13:29:25,515 INFO [HTTP-Thread-49]: ChangedStatusRecord:861229014155033-[1196] sensorID:[1196] statusChangedSensorID:[]Waiting...
2014-02-09 13:29:27,199 INFO [Polling thread for sensor: VolumeSensor]: received message: {'command':'ELAPSE',
2014-02-09 13:29:27,199 TRACE [Polling thread for sensor: VolumeSensor]: Processed '8', received '8'

The error looks consistant; When I 'click' the middle of the slider I always get a value of 3 to 5 depending on how close I am to the centre - just below produces 3, slightly highter produces 4, bit higher produces 5 - almost as if it is only processing the left most value of 33,34,35. Thoughts?

I tried using $$.volume in the JSON parse command on the basis that it represents 2 chars but no luck.

Is $ the correct symbol for retrieving an integer?

Not a solution but demonstrates that everything works apart from the step between sending the new volume value and getting it back - the following uses buttons to set the volume and then successfully updates the sensor and slider:-

Ok - got something here; I've set the command/param to send;$VAL$\{param}

In the log it looks like this;$VAL28

I'm losing the '$' symbol which is problem.

Whew - Got there! The param requires packing and requires '$$' for the param - it was only passing one character. Now looks like this and works; \$VAL$$\{param}


Posted by rich.sperrin at Feb 09, 2014 13:22
Document generated by Confluence on Jun 05, 2016 09:34