Cling in java
1. Three important concepts in cling
Before you start using the Cling framework, you must understand three concepts.
Device
: an abstract concept, specific examples such as tv, dvd, and router in our life.Service
: just like class in Orient-Object language.Action
: equal to function in common program language.
Take our smartphone device as an example. It has multimedia, communication, and storage services.
Under the multimedia service, you can execute actions such as set volume or play control.
The follow is a Device-Service-Action
three-layer construct in Cling.
smartphone example
2. Implement smartphone example
Through the above image, we can hava a preliminary image of the Device-Service-Action three-layer architecture. Next, we will implement Smartphone Example by java code.
- Create a maven project and import related dependencies (Note: Please use
apache-maven-3.6.3
, the higher versions don't allow use HTTP repository url directly)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>cling-in-java</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cling-in-java</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty.version>8.1.14.v20131031</jetty.version>
<slf4j.version>6.1.26</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.fourthline.cling</groupId>
<artifactId>cling-support</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fourthline.cling</groupId>
<artifactId>cling-core</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>nexus-4thline</id>
<!--Note: Please use `apache-maven-3.6.3`, the higher versions don't allow use HTTP repository url directly-->
<url>http://4thline.org/m2/</url>
</repository>
</repositories>
</project>
- Creating a service and device (For simplicity, we only implement the multimedia service).
package org.example;
import org.fourthline.cling.binding.annotations.*;
@UpnpService(
serviceId = @UpnpServiceId(MultiMediaService.SERVICE_ID),
serviceType = @UpnpServiceType(MultiMediaService.SERVICE_ID)
)
public class MultiMediaService {
public static final String SERVICE_ID = "MultiMediaService";
public static final String ACTION_SET_VOLUME = "SetVolume";
@UpnpStateVariable(defaultValue = "50")
private String volume = "50"; // the UpnpStateVariable only support java.lang.String type
@UpnpAction
public void setVolume(@UpnpInputArgument(name = "ExpiredVolumeValue") String expiredVolumeValue) {
int volumeValue = Integer.parseInt(expiredVolumeValue);
if (volumeValue < 0 || volumeValue > 100) {
throw new IllegalArgumentException(String.format("VolumeValue range between [0, 100], you input value is %s", volumeValue));
}
volume = expiredVolumeValue;
}
@UpnpAction(out = @UpnpOutputArgument(name = "RealVolumeValue"))
public String getVolume() {
return volume;
}
}
package org.example;
import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder;
import org.fourthline.cling.model.DefaultServiceManager;
import org.fourthline.cling.model.ValidationException;
import org.fourthline.cling.model.meta.DeviceDetails;
import org.fourthline.cling.model.meta.DeviceIdentity;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.LocalService;
import org.fourthline.cling.model.types.DeviceType;
import org.fourthline.cling.model.types.UDN;
public class SmartphoneDevice {
public static LocalDevice getInstance() {
DeviceIdentity deviceIdentity = new DeviceIdentity(UDN.uniqueSystemIdentifier("SmartphoneDevice"));
DeviceType deviceType = new DeviceType("org.example", "SmartphoneDevice");
DeviceDetails deviceDetails = new DeviceDetails("SmartphoneDevice");
LocalService volumeControlService = new AnnotationLocalServiceBinder().read(MultiMediaService.class);
volumeControlService.setManager(new DefaultServiceManager(volumeControlService, MultiMediaService.class));
LocalDevice localDevice = null;
try {
localDevice = new LocalDevice(deviceIdentity, deviceType, deviceDetails, volumeControlService);
return localDevice;
} catch (ValidationException e) {
throw new RuntimeException(e);
}
}
}
3. Executing action in custom device
We have now created a smartphone device with multimedia service. Next, we will demonstrate how to execute the SetVolume action in this service. The invocation process is follow :
package org.example;
import org.fourthline.cling.UpnpServiceImpl;
import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.model.action.ActionInvocation;
import org.fourthline.cling.model.message.UpnpResponse;
import org.fourthline.cling.model.meta.Action;
import org.fourthline.cling.model.meta.LocalDevice;
import org.fourthline.cling.model.meta.LocalService;
import org.fourthline.cling.model.types.UDAServiceId;
import org.junit.Test;
public class AppTest
{
@Test
public void testActionInvocation() {
// get smartphone device instance
LocalDevice localDevice = SmartphoneDevice.getInstance();
// find multiMediaService in device
LocalService service = localDevice.findService(new UDAServiceId(MultiMediaService.SERVICE_ID));
invocationActionSync(service);
invocationActionAsync(service);
}
public void invocationActionSync(LocalService service) {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Action action = service.getAction(MultiMediaService.ACTION_SET_VOLUME);
ActionInvocation actionInvocation = new ActionInvocation<>(action);
actionInvocation.setInput("ExpiredVolumeValue", "88");
new ActionCallback.Default(actionInvocation, upnpService.getControlPoint()).run();
}
public void invocationActionAsync(LocalService service) {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Action action = service.getAction(MultiMediaService.ACTION_SET_VOLUME);
ActionInvocation actionInvocation = new ActionInvocation<>(action);
actionInvocation.setInput("ExpiredVolumeValue", "99");
upnpService.getControlPoint().execute(new ActionCallback(actionInvocation) {
@Override
public void success(ActionInvocation invocation) {
System.out.println(invocation);
}
@Override
public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
System.out.println(defaultMsg);
}
});
// Avoiding function complete immediately
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
4. Executing action in network device
In the above example, we directly obtained the smartphone device instance through
the SmartphoneDevice.getInstance
. However, in the actual development process.
we rarely customize devices, we only need to find special device in LAN (Local area network)
,then invocation action in this device.
In the next, we will create two thread to simulate search device in LAN.
- The
ServeThread
provide smartphone device. - The
ClientThread
find the device by network, then invocation action in this device.
package org.example;
import org.fourthline.cling.UpnpServiceImpl;
import org.fourthline.cling.binding.annotations.*;
import org.fourthline.cling.registry.Registry;
public class ServeThread implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new ServeThread());
thread.setDaemon(false);
thread.start();
}
@Override
public void run() {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Registry registry = upnpService.getRegistry();
registry.addDevice(SmartphoneDevice.getInstance());
}
}
package org.example;
import org.fourthline.cling.UpnpServiceImpl;
import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.model.action.ActionInvocation;
import org.fourthline.cling.model.message.UpnpResponse;
import org.fourthline.cling.model.meta.*;
import org.fourthline.cling.model.types.UDAServiceId;
import org.fourthline.cling.registry.Registry;
import org.fourthline.cling.registry.RegistryListener;
public class ClientThread implements Runnable, RegistryListener {
public static void main(String[] args) {
Thread thread = new Thread(new ClientThread());
thread.setDaemon(false);
thread.start();
}
@Override
public void run() {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Registry registry = upnpService.getRegistry();
registry.addListener(this);
upnpService.getControlPoint().search();
}
@Override
public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
}
@Override
public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
}
@Override
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
UDAServiceId udaServiceId = new UDAServiceId(MultiMediaService.SERVICE_ID);
RemoteService service = device.findService(udaServiceId);
if (service != null) {
// find the smartphone device
invocationActionSync(service);
invocationActionAsync(service);
}
}
@Override
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
}
@Override
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
}
@Override
public void localDeviceAdded(Registry registry, LocalDevice device) {
}
@Override
public void localDeviceRemoved(Registry registry, LocalDevice device) {
}
@Override
public void beforeShutdown(Registry registry) {
}
@Override
public void afterShutdown() {
}
public void invocationActionSync(Service service) {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Action action = service.getAction(MultiMediaService.ACTION_SET_VOLUME);
ActionInvocation actionInvocation = new ActionInvocation<>(action);
actionInvocation.setInput("ExpiredVolumeValue", "88");
new ActionCallback.Default(actionInvocation, upnpService.getControlPoint()).run();
}
public void invocationActionAsync(Service service) {
UpnpServiceImpl upnpService = new UpnpServiceImpl();
Action action = service.getAction(MultiMediaService.ACTION_SET_VOLUME);
ActionInvocation actionInvocation = new ActionInvocation<>(action);
actionInvocation.setInput("ExpiredVolumeValue", "99");
upnpService.getControlPoint().execute(new ActionCallback(actionInvocation) {
@Override
public void success(ActionInvocation invocation) {
System.out.println(invocation);
}
@Override
public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
System.out.println(defaultMsg);
}
});
// Avoiding function complete immediately
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Because we have created two long-time keep alive thread, it is difficult to
verify action invocation by debugger. The best way is to print volume value
in console after setVolume
function end.
When you print volume value, you will see twice output in console, one is sync , the other is async.