Index: trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/upnp/SubscriptionManager.java
===================================================================
--- trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/upnp/SubscriptionManager.java (revision 140)
+++ trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/upnp/SubscriptionManager.java (revision 141)
@@ -8,9 +8,14 @@
 package uk.co.matbooth.sonorous.upnp;
 
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Properties;
 
 import org.eclipse.core.runtime.ILog;
 import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
 import org.eclipse.core.runtime.Status;
 import org.osgi.framework.BundleContext;
@@ -73,18 +78,23 @@
 					+ serviceId + " on " + deviceId));
 		} else {
-			log.log(new Status(IStatus.WARNING, Activator.ID, "No subscription to "
-					+ serviceId + " on " + deviceId));
+			log.log(new Status(IStatus.WARNING, Activator.ID,
+					"No subscription exists to " + serviceId + " on " + deviceId));
 		}
 	}
 
-	private class Subscription {
+	/**
+	 * TODO
+	 */
+	private class Subscription implements UPnPEventListener {
 
 		private final ServiceRegistration reg;
+		private final UPnPEventListener listener;
 
-		public Subscription(BundleContext context, String filter,
-				UPnPEventListener listener) throws Exception {
+		public Subscription(BundleContext context, String filter, UPnPEventListener l)
+				throws Exception {
 			Properties p = new Properties();
 			p.put(UPnPEventListener.UPNP_FILTER, context.createFilter(filter));
-			reg = context.registerService(UPnPEventListener.class.getName(), listener, p);
+			listener = l;
+			reg = context.registerService(UPnPEventListener.class.getName(), this, p);
 		}
 
@@ -92,4 +102,19 @@
 			reg.unregister();
 		}
+
+		public void notifyUPnPEvent(String deviceId, String serviceId, Dictionary events) {
+			// Log the event before sending it up to the real listener
+			List<IStatus> details = new ArrayList<IStatus>();
+			for (Enumeration<String> keys = events.keys(); keys.hasMoreElements();) {
+				String key = keys.nextElement();
+				details.add(new Status(IStatus.INFO, Activator.ID, key + ": "
+						+ events.get(key)));
+			}
+			MultiStatus status = new MultiStatus(Activator.ID, IStatus.INFO, details
+					.toArray(new IStatus[details.size()]), "Event received from "
+					+ serviceId + " on " + deviceId, null);
+			log.log(status);
+			listener.notifyUPnPEvent(deviceId, serviceId, events);
+		}
 	}
 }
Index: trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneLabelProvider.java
===================================================================
--- trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneLabelProvider.java (revision 140)
+++ trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneLabelProvider.java (revision 141)
@@ -22,5 +22,5 @@
 		switch (columnIndex) {
 		case 0:
-			return zone.getRootUDN();
+			return zone.getZoneName();
 		default:
 			return "?";
Index: trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneManager.java
===================================================================
--- trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneManager.java (revision 140)
+++ trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/ZoneManager.java (revision 141)
@@ -8,4 +8,6 @@
 package uk.co.matbooth.sonorous.zones;
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -21,5 +23,5 @@
  * and notifies interested parties of changes to that model.
  */
-public class ZoneManager implements DeviceListener {
+public class ZoneManager implements DeviceListener, PropertyChangeListener {
 	private static ZoneManager provider;
 
@@ -60,4 +62,5 @@
 			Zone z = new Zone(event.getDevice());
 			if (zones.add(z)) {
+				z.addPropertyChangeListener(this);
 				z.startSubscriptions();
 				fireZoneChanged();
@@ -76,4 +79,5 @@
 				if (z.getRootUDN().equals(event.getDevice().getUDN())) {
 					z.stopSubscriptions();
+					z.removePropertyChangeListener(this);
 					i.remove();
 					fireZoneChanged();
@@ -81,4 +85,11 @@
 			}
 		}
+	}
+
+	/**
+	 * Implement PropertyChangeListener. Fire an event when a zone changes.
+	 */
+	public void propertyChange(PropertyChangeEvent evt) {
+		fireZoneChanged();
 	}
 
Index: trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/Zone.java
===================================================================
--- trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/Zone.java (revision 140)
+++ trunk/sonorous/uk.co.matbooth.sonorous/src/uk/co/matbooth/sonorous/zones/Zone.java (revision 141)
@@ -8,4 +8,6 @@
 package uk.co.matbooth.sonorous.zones;
 
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
 import java.util.ArrayList;
 import java.util.Dictionary;
@@ -22,11 +24,18 @@
  */
 public class Zone implements UPnPEventListener {
+	private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
+
+	/**
+	 * The device tree this zone is based on.
+	 */
 	private final DeviceNode rootNode;
 
 	/**
-	 * List of all the services we want to subscribe to.
+	 * List of all the services provided by the device tree that we want to
+	 * subscribe to.
 	 */
 	private static final String SERVICES[] = { "urn:upnp-org:serviceId:DeviceProperties",
-			"urn:upnp-org:serviceId:ZoneGroupTopology", };
+//			"urn:upnp-org:serviceId:ZoneGroupTopology", // TODO
+	};
 
 	/**
@@ -36,4 +45,6 @@
 	private final List<String[]> subscriptions = new ArrayList<String[]>();
 
+	private String zoneName = "";
+
 	public Zone(DeviceNode device) {
 		rootNode = device;
@@ -42,4 +53,8 @@
 	public String getRootUDN() {
 		return rootNode.getUDN();
+	}
+
+	public String getZoneName() {
+		return zoneName;
 	}
 
@@ -62,15 +77,46 @@
 	}
 
+	/**
+	 * Implement UPnPEventListener. Update the zone's properties and fire off
+	 * property change events.
+	 */
 	public void notifyUPnPEvent(String deviceId, String serviceId, Dictionary events) {
-		Enumeration en = events.keys();
-		System.out.println();
-		System.out.println("Event!");
-		System.out.println("UDN: " + deviceId);
-		System.out.println("ServiceID: " + serviceId);
-		for (; en.hasMoreElements();) {
-			String ssvName = (String) en.nextElement();
-			Object value = events.get(ssvName);
-			System.out.println("Variable: " + ssvName + " = " + value);
+		if (serviceId.equals("urn:upnp-org:serviceId:DeviceProperties")) {
+			for (Enumeration<String> keys = events.keys(); keys.hasMoreElements();) {
+				String key = keys.nextElement();
+				if (key.equals("ZoneName"))
+					pcs.firePropertyChange(key, zoneName, zoneName = (String) events
+							.get(key));
+			}
+			// return;
+		}
+		if (serviceId.equals("urn:upnp-org:serviceId:ZoneGroupTopology")) {
+			for (Enumeration<String> keys = events.keys(); keys.hasMoreElements();) {
+
+			}
+			// return;
 		}
 	}
+
+	/**
+	 * Adds a listener to receive property change events from this zone.
+	 * 
+	 * @param listener
+	 *            a PropertyChangeListener implementation
+	 */
+	public void addPropertyChangeListener(PropertyChangeListener listener) {
+		pcs.addPropertyChangeListener(listener);
+	}
+
+	/**
+	 * Removes the specified listener so that it no longer receives property
+	 * change events from this zone.
+	 * 
+	 * @param listener
+	 *            a PropertyChangeListener implementation
+	 */
+	public void removePropertyChangeListener(PropertyChangeListener listener) {
+		pcs.removePropertyChangeListener(listener);
+	}
+
 }
