Skip to content

Commit f3cc15f

Browse files
committed
ControlPoint: subscriptions
1 parent ade1c06 commit f3cc15f

File tree

6 files changed

+90
-49
lines changed

6 files changed

+90
-49
lines changed

examples/control-point-media-renderer/control-point-media-renderer.ino

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ void setup() {
3434

3535
setupWifi();
3636

37+
// Provide local URL for notifications
38+
renderer.setLocalURL(WiFi.localIP(), port, "/events");
39+
3740
// Start control point to discover devices: 10 min
3841
if (!renderer.begin(1000, 10 * 60 * 1000)) {
3942
Serial.println("No devices found");
@@ -46,8 +49,9 @@ void setup() {
4649

4750
// Use first discovered renderer device
4851
renderer.setDeviceIndex(0);
52+
4953
// Subscribe to event notifications
50-
renderer.setNotificationsActive(true);
54+
renderer.subscribe();
5155

5256
// Starting playback
5357
Serial.println("Starting playback...");

src/dlna/clients/DLNAControlPoint.h

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
#include "Client.h"
66
#include "DLNAControlPointRequestParser.h"
77
#include "basic/Url.h"
8+
#include "dlna/clients/IControlPoint.h"
9+
#include "dlna/clients/SubscriptionMgrControlPoint.h"
810
#include "dlna/common/DLNADeviceInfo.h"
911
#include "dlna/common/Schedule.h"
1012
#include "dlna/common/Scheduler.h"
11-
#include "dlna/clients/IControlPoint.h"
12-
#include "dlna/clients/SubscriptionMgrControlPoint.h"
1313
#include "dlna/devices/DLNADevice.h"
1414
#include "dlna/xml/XMLDeviceParser.h"
1515
#include "dlna/xml/XMLParser.h"
@@ -87,10 +87,11 @@ class DLNAControlPoint : public IControlPoint {
8787

8888
/// Defines the local url (needed for subscriptions)
8989
void setLocalURL(Url url) override { local_url = url; }
90-
void setLocalURL(IPAddress url, int port=9001, const char* path="") override {
90+
void setLocalURL(IPAddress url, int port = 9001,
91+
const char* path = "") override {
9192
char buffer[200];
92-
snprintf(buffer, sizeof(buffer), "http://%s:%d%s", url.toString().c_str(), port,
93-
path);
93+
snprintf(buffer, sizeof(buffer), "http://%s:%d%s", url.toString().c_str(),
94+
port, path);
9495
local_url.setUrl(buffer);
9596
}
9697

@@ -118,7 +119,6 @@ class DLNAControlPoint : public IControlPoint {
118119
void setHttpServer(IHttpServer& server) override {
119120
DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::setHttpServer");
120121
p_http_server = &server;
121-
subscription_mgr.setHttpServer(server);
122122
}
123123

124124
/// Register a callback that will be invoked when parsing SOAP/Action results
@@ -161,14 +161,6 @@ class DLNAControlPoint : public IControlPoint {
161161
is_active = true;
162162
setTransports(http, udp);
163163

164-
if (p_http_server) {
165-
// handle server requests
166-
if (!p_http_server->begin()) {
167-
DlnaLogger.log(DlnaLogLevel::Error, "HttpServer begin failed");
168-
return false;
169-
}
170-
}
171-
172164
// setup multicast UDP
173165
if (!(p_udp->begin(DLNABroadcastAddress))) {
174166
DlnaLogger.log(DlnaLogLevel::Error, "UDP begin failed");
@@ -200,22 +192,6 @@ class DLNAControlPoint : public IControlPoint {
200192
loop();
201193
}
202194

203-
// setup subscription manager
204-
if (local_url && p_http_server) {
205-
subscription_mgr.setup(http, udp, local_url, getDevice());
206-
} else {
207-
if (!local_url && !p_http_server) {
208-
DlnaLogger.log(DlnaLogLevel::Info,
209-
"No local URL and no HttpServer for subscriptions");
210-
} else if (!local_url) {
211-
DlnaLogger.log(DlnaLogLevel::Warning,
212-
"No local URL for subscriptions");
213-
} else {
214-
DlnaLogger.log(DlnaLogLevel::Warning,
215-
"No HttpServer for subscriptions");
216-
}
217-
}
218-
219195
// If we exited early because a device was found, deactivate the MSearch
220196
// schedule so it will stop repeating. The scheduler will clean up
221197
// inactive schedules on its next pass.
@@ -236,6 +212,36 @@ class DLNAControlPoint : public IControlPoint {
236212
p_udp = &udp;
237213
}
238214

215+
/// Subscribes to event notifications for all services of the selected device
216+
bool subscribe() {
217+
if (devices.size() == 0) return false;
218+
if (!getDevice()) return false;
219+
220+
// setup subscription manager
221+
if (local_url && p_http_server) {
222+
if (p_http_server) {
223+
// handle server requests
224+
if (!p_http_server->begin()) {
225+
DlnaLogger.log(DlnaLogLevel::Error, "HttpServer begin failed");
226+
return false;
227+
}
228+
}
229+
subscription_mgr.setHttpServer(*p_http_server);
230+
subscription_mgr.setup(*p_http, *p_udp, local_url, getDevice());
231+
return true;
232+
}
233+
234+
if (!local_url && !p_http_server) {
235+
DlnaLogger.log(DlnaLogLevel::Info,
236+
"No local URL and no HttpServer for subscriptions");
237+
} else if (!local_url) {
238+
DlnaLogger.log(DlnaLogLevel::Warning, "No local URL for subscriptions");
239+
} else {
240+
DlnaLogger.log(DlnaLogLevel::Warning, "No HttpServer for subscriptions");
241+
}
242+
return false;
243+
}
244+
239245
/// Stops the processing and releases the resources
240246
void end() override {
241247
DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::end");
@@ -365,6 +371,8 @@ class DLNAControlPoint : public IControlPoint {
365371
/// Provides the device information of the actually selected device
366372
DLNADeviceInfo& getDevice() override {
367373
DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::getDevice");
374+
if (default_device_idx < 0 || default_device_idx >= devices.size())
375+
return NO_DEVICE;
368376
return devices[default_device_idx];
369377
}
370378

@@ -672,7 +680,8 @@ class DLNAControlPoint : public IControlPoint {
672680
bool matches(const char* usn) {
673681
if (search_target == nullptr || *search_target == '\0') return true;
674682
if (StrView(search_target).equals("ssdp:all")) return true;
675-
return StrView(usn).contains(search_target) || StrView(search_target).contains(usn);
683+
return StrView(usn).contains(search_target) ||
684+
StrView(search_target).contains(usn);
676685
}
677686

678687
/// processes a bye-bye message
@@ -776,7 +785,8 @@ class DLNAControlPoint : public IControlPoint {
776785
"Service control_url: %s, device base: %s",
777786
StrView(service.control_url).c_str(),
778787
StrView(device.getBaseURL()).c_str());
779-
Url post_url = getUrl(device, service.control_url, url_buffer, DLNA_MAX_URL_LEN);
788+
Url post_url =
789+
getUrl(device, service.control_url, url_buffer, DLNA_MAX_URL_LEN);
780790
DlnaLogger.log(DlnaLogLevel::Info, "POST URL computed: %s", post_url.url());
781791

782792
// send HTTP POST and collect/handle response. If the caller provided an

src/dlna/clients/DLNAControlPointMediaRenderer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ class DLNAControlPointMediaRenderer {
5656
void setDeviceTypeFilter(const char* filter) {
5757
device_type_filter = filter ? filter : device_type_filter_default;
5858
}
59+
/// Defines the local URL for the control point
60+
void setLocalURL(Url url) { p_mgr->setLocalURL(url); }
61+
62+
/// Defines the local URL for the control point
63+
void setLocalURL(IPAddress url, int port=9001, const char* path="") {
64+
this->p_mgr->setLocalURL(url, port, path);
65+
}
5966

6067
/**
6168
* @brief Begin discovery and processing
@@ -105,6 +112,10 @@ class DLNAControlPointMediaRenderer {
105112
*/
106113
void setDeviceIndex(int idx) { device_index = idx; }
107114

115+
/// Subscribe to event notifications: call after selecting the device
116+
bool subscribe() { return p_mgr->subscribe(); }
117+
118+
108119
/// Setter for the HTTP wrapper used for subscriptions and callbacks
109120
void setHttp(IHttpRequest& http) { p_http = &http; }
110121

src/dlna/clients/DLNAControlPointMediaServer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ class DLNAControlPointMediaServer {
4040
/// Set the control point manager instance (required before using helper)
4141
void setDLNAControlPoint(DLNAControlPoint& m) { p_mgr = &m; }
4242

43+
/// Defines the local URL for the control point
44+
void setLocalURL(Url url) { p_mgr->setLocalURL(url); }
45+
46+
/// Defines the local URL for the control point
47+
void setLocalURL(IPAddress url, int port=9001, const char* path="") {
48+
this->p_mgr->setLocalURL(url, port, path);
49+
}
50+
4351
/**
4452
* @brief Begin discovery and processing (forwards to underlying control
4553
* point)
@@ -103,6 +111,9 @@ class DLNAControlPointMediaServer {
103111
*/
104112
void setDeviceIndex(int idx) { p_mgr->setDeviceIndex(idx); }
105113

114+
bool subscribe() { return p_mgr->subscribe(); } /// Subscribe to event notifications: call after selecting the device
115+
116+
106117
/// Activate/deactivate subscription notifications
107118
void setNotificationsActive(bool flag) {
108119
p_mgr->setNotificationsActive(flag);

src/dlna/clients/SubscriptionMgrControlPoint.h

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class SubscriptionMgrControlPoint {
6767
p_local_url = &localCallbackUrl;
6868
p_device = &device;
6969
is_setup = true;
70+
attachHttpServer(*p_http_server);
7071
// Initialize event subscription state
7172
setNotificationsActive(event_subscription_active);
7273
}
@@ -84,7 +85,6 @@ class SubscriptionMgrControlPoint {
8485
void setHttpServer(IHttpServer& server) {
8586
DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::setHttpServer");
8687
p_http_server = &server;
87-
attachHttpServer(server);
8888
}
8989

9090
/**
@@ -328,14 +328,9 @@ class SubscriptionMgrControlPoint {
328328
DlnaLogger.log(DlnaLogLevel::Debug,
329329
"DLNAControlPointMgr::attachHttpServer");
330330
p_http_server = &server;
331-
// register handler at the local path. If local_url is not set we use
332-
// a default path "/dlna/events"
333-
const char* path = "/dlna/events";
334-
if (p_local_url && !StrView(p_local_url->url()).isEmpty())
335-
path = p_local_url->path();
336-
337331
void* ctx[1];
338332
ctx[0] = this;
333+
const char* path = p_local_url->path();
339334
server.on(path, T_NOTIFY, notifyHandler, ctx, 1);
340335
}
341336

@@ -434,15 +429,22 @@ class SubscriptionMgrControlPoint {
434429
char seconds_txt[80] = {0};
435430
snprintf(seconds_txt, sizeof(seconds_txt), "Second-%d",
436431
event_subscription_duration_sec);
432+
char callback_url[256] = {0};
433+
snprintf(callback_url, sizeof(callback_url), "<%s>",
434+
p_local_url ? p_local_url->url() : "");
437435
p_http->request().put("NT", "upnp:event");
438436
p_http->request().put("TIMEOUT", seconds_txt);
439-
p_http->request().put("CALLBACK", p_local_url ? p_local_url->url() : "");
437+
p_http->request().put("CALLBACK", callback_url);
440438
// For re-subscribe, include existing SID header if present
441439
if (!service.subscription_id.isEmpty()) {
442440
p_http->request().put("SID", service.subscription_id.c_str());
443441
}
442+
p_http->request().put("CONTENT-LENGTH", "0");
444443

445444
int rc = p_http->subscribe(url);
445+
if (!p_http->isKeepAlive()) {
446+
p_http->stop();
447+
}
446448
if (rc == 200) {
447449
const char* sid = p_http->reply().get("SID");
448450
if (sid != nullptr) {
@@ -530,7 +532,8 @@ class SubscriptionMgrControlPoint {
530532
* @param handlerLine Pointer to the server-provided handler context which
531533
* contains the manager instance in its context array.
532534
*/
533-
static void notifyHandler(IClientHandler& client, IHttpServer* server, const char* requestPath,
535+
static void notifyHandler(IClientHandler& client, IHttpServer* server,
536+
const char* requestPath,
534537
HttpRequestHandlerLine* handlerLine) {
535538
if (handlerLine == nullptr || handlerLine->contextCount < 1) return;
536539
void* ctx0 = handlerLine->context[0];
@@ -575,12 +578,14 @@ class SubscriptionMgrControlPoint {
575578
}
576579

577580
Str getURLString(DLNAServiceInfo& service) {
578-
Str url_str{100};
581+
Str url_str{100};
579582
if (service.event_sub_url.startsWith("/")) {
580583
// relative URL: need to build full URL using device base URL
581584
url_str = p_device->getBaseURL();
582-
url_str += service.event_sub_url.c_str();
583-
url_str.replace("//", "/");
585+
if (url_str.endsWith("/") && service.event_sub_url.startsWith("/"))
586+
url_str += service.event_sub_url.c_str() + 1;
587+
else
588+
url_str += service.event_sub_url.c_str();
584589

585590
} else {
586591
url_str = service.event_sub_url.c_str();

src/http/Server/HttpRequest.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ class HttpRequest : public IHttpRequest {
131131

132132
/// Sends a SUBSCRIBE request.
133133
int subscribe(Url& url) override {
134-
DlnaLogger.log(DlnaLogLevel::Info, "post %s", url.url());
134+
DlnaLogger.log(DlnaLogLevel::Info, "%s %s", methods[T_SUBSCRIBE], url.path());
135135
return process(T_SUBSCRIBE, url, nullptr, nullptr, 0);
136136
}
137137

138138
/// Sends an UNSUBSCRIBE request.
139139
int unsubscribe(Url& url, const char* sid) override {
140-
DlnaLogger.log(DlnaLogLevel::Info, "unsubscribe %s (SID=%s)", url.url(),
141-
sid);
140+
DlnaLogger.log(DlnaLogLevel::Info, "%s %s (SID=%s)", methods[T_UNSUBSCRIBE],
141+
url.path(), sid);
142142
if (sid != nullptr) {
143143
request_header.put("SID", sid);
144144
}
@@ -226,7 +226,7 @@ class HttpRequest : public IHttpRequest {
226226
DlnaLogger.log(DlnaLogLevel::Info, "HttpRequest::connect %s:%d", ip, port);
227227
uint64_t end = millis() + client_ptr->getTimeout();
228228
bool rc = false;
229-
for (int j=0;j < 3; j++) {
229+
for (int j = 0; j < 3; j++) {
230230
rc = this->client_ptr->connect(ip, port);
231231
if (rc) break;
232232
delay(200);

0 commit comments

Comments
 (0)