Understanding Upnp Discovery and UpnpDiscoveryParticipant workflow in general

Greetings from germany,

I need some help dicovering my diy devices through Upnp discovery. I have quiet a hard time to understand Upnp Discovery and espacilly the implementation of the class UpnpDiscoveryParticipant in OpenHab itself. I’ve read a lot about Upnp. I think this link is quiet helpfull to understand Upnp Discovery in general: https://www.electricmonk.nl/log/2016/07/05/exploring-upnp-with-python/

Refering to the link: I don’t understand if OpenHab is the client or the server. Is OpenHab sending out a M-Search message (so this means it’s the client aka control point) and is waiting for a reply from my diy device (which means, the device has to constantly check for an incomming M-Search message and has to respond to it) or is OpenHab waiting for a Notify message (so OpenHab is the Server) like addressed in this explanation page 12 send by my diy device?

If I can understand the targeted Upnp worklow by OpenHab than I think I can implement the other half on my diy device. Hope you can help me out, cause right now I’m rly confused.

Thank you in advance,
Tmirror

I think the best way to understand the workflow ist to have a look at the sources of SONOS Discovery


or WeMo Discovery

as both Bindings use UPnP.

Thanks for your reply,

I’ve looked into the source code of SONOS and WeMo discovery. Obviously it only shows the OpenHab part of the implementation. I’ve understood what the purpose of these 3 methods are and when they are called, but as far as I’ve understood this, they aren’t part of the actual “UDP discovery message stuff”. For example: The method createResult() is called, when the framework handled the message stuff already. It then informs the discovery service that a remote device has been found and gives me the option to create a result based on this remote device. But before the framework even calls the method createResult() a communication with the diy device must happen. And I need some kind of resource or explanation on how this communication happens in detail. It would be great if it refers to the way OpenHab communicates (e.g.: sending a search message broadcast or waiting for a notify message broadcast) and not the general way of Upnp communication. For this i can just use the resources of upnp.org

Update:

I’ve discovered that OpenHab2 sends out a M-Search message to explicit search for new devices. It doesn’t wait until a device sends out a Notify message without receiving a M-Search message before. I’ll keep learning and keep you all up to date if interested.

Still would be glad if someone can point out some ressources to me which indicates how to proper implement a Upnp service for diy devices (c, java, python idc.) and it would be awesome if these resources also refer to OpenHab2 (something like: “Implemention of an Arduino Upnp service to be discovered by OpenHab2 Upnp discovery”).

Thanks in advance :slight_smile:

keep us posted👍

Check out the HUE emulation, if enabled, openHAB discoveres a virtual HUE Bridge and shows it in inbox.

The several M-Search messages you see are produced by background scan, but Notify messages should aslo produce discovery results.

1 Like

Hey hmerk,

thanks for the reply. It really helped me out understanding the principles a little bit better. I’ve found the emulation within the normal OpenHab2 distribution which is available at the download page. Right now I’m using eclipse smarthome for OpenHab2 (due to binding developement and so on). Is there a way to use Hue Emulation there? I cannot find it right now. Normaly I can add bindings and other addons if I change the run configurations.

Of course, you can select it in Run od Debug configuration, just like any other Binding. Just scroll down the list, as it is an IO bundle…

Hey hmerk,

can you tell me the full name of the bundle? I guess it must be something like “org.eclipse.smarthome.io.hue.emulation” but i really can’t find it.

Thanks

Use the console and check:

bundle:list -s |grep hue

Started OpenHab via SmartHome Eclipse. Is it still possible to access console there?

Tried reaching it via Putty, doesn’t work. (Localhost:8101)

not sure… I haven’t used ESH for a while now…
in theory: yes

It seems the console is enabled in the plugins… I’ll keep trying

1 Like

Checked out the bundles manually. It seems org.openhab.io.hueemulation is rly missing. I’ll search for a github link to download it.

1 Like

it should be on the ESH repo: https://github.com/eclipse/smarthome/tree/master/extensions/binding/org.eclipse.smarthome.binding.hue

but this is different than the Hue Emulation: https://www.openhab.org/addons/integrations/hueemulation/

(over) simplified:
Hue Binding = Connects to a Hue bridge
Hue Emulation = Acts as a Hue Bridge

The normal hue binding was present. What’s missing is the hue emulation. It doesn’t comes with the normal binding. Found a repo. We’ll see if I can implement it as a plugin.

I think it’s here: https://github.com/openhab/openhab2-addons/tree/master/addons/io/org.openhab.io.hueemulation

tbh: I really don’t know cause I don’t have Hue stuff in my setup

No, it isn’t, it is

org.openhab.io.hueemulation
1 Like

Got it with some build errors. Still, it’s running. Good enough for my purpose. Thank you

2 Likes

Attention: a lot of informations are incomming :no_mouth:

After I’ve read a lot of informations about Upnp and SSDP in general as well as the help I received in this forum, I set up a test enviorment to come a step closer in understanding Upnp Discovery with OpenHab2. I’ve implemented the following parts:

  1. A discovery service for my custom binding (general setup to get some console output. There is no actual discovery result created) [OpenHab2]

  2. A device simulation, which waits for a M-Search message and responses accordingly to SSDP specification (just basic stuff) [DeviceSimulation]

  3. A tomcat server, which provides the location XML file as stated in the RFC [DeviceSimulation]

My understanding goes as far as this: When I start the OpenHab discovery by pressing the scan button OpenHab sends out a UDP Message which looks like this:

