Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Controlling a SqueezeBox Server using C# (VS2010) - Part 1

5.00/5 (14 votes)
6 Nov 2010CPOL5 min read 79K   1.5K  
This article describes how you can asynchronously control your SqueezeBox server

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.

SqueezeControl.jpg

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.

C#
public event System.EventHandler<GetServerStatusCompletedEventArgs> 
	GetServerStatusCompleted;
public virtual void GetServerStatusAsync()
{
Execute<ServerStatus>(SqueezeMessage.CreateMessage(SqueezeCommand.ServerStatus));
}

The following classes are used:

SqueezeControlClasses.jpg

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:

C#
{"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.

C#
[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:

C#
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.

C#
/// <summary>
/// Sends the requested data to the server, 
/// deserializes received data into the specified type
/// </summary>
/// <param name="username">Username if authentication is enabled</param>
/// <param name="password">Password if authentication is enabled</param>
/// <param name="url">Location of the server</param>
/// <param name="jsonText">Serialized message which will be sent to the server</param>
public void BeginSendCommandAsync<T>(string username, string password, 
	string url, string jsonText)
where T : SqueezeBase
{
// Create a new HttpHelper which is used to post and retrieve data 
// from the SqueezeBox Web server
HttpHelper helper = new HttpHelper(new Uri(url), username, 
			password, "POST", "application/json", jsonText);
SetErrorHandler(helper);
helper.ResponseComplete += (e) =>
{
// The requested data is deserialized into the requested type
T t = Deserialize<T>(e.Response);
// And inform anyone who is listening....
if (ResponseComplete != null)
{
ResponseComplete(new JsonResponseCompleteEventArgs(t));
}
};
// Start communication async
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:

C#
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.

C#
/// <summary>
/// Execute the JsonCommand
/// </summary>
/// <typeparam name="T">Return type of the command</typeparam>
/// <param name="jsonCommand">string representation of the JsonCommand</param>
protected virtual void Execute<T>(string jsonCommand)
where T : SqueezeBase
{
// Use reflection the get the name of the caller. 
// This name is used to determine the name of the corresponding EventHandler
string caller = GetCallerName();
JsonHelper jsonHelper = GetJsonHelper();
jsonHelper.ResponseComplete += (e) =>
{
// Convention over Configuration: each "Get<Name>Async" 
// command has a corresponding "Get<Name>Completed" EventHandler 
FireEvent(caller.Replace("Async", "Completed"), e.Response);
};
// Executes the Async command
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.

C#
/// <summary>
/// Fire the specified event and inject the parameters using reflection
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="param">EventArgs of the event</param>
protected virtual void FireEvent(string eventName, object param)
{
// Try to retrieve the specified EventHandler
FieldInfo eventInfo = this.GetType().GetField
	(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
// If the EventHandler is found, try to execute it
if (eventInfo != null)
{
var event_member = eventInfo.GetValue(this);
if (event_member != null)
{
EventArgs eventArgs;
// Get the type of the class which is used in the default constructor
var type = event_member.GetType().GetMethod("Invoke").GetParameters()[1].ParameterType;
// Create an instance of the parameter
eventArgs = (EventArgs)Activator.CreateInstance(type, param);
// Invoke the EventHandler
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)