Introduction
This article is intended to share some code on how to control a Logitech SqueezeBox Server (SBS) by using C#. Currently only some functionality of the SqueezeBox server is implemented, but other functions will be added in the future. For now, it should give you a good idea how to control and retrieve information from SBS. A sample application is included which shows all the albums and the current playlist on the selected player.
Background
I have a Squeezebox Duet which I use quite frequently. Although the controller is pretty nice for selecting an album or other song, creating playlists is not what it should be in my opinion. You could use the webserver of SBS to create playlists, but like the controller I'm simply not too crazy about it. An idea was born...
How to Control the SqueezeBox Server
SBS offers two methods of controlling the server. It provides a command line interface (CLI) which uses a telnet interface, pretty simple, but all plain text. Another way to control SBS is to send commands to the integrated web server. Commands and response are Json formatted data. A complete list of commands can be found on your SBS: Local SBS Documentation (assuming SBS is installed on your local system).
Sequence to Send, Retrieve and Deserialize Commands
In order to retrieve information from the server, we need to create a valid CLI command, send it to the server, retrieve and deserialize the response and inform the caller of the result. The following code is all it takes to do this, further I will explain what the code exactly does.
public event System.EventHandler<GetServerStatusCompletedEventArgs>
GetServerStatusCompleted;
public virtual void GetServerStatusAsync()
{
Execute<ServerStatus>(SqueezeMessage.CreateMessage(SqueezeCommand.ServerStatus));
}
The following classes are used:
Formatted Messages
All commands should be JSON data which are posted to the JsonRpc
interface of the server. I use Json.NET to serialize and deserialize commands and responses. Json.NET is a great implementation which is very easy to use. A big thanks to James Newton-King for providing this library free of charge.
To give you an idea of what exactly is posted to the server, this is the message to retrieve the first 50 albums:
{"id":1,"method":"slim.request","params":[ "", ["albums", "0","50" ]]}
The first part of the message is the same for each message, after the "params
", it gets a bit more interesting. First provide the ID of the player (if applicable) the command with parameters. In the example above, the player ID is not required so it is omitted. The command itself is pretty straightforward, the albums command and a skip and take parameter.
You don't have to construct these messages yourself, a SqueezeMessage
helper class is included to do this for you. The following properties will be serialized to create a valid CLI command for the server.
[JsonProperty(PropertyName = "id")]
private int ID { get; set; }
[JsonProperty(PropertyName = "method")]
private string Method { get; set; }
[JsonProperty(PropertyName = "params")]
private object[] Params { get; set; }
To serialize the command, use the Json.NET library:
public static string CreateMessage(string command)
{
return JsonConvert.SerializeObject(new SqueezeMessage(new object[2]
{ null, new List<string>(1) { command } }));
}
Send the Command to the Server
Now we have a valid command, we have to send it to the server. I use a HttpHelper
class to post the data and retrieve the response asynchronously. I assume you already know how to use the HttpWebRequest
, so I will not go into detail about how to get the data from a web server. The following code creates the HttpHelper
, sends the command and finally uses an anonymous delegate to deserialize the response. Because all classes derive from SqueezeBase
, we can use a generic method for all commands.
public void BeginSendCommandAsync<T>(string username, string password,
string url, string jsonText)
where T : SqueezeBase
{
HttpHelper helper = new HttpHelper(new Uri(url), username,
password, "POST", "application/json", jsonText);
SetErrorHandler(helper);
helper.ResponseComplete += (e) =>
{
T t = Deserialize<T>(e.Response);
if (ResponseComplete != null)
{
ResponseComplete(new JsonResponseCompleteEventArgs(t));
}
};
helper.ExecuteAsync();
}
}
SqueezeBox Repository
The SqueezeBox
repository class is the main class which is used to communicate with SBS. Each async command (e.g. retrieve a list of albums, get serverstatus, get a list of connected players) has a corresponding event to inform the caller of the requested information. I really like 'convention over configuration' so I will not call the event directly but I use reflection to get the event, create the eventargs and fire it up. This keeps the code clean and adding new commands takes only a couple of lines of code.
As mentioned above, the only code necessary to get the status of the server is this:
public event System.EventHandler<GetServerStatusCompletedEventArgs>
GetServerStatusCompleted;
public virtual void GetServerStatusAsync()
{
Execute<ServerStatus>(SqueezeMessage.CreateMessage(SqueezeCommand.ServerStatus));
}
So what exactly will happen? The JsonMessage.CreateMessage
creates the json serialize command and passes it to the Execute
method. Let's see what the Execute
method exactly does.
protected virtual void Execute<T>(string jsonCommand)
where T : SqueezeBase
{
string caller = GetCallerName();
JsonHelper jsonHelper = GetJsonHelper();
jsonHelper.ResponseComplete += (e) =>
{
FireEvent(caller.Replace("Async", "Completed"), e.Response);
};
jsonHelper.BeginSendCommandAsync<T>(Username, Password,
SqueezeConfig.RemoteUrlJson, jsonCommand);
}
The execute
method first determines the name of the caller. We need this name to determine the name of the corresponding event. By convention, each Get<name>Async
method has a corresponding Get<name>Completed
event. So the GetServerStatusAsync
method has a GetServerStatusCompleted
event. You could write all the code to fire the event yourself, but I prefer this because it keeps the codebase very small.
After the response is received, it is passed to the FireEvent
method which will get the event and execute it.
protected virtual void FireEvent(string eventName, object param)
{
FieldInfo eventInfo = this.GetType().GetField
(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
if (eventInfo != null)
{
var event_member = eventInfo.GetValue(this);
if (event_member != null)
{
EventArgs eventArgs;
var type = event_member.GetType().GetMethod("Invoke").GetParameters()[1].ParameterType;
eventArgs = (EventArgs)Activator.CreateInstance(type, param);
event_member.GetType().GetMethod("Invoke").Invoke
(event_member, new object[] { this, eventArgs });
}
}
}
First use reflection to get the EventInfo
, then determine which EventArgs
the event has and create an instance using the deserialized response.
Using the SqueezeBox Repository
Now all work is done, we can actually use the code in a sample application.
In order to do this, we first have to set the configuration of the squeezebox server before we can use it. Then make sure all events you want to use are captured. Also remember all calls are asynchronous, so you cannot update the UI directly. Instead invoke the UI update on the UI thread.
SqueezeConfig.SetConfig("localhost", 9000);
repository = new SqueezeRepository();
repository.OnError += new EventHandler<SqueezeErrorEventArgs>(repository_OnError);
repository.GetServerStatusCompleted +=
new EventHandler<GetServerStatusCompletedEventArgs>(repository_GetServerStatusCompleted);
repository.GetServerStatusAsync();
What's Next
This first article should give you a good understanding of how to control and retrieve information from a SqueezeBox Server. Depending on the reactions I get, I will update this library so it will eventually supports all commands which are available through the CLI interface. I'm currently working on an open source Windows Phone 7 Squeezebox Controller and WPF SqueezeBox Controller which will also be posted on CodeProject.
I hope you find this article useful. Please leave comments if you think I could improve it.
History
- 2010-11-05 Initial version