M-SEARCH * HTTP/1.1
ST: ssdp:all
HOST: 239.255.255.250:1900
MX: 3
MAN: ssdp:discover

This matches with the SSDP RFC. My device needs to listen to 239.255.255.250:1900. If a message is noticed, it checks if the message is an M-SEARCH message. If it is, it response. Therefore thefollowing HTTP 200 is created:

HTTP/1.1 200 OK
HOST: 239.255.255.250:1900
EXT:
CACHE-CONTROL: max-age=100
LOCATION: http://localhost:8090/demorest/discovery.xml
SERVER: FreeRTOS/7.4.2, UPnP/1.0, IpBridge/1.15.0
hue-bridgeid: 12
ST: service:hue
USN: uuid:hueIdentification::upnp:rootdevice

(Let’s ignore for the moment which informations are passed as the location and so on). This message is sent back to OpenHab. It’s not sent to the Broadcast Adress but directly to OpenHabs IP and Port, where the M-SEARCH message came from. OpenHab should receive this information and should access the location XML file (by visiting the URL that’s written after “LOCATION”). At this point my understanding fades out. I would expect that OpenHab2 would retrieve the XML file and then the method createResult() of the implemented UpnpDiscoveryParticipant is called. But somehow this doesn’t seem to happen.

Right now I have 3 central question that needs to be solved:

  1. Is my understanding of the SSDP workflow with OpenHab and my simulated device correct?

  2. Which informations must the XML file contain?

  3. Why is the mehtode createResult not called for my device (it’s called for other Upnp devices tho)? I guess the XML file was accessed by OpenHab but sadly I cannot get a proof for that.

Here is my actual code (dirty quick typing, no real concepts):

Discovery Service

package o.e.sh.binding.drehbinding.discovery;

import static o.e.sh.binding.drehbinding.DrehBindingBindingConstants.THING_TYPE_DREHKNOPF;

import java.util.Collections;
import java.util.Set;

import org.eclipse.smarthome.config.discovery.DiscoveryResult;
import org.eclipse.smarthome.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.ThingUID;
import org.jupnp.model.meta.RemoteDevice;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link DrehKnopfUpnpDiscoveryService} is responsible processing the
 * results of searches for UPNP devices
 *
 * @author Karel Goderis - Initial contribution
 */
@Component(immediate = true)
public class DrehKnopfUpnpDiscoveryService implements UpnpDiscoveryParticipant {

    private final Logger logger = LoggerFactory.getLogger(DrehKnopfUpnpDiscoveryService.class);

    @Override
    public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
        return Collections.singleton(THING_TYPE_DREHKNOPF);
    }

    @Override
    public DiscoveryResult createResult(RemoteDevice device) {
        ThingUID uid = getThingUID(device);
        logger.debug(device.getDisplayString());
        return null;
    }

    @Override
    public ThingUID getThingUID(RemoteDevice device) {
        logger.debug(device.getDisplayString());
        return null;
    }

}

MyDevice Listener and Responder for M-Search

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.util.concurrent.TimeUnit;

public class SearchListener extends Thread {

	private static final int UPNP_PORT = 1900;
	private static final String MULTICAST_ADDR = "239.255.255.250";
	private static final String LOCATION = "http://localhost:8090/demorest/discovery.xml";
	private static final String HUE_ID = "12";
	private static final String ST = "service:hue";
	private static final String USN = "hueIdentification";

	private String responseMsg = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%d\r\n" + "EXT:\r\n"
			+ "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: %s\r\n"
			+ "SERVER: FreeRTOS/7.4.2, UPnP/1.0, IpBridge/1.15.0\r\n" + "hue-bridgeid: %s\r\n" + "ST: %s\r\n"
			+ "USN: uuid:%s::upnp:rootdevice\r\n\r\n";

	private MulticastSocket multiSocket;
	private byte[] buf;
	private DatagramPacket recv;

	private DatagramSocket sendSocket;

	public SearchListener() throws IOException {
		multiSocket = new MulticastSocket(UPNP_PORT);
		multiSocket.joinGroup(InetAddress.getByName(MULTICAST_ADDR));

		sendSocket = new DatagramSocket();

		buf = new byte[1024];
		recv = new DatagramPacket(buf, buf.length);
	}

	@Override
	public void run() {
		try {
			System.out.println("Running");
			while (true) {
				if (receiveMessage()) {
					sendResponse();
				}

				TimeUnit.SECONDS.sleep(3);
			}
		} catch (IOException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private boolean receiveMessage() throws IOException {
		System.out.println("Waiting for message");
//		System.out.println(multiSocket.getLocalAddress() + ":" + multiSocket.getLocalPort() + multiSocket.getPort());
		multiSocket.receive(recv);
		System.out.println("Received message from " + recv.getAddress() + ":" + recv.getPort());

		if (recv.getLength() > 0) {
			String data = new String(recv.getData());

			if (data.startsWith("M-SEARCH")) {
				return true;
			}
		}

		return false;
	}

	private void sendResponse() throws IOException {
		String msg = String.format(responseMsg, MULTICAST_ADDR, UPNP_PORT, LOCATION, HUE_ID, ST, USN);
		sendSocket.send(new DatagramPacket(msg.getBytes(), msg.getBytes().length, recv.getAddress(), recv.getPort()));
		System.out.println("Sent out message to: " + recv.getAddress() + ":" + recv.getPort());
		System.out.println(msg);
	}

}

The XML file


I guess I’m missing something really essential but I need to remind you, that I’ve just started working with Upnp and SSDP.

Hope you can help me out once again.
Thanks in advance!