Arduino DLNA Server
Loading...
Searching...
No Matches
SubscriptionMgrControlPoint.h
Go to the documentation of this file.
1
2#pragma once
3
4#include <functional>
5
6#include "basic/Url.h"
7#include "basic/Vector.h"
12#include "dlna_config.h"
13#include "http/Http.h"
16
17namespace tiny_dlna {
45 public:
62 void setup(IHttpRequest& http, IUDPService& udp, Url& localCallbackUrl,
63 DLNADeviceInfo& device) {
64 p_http = &http;
65 p_udp = &udp;
66 // store pointers to shared objects (caller/owner manages lifetime)
67 p_local_url = &localCallbackUrl;
68 p_device = &device;
69 is_setup = true;
71 // Initialize event subscription state
73 }
85 void setHttpServer(IHttpServer& server) {
86 DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::setHttpServer");
87 p_http_server = &server;
88 }
89
93 bool hasEventSubscriptionCallback() { return event_callback != nullptr; }
94
109 std::function<void(const char* sid, const char* varName,
110 const char* newValue, void* reference)>
111 callback,
112 void* ref = nullptr) {
113 event_callback = callback;
114 event_callback_ref = ref;
115 }
116
121 void end() {
122 // Reset pointers and flags that were initialized by setup()
123 p_http = nullptr;
124 p_udp = nullptr;
125 p_local_url = nullptr;
126 p_device = nullptr;
127 // Clear manager state related to active subscriptions
128 is_setup = false;
134 // leave p_http_server alone (owner may manage server lifecycle)
135 }
136
146 void setEventSubscriptionDurationSec(int seconds = 3600) {
148 }
149
160
172 void setNotificationsActive(bool active) {
174
175 // if not setup, ignore
176 if (!is_setup) return;
177
179
180 if (active) {
181 is_active = true;
182 }
183 }
184
189
198 bool loop() {
199 if (!isActive()) return false;
200 if (!is_setup) return false;
201
202 if (processing_timeout == 0 || millis() < processing_timeout) {
205 return true;
206 }
207 return false;
208 }
209
216 bool isActive() { return is_active; }
217
218 protected:
219 void* event_callback_ref = nullptr;
220 bool is_active = false;
222 bool is_setup = false;
223 uint64_t subscribe_timeout = 0;
227 uint64_t last_event_notify_ms = 0; // timestamp of last received NOTIFY
229 IUDPService* p_udp = nullptr;
231 Url* p_local_url = nullptr;
234 uint64_t processing_timeout = 0;
235 std::function<void(const char* sid, const char* varName, const char* newValue,
236 void* reference)>
237 event_callback = [](const char* sid, const char* varName,
238 const char* newValue, void* reference) {
239 // Default simple logger: print SID, variable name and new value.
240 DlnaLogger.log(DlnaLogLevel::Debug,
241 "- Event notification: SID='%s' var='%s' value='%s'",
242 sid ? sid : "", varName ? varName : "",
243 newValue ? newValue : "");
244 };
245
266 // subscribe
271 }
272 // or unsubscribe
277 }
278 }
279
290 void updateReceived(const char* sid) {
291 last_event_notify_ms = millis();
292 DLNAServiceInfo& service = getServiceInfoBySID(sid);
293 if (&service != &NO_SERVICE) {
294 service.time_subscription_confirmed = millis();
296 }
297 }
298
299 // Find service info by matching stored SID. Returns NO_SERVICE if not
300 // found.
302 if (sid == nullptr) return NO_SERVICE;
303
304 // Search single selected device first (if set)
305 if (p_device != nullptr) {
306 for (auto& service : p_device->getServices()) {
307 const char* ssid = service.subscription_id.c_str();
308 if (ssid != nullptr && strcmp(ssid, sid) == 0) return service;
309 }
310 }
311
312 return NO_SERVICE;
313 }
314
328 DlnaLogger.log(DlnaLogLevel::Debug,
329 "DLNAControlPointMgr::attachHttpServer");
330 p_http_server = &server;
331 void* ctx[1];
332 ctx[0] = this;
333 const char* path = p_local_url->path();
334 server.on(path, T_NOTIFY, notifyHandler, ctx, 1);
335 }
336
349 bool subscribeNotifications(bool subscribe = true) {
350 if (p_device == nullptr) return false;
351 if (subscribe) return subscribeToDevice(*p_device);
353 }
366 DlnaLogger.log(DlnaLogLevel::Info, "subscribeToDevice");
367 bool ok = true;
368 for (auto& service : device.getServices()) {
369 if (!subscribeToService(service)) {
370 DlnaLogger.log(DlnaLogLevel::Error, "Subscription to service %s failed",
371 service.service_id.c_str());
372 ok = false;
373 }
374 }
375 return ok;
376 }
377
389 DlnaLogger.log(DlnaLogLevel::Info, "unsubscribeFromDevice");
390 bool ok = true;
391 for (auto& service : device.getServices()) {
392 if (!unsubscribeFromService(service)) {
393 DlnaLogger.log(DlnaLogLevel::Warning,
394 "Unsubscribe from service %s failed",
395 service.service_id.c_str());
396 ok = false;
397 }
398 }
399 return ok;
400 }
401
417 if (StrView(service.event_sub_url).isEmpty()) {
418 return false;
419 }
420
421 Url url(getURLString(service).c_str());
422
423 // If already subscribed and not expired, nothing to do
425 millis() < service.time_subscription_expires) {
426 return true;
427 }
428
429 char seconds_txt[80] = {0};
430 snprintf(seconds_txt, sizeof(seconds_txt), "Second-%d",
432 char callback_url[256] = {0};
433 snprintf(callback_url, sizeof(callback_url), "<%s>",
434 p_local_url ? p_local_url->url() : "");
435 p_http->request().put("NT", "upnp:event");
436 p_http->request().put("TIMEOUT", seconds_txt);
437 p_http->request().put("CALLBACK", callback_url);
438 // For re-subscribe, include existing SID header if present
439 if (!service.subscription_id.isEmpty()) {
440 p_http->request().put("SID", service.subscription_id.c_str());
441 }
442 p_http->request().put("CONTENT-LENGTH", "0");
443
444 int rc = p_http->subscribe(url);
445 if (!p_http->isKeepAlive()) {
446 p_http->stop();
447 }
448 if (rc == 200) {
449 const char* sid = p_http->reply().get("SID");
450 if (sid != nullptr) {
451 service.subscription_id = sid;
453 service.time_subscription_started = millis();
454 service.time_subscription_confirmed = millis();
456 millis() + (event_subscription_duration_sec * 1000);
458 DlnaLogger.log(DlnaLogLevel::Info, "Subscribe %s -> rc=%d", url.url(),
459 rc);
460 } else {
461 DlnaLogger.log(DlnaLogLevel::Warning,
462 "Subscribe %s succeeded but no SID returned", url.url());
463 return false;
464 }
465 } else {
466 // failed to subscribe
467 DlnaLogger.log(DlnaLogLevel::Error,
468 "Failed to subscribe to service %s, rc=%d",
469 service.service_id.c_str(), rc);
470 return false;
471 }
472 return true;
473 }
474
487 if (StrView(service.event_sub_url).isEmpty()) {
488 return false;
489 }
490 // If already subscribed and not expired, nothing to do
492 return true;
493 }
494
495 Url url(getURLString(service).c_str());
496 int rc = p_http->unsubscribe(url, service.subscription_id.c_str());
497 if (rc == 200) {
498 DlnaLogger.log(DlnaLogLevel::Info, "Unsubscribe %s -> rc=%d", url.url(),
499 rc);
500 // clear SID and metadata regardless of remote rc
501 service.subscription_id = "";
503 service.time_subscription_confirmed = 0;
504 service.time_subscription_expires = 0;
506 } else {
507 DlnaLogger.log(DlnaLogLevel::Error,
508 "Failed to unsubscribe from service %s, rc=%d",
509 service.service_id.c_str(), rc);
510 return false;
511 }
512 return true;
513 }
514
535 static void notifyHandler(IClientHandler& client, IHttpServer* server,
536 const char* requestPath,
537 HttpRequestHandlerLine* handlerLine) {
538 if (handlerLine == nullptr || handlerLine->contextCount < 1) return;
539 void* ctx0 = handlerLine->context[0];
541 static_cast<SubscriptionMgrControlPoint*>(ctx0);
542 if (!mgr) return;
543 void* reference = mgr->event_callback_ref;
544
545 // read SID header and body
546 const char* sid = client.requestHeader().get("SID");
547
548 // mark notify received early so subscription metadata gets updated
549 mgr->updateReceived(sid);
550
551 XMLParserPrint xml_parser;
552 uint8_t buffer[XML_PARSER_BUFFER_SIZE];
553 Str nodeName;
554 Vector<Str> path;
555 Str text;
556 Str attrs;
557 xml_parser.setExpandEncoded(true);
558 Client* p_client = client.client();
559 while (p_client && p_client->available()) {
560 int read = p_client->read(buffer, XML_PARSER_BUFFER_SIZE);
561 xml_parser.write(buffer, read);
562
563 while (xml_parser.parse(nodeName, path, text, attrs)) {
564 // We care about text nodes that are children of a <property> element
565 if (text.isEmpty()) continue;
566
567 // dispatch
569 if (mgr->event_callback) {
570 const char* sid_c = sid ? sid : "";
571 mgr->event_callback(sid_c, nodeName.c_str(), text.c_str(), reference);
572 }
573 }
574 }
575
576 // reply OK to the NOTIFY
577 client.replyOK();
578 }
579
581 Str url_str{100};
582 if (service.event_sub_url.startsWith("/")) {
583 // relative URL: need to build full URL using device base URL
584 url_str = p_device->getBaseURL();
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();
589
590 } else {
591 url_str = service.event_sub_url.c_str();
592 }
593 return url_str;
594 }
595};
596
597} // namespace tiny_dlna
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADeviceInfo.h:28
Vector< DLNAServiceInfo > & getServices()
Provides all service definitions.
Definition: DLNADeviceInfo.h:205
const char * getBaseURL()
Provides the base url.
Definition: DLNADeviceInfo.h:120
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:18
uint64_t time_subscription_confirmed
timestamp when subscription was confirmed
Definition: DLNAServiceInfo.h:51
SubscriptionState subscription_state
Definition: DLNAServiceInfo.h:49
uint64_t time_subscription_expires
timestamp when subscription expires
Definition: DLNAServiceInfo.h:52
Str subscription_id
for subscriptions
Definition: DLNAServiceInfo.h:48
Str event_sub_url
Definition: DLNAServiceInfo.h:41
Str service_id
Definition: DLNAServiceInfo.h:38
uint64_t time_subscription_started
timestamp when subscription started
Definition: DLNAServiceInfo.h:50
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:105
const char * get(const char *key)
Definition: HttpHeader.h:161
Used to register and process callbacks.
Definition: HttpRequestHandlerLine.h:12
void ** context
Definition: HttpRequestHandlerLine.h:37
int contextCount
Definition: HttpRequestHandlerLine.h:38
Definition: IHttpServer.h:19
virtual HttpRequestHeader & requestHeader()=0
virtual void replyOK()=0
virtual Client * client()=0
Abstract interface for HTTP client request functionality.
Definition: IHttpRequest.h:21
virtual HttpRequestHeader & request()=0
Get reference to request header.
virtual void stop()=0
Stop the connection.
virtual HttpReplyHeader & reply()=0
Get reference to reply header.
virtual int subscribe(Url &url)=0
Send SUBSCRIBE request for UPnP events.
virtual bool isKeepAlive()=0
Do not close the connection after request.
virtual int unsubscribe(Url &url, const char *sid)=0
Send UNSUBSCRIBE request for UPnP events.
Abstract interface for HTTP server functionality.
Definition: IHttpServer.h:50
virtual void on(const char *url, TinyMethodID method, web_callback_fn fn, void *ctx[]=nullptr, int ctxCount=0)=0
Abstract Interface for UDP API.
Definition: IUDPService.h:33
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:18
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:383
Heap-backed string utility used throughout tiny_dlna.
Definition: Str.h:27
bool isEmpty() const
True if empty.
Definition: Str.h:54
bool startsWith(const char *prefix) const
True if starts with prefix (case-sensitive)
Definition: Str.h:206
const char * c_str() const
C-string pointer to internal buffer.
Definition: Str.h:88
Standalone manager for UPnP/DLNA event subscriptions used by control points and devices.
Definition: SubscriptionMgrControlPoint.h:44
uint64_t getLastEventNotifyMs()
Get the timestamp of the last received NOTIFY message.
Definition: SubscriptionMgrControlPoint.h:188
bool event_subscription_active
Definition: SubscriptionMgrControlPoint.h:226
std::function< void(const char *sid, const char *varName, const char *newValue, void *reference)> event_callback
Definition: SubscriptionMgrControlPoint.h:237
void updateReceived(const char *sid)
Update internal and service metadata when a NOTIFY is received.
Definition: SubscriptionMgrControlPoint.h:290
uint64_t last_event_notify_ms
Definition: SubscriptionMgrControlPoint.h:227
void setEventSubscriptionCallback(std::function< void(const char *sid, const char *varName, const char *newValue, void *reference)> callback, void *ref=nullptr)
Register a callback invoked when event properties change.
Definition: SubscriptionMgrControlPoint.h:108
bool subscribeToDevice(DLNADeviceInfo &device)
Subscribe to all services exposed by device.
Definition: SubscriptionMgrControlPoint.h:365
bool subscribeToService(DLNAServiceInfo &service)
Subscribe to a single eventing service described by service.
Definition: SubscriptionMgrControlPoint.h:416
IHttpRequest * p_http
Definition: SubscriptionMgrControlPoint.h:228
bool isActive()
Query whether the manager is currently active.
Definition: SubscriptionMgrControlPoint.h:216
DLNADeviceInfo * p_device
Definition: SubscriptionMgrControlPoint.h:232
static void notifyHandler(IClientHandler &client, IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *handlerLine)
HTTP NOTIFY handler registered with HttpServer.
Definition: SubscriptionMgrControlPoint.h:535
void setEventSubscriptionRetryMs(int ms)
Set the retry threshold in milliseconds.
Definition: SubscriptionMgrControlPoint.h:159
bool is_setup
Definition: SubscriptionMgrControlPoint.h:222
bool subscribeNotifications(bool subscribe=true)
Convenience to subscribe or unsubscribe all services of the currently selected device.
Definition: SubscriptionMgrControlPoint.h:349
Url * p_local_url
Definition: SubscriptionMgrControlPoint.h:231
bool hasEventSubscriptionCallback()
Return true when an application notification callback is set.
Definition: SubscriptionMgrControlPoint.h:93
IUDPService * p_udp
Definition: SubscriptionMgrControlPoint.h:229
void * event_callback_ref
Definition: SubscriptionMgrControlPoint.h:219
DLNAServiceInfo NO_SERVICE
Definition: SubscriptionMgrControlPoint.h:233
bool unsubscribeFromDevice(DLNADeviceInfo &device)
Unsubscribe from all services of device.
Definition: SubscriptionMgrControlPoint.h:388
IHttpServer * p_http_server
Definition: SubscriptionMgrControlPoint.h:230
bool loop()
Periodic work to manage subscription renewals and retries.
Definition: SubscriptionMgrControlPoint.h:198
Str getURLString(DLNAServiceInfo &service)
Definition: SubscriptionMgrControlPoint.h:580
bool is_active
Definition: SubscriptionMgrControlPoint.h:220
bool unsubscribeFromService(DLNAServiceInfo &service)
Unsubscribe from a single service.
Definition: SubscriptionMgrControlPoint.h:486
SubscriptionState subscription_state
Definition: SubscriptionMgrControlPoint.h:221
void attachHttpServer(IHttpServer &server)
Locate a DLNAServiceInfo by its stored SID.
Definition: SubscriptionMgrControlPoint.h:327
void updateSubscriptions()
Evaluate and apply subscription state transitions.
Definition: SubscriptionMgrControlPoint.h:265
void setHttpServer(IHttpServer &server)
Attach an HttpServer instance and register the internal NOTIFY handler.
Definition: SubscriptionMgrControlPoint.h:85
void end()
Tear down the manager. This clears the internal setup state but does not perform network unsubscribe ...
Definition: SubscriptionMgrControlPoint.h:121
void setNotificationsActive(bool active)
Turn automatic event subscription on or off.
Definition: SubscriptionMgrControlPoint.h:172
DLNAServiceInfo & getServiceInfoBySID(const char *sid)
Definition: SubscriptionMgrControlPoint.h:301
void setEventSubscriptionDurationSec(int seconds=3600)
Configure desired subscription duration in seconds.
Definition: SubscriptionMgrControlPoint.h:146
uint32_t event_subscription_retry_ms
Definition: SubscriptionMgrControlPoint.h:225
uint32_t event_subscription_duration_sec
Definition: SubscriptionMgrControlPoint.h:224
void setup(IHttpRequest &http, IUDPService &udp, Url &localCallbackUrl, DLNADeviceInfo &device)
Inject required dependencies and initialize the manager.
Definition: SubscriptionMgrControlPoint.h:62
uint64_t processing_timeout
Definition: SubscriptionMgrControlPoint.h:234
uint64_t subscribe_timeout
Definition: SubscriptionMgrControlPoint.h:223
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:18
const char * url()
Definition: Url.h:39
const char * path()
Definition: Url.h:40
Lightweight wrapper around std::vector with Arduino-friendly helpers and a pluggable allocator.
Definition: Vector.h:39
Helper class that implements a Print interface to accumulate XML data and then parse it using XMLPars...
Definition: XMLParserPrint.h:16
size_t write(uint8_t ch) override
Writes a single byte to the buffer (Print interface)
Definition: XMLParserPrint.h:32
void setExpandEncoded(bool flag)
Forwards expand-entities setting to the underlying XMLParser.
Definition: XMLParserPrint.h:48
bool parse(Str &outNodeName, Vector< Str > &outPath, Str &outText, Str &outAttributes)
Parses the accumulated XML data and returns results via output parameters.
Definition: XMLParserPrint.h:59
#define XML_PARSER_BUFFER_SIZE
Define XML parse buffer size.
Definition: dlna_config.h:35
Definition: Allocator.h:13
@ T_NOTIFY
Definition: HttpHeader.h:48
SubscriptionState
Subscription State for DLNA eventing.
Definition: DLNACommon.h:13