Subscription to an UPNP event

Discuss any general programming issues here
Post Reply
User avatar
mrupp
Posts: 66
Joined: Sun Jan 31, 2021 7:44 pm
Location: Switzerland
Contact:

Subscription to an UPNP event

Post by mrupp »

Hi there
For my SonosController I need to do subscriptions to UPNP events (https://en.wikipedia.org/wiki/Universal ... tification). I tried LOTS of things for a day or two and actually got quite far in the end. One piece is still missing, though.

This is what currently is working:
- Setting up a server that is listening to incoming requests
- Calling the host's endpoint to subscribe to to its events
- Receiving a first query from the host

Now, according to the docs about on how to do such a subscription, the listening server has to confirm the first incoming request with a "200 OK".
Now, how do I do this, sending a "200 OK"?

Here's the script containing the mentioned bits of code. If you have a Sonos speaker, change the IPs accordingly and it should run:

Code: Select all

@REQUIRE "hurl", {Link = True}

@APPTITLE "UPNP-Subscription-Test"

Function p_Subscribe()
	Local serverId = CreateServer(Nil)
	Local ip$ = "192.168.1.122" ; TODO: get IP dynamically, GetLocalInterfaces()
	Local port$ = GetLocalPort(serverId, #NETWORKSERVER)
	DebugPrint(ip$ .. ":" .. port$)

  Local host$ = "192.168.1.241:1400" ; IP of a Sonos speaker

  Local easy = hurl.Easy()
	easy:setopt_customrequest("SUBSCRIBE /MediaRenderer/AVTransport/Event HTTP/1.1") ; this took me hours to figure out :-)
	easy:setopt_httpheader({
		"TIMEOUT: Second-600",
		"CALLBACK: <http://" .. ip$ .. ":" .. port$ .. ">",
		"NT: upnp:event",
		"Host: " .. host$
	});	
	easy:setopt_url("http://".. host$)
	easy:setopt_writefunction(p_WriteFunction)
	easy:perform()
  
EndFunction

Function p_WriteFunction(data)
	DebugPrint("p_WriteFunction", data)
	DebugPrint(data)
EndFunction

Function p_OnConnect(data)
	DebugPrint("p_OnConnect")
	p_PrintTable(data)
EndFunction

Function p_OnReceiveData(data)
	DebugPrint("p_OnReceiveData")
	p_PrintTable(data)
EndFunction

Function p_PrintTable(table)
	For k, v In Pairs(table)
		If GetType(v) = #TABLE
			DebugPrint(k, ":")
			p_PrintTable(v)
		Else
			DebugPrint(k, ":", v)
		EndIf
	Next
	DebugPrint("-----------------------------------------")
EndFunction

InstallEventHandler({
  OnConnect = p_OnConnect,
  OnReceiveData = p_OnReceiveData
})

p_Subscribe()

Repeat
  WaitEvent
Forever
Output:

Code: Select all

192.168.1.122:55429
p_OnConnect
action : OnConnect
clientid : UserData: 00000000032562F0
timestamp : 1.3396244049072
serverid : UserData: 00000000032569F0
-----------------------------------------
p_OnReceiveData
id : UserData: 00000000032562F0
timestamp : 1.3416767120361
action : OnReceiveData
-----------------------------------------
I was so happy I almost cried when I first and finally had p_OnReceiveData() output something. :D

Cheers,
Michael

User avatar
msu
Posts: 71
Joined: Mon Jun 13, 2016 11:36 am
Location: Sinzig/Germany

Re: Subscription to an UPNP event

Post by msu »

I would look at how other programs carry out communication.
For example the Sonos desktop app.
https://www.sonos.com/de-de/controller-app

With software like Wirshark, you could see all of the communication.
https://www.wireshark.org/

User avatar
mrupp
Posts: 66
Joined: Sun Jan 31, 2021 7:44 pm
Location: Switzerland
Contact:

Re: Subscription to an UPNP event

Post by mrupp »

msu wrote:
Thu Apr 01, 2021 11:58 am
I would look at how other programs carry out communication.
For example the Sonos desktop app.
https://www.sonos.com/de-de/controller-app

With software like Wirshark, you could see all of the communication.
https://www.wireshark.org/
Good idea, I'll try it out... Thanks!

User avatar
msu
Posts: 71
Joined: Mon Jun 13, 2016 11:36 am
Location: Sinzig/Germany

Re: Subscription to an UPNP event

Post by msu »

Wireshark is also very useful when you develop server / client software.
Errors in the protocol or in the transmission can be found very quickly.

Greetings and happy Easter days

User avatar
mrupp
Posts: 66
Joined: Sun Jan 31, 2021 7:44 pm
Location: Switzerland
Contact:

Re: Subscription to an UPNP event

Post by mrupp »

Thanks to Wireshark I finally figured it out. I tried lots of things, but in the end it turned out to be as simple as this:

Code: Select all

SendData(data.id, "HTTP/1.1 200 OK\r\nContent-length: 0\r\nConnection: close\r\n\r\n")
Here's the full script of the working example:

Code: Select all

@REQUIRE "hurl", {Link = True}

@APPTITLE "UPNP-Subscription-Test"

Function p_WriteFunction(data)
	DebugPrint("p_WriteFunction", data)
	DebugPrint(data)
EndFunction

Function p_Subscribe(host$)
	Local serverId = CreateServer(Nil)
	Local ip$ = "192.168.1.122" ; TODO: get IP dynamically, GetLocalInterfaces()
	Local port = GetLocalPort(serverId, #NETWORKSERVER)
	DebugPrint(ip$ .. ":" .. port)

	Local easy = hurl.Easy()
	easy:setopt_customrequest("SUBSCRIBE /MediaRenderer/AVTransport/Event HTTP/1.1") ; this took me hours to figure out :-)
	easy:setopt_httpheader({
		"TIMEOUT: Second-600",
		"CALLBACK: <http://" .. ip$ .. ":" .. port .. ">",
		"NT: upnp:event",
		"Host: " .. host$
	})
	easy:setopt_url("http://".. host$)
	easy:setopt_writefunction(p_WriteFunction)
	easy:perform()
	easy:close()
EndFunction

Function p_OnConnect(data)
	DebugPrint("p_OnConnect")
	p_PrintTable(data)
EndFunction

Function p_OnDisconnect(data)
	DebugPrint("p_OnDisconnect")
	p_PrintTable(data)
EndFunction

Function p_OnReceiveData(data)
	DebugPrint("p_OnReceiveData")
	p_PrintTable(data)

	SendData(data.id, "HTTP/1.1 200 OK\r\nContent-length: 0\r\nConnection: close\r\n\r\n")

	Local data$, count, done = ReceiveData(data.id, #RECEIVEALL)
	DebugPrint(data$)
	; --> do something with that data$
EndFunction

Function p_PrintTable(table)
	For k, v In Pairs(table)
		If GetType(v) = #TABLE
			DebugPrint(k, ":")
			p_PrintTable(v)
		Else
			DebugPrint(k, ":", v)
		EndIf
	Next
	DebugPrint("-----------------------------------------")
EndFunction

InstallEventHandler({
	OnConnect = p_OnConnect,
	OnReceiveData = p_OnReceiveData,
	OnDisconnect = p_OnDisconnect
})

p_Subscribe("192.168.1.241:1400")

Repeat
	WaitEvent
Forever
Please note that the following TODOs are still missing in this script:
  • renewal of the subscription before it expires after 600 seconds
  • properly unsubscribe from the event when quitting the app

Post Reply