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;
70 // Initialize event subscription state
72 }
84 void setHttpServer(IHttpServer& server) {
85 DlnaLogger.log(DlnaLogLevel::Debug, "DLNAControlPointMgr::setHttpServer");
86 p_http_server = &server;
87 attachHttpServer(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 setEventSubscriptionActive(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 // 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";
335 path = p_local_url->path();
336
337 void* ctx[1];
338 ctx[0] = this;
339 server.on(path, T_NOTIFY, notifyHandler, ctx, 1);
340 }
341
354 bool subscribeNotifications(bool subscribe = true) {
355 if (p_device == nullptr) return false;
356 if (subscribe) return subscribeToDevice(*p_device);
358 }
371 bool ok = true;
372 for (auto& service : device.getServices()) {
373 if (!subscribeToService(service)) {
374 DlnaLogger.log(DlnaLogLevel::Error, "Subscription to service %s failed",
375 service.service_id);
376 ok = false;
377 }
378 }
379 return ok;
380 }
381
393 bool ok = true;
394 for (auto& service : device.getServices()) {
395 if (!unsubscribeFromService(service)) {
396 DlnaLogger.log(DlnaLogLevel::Warning,
397 "Unsubscribe from service %s failed",
398 service.service_id);
399 ok = false;
400 }
401 }
402 return ok;
403 }
404
420 if (StrView(service.event_sub_url).isEmpty()) {
421 return false;
422 }
423 Url url(service.event_sub_url);
424
425 // If already subscribed and not expired, nothing to do
427 millis() < service.time_subscription_expires) {
428 return true;
429 }
430
431 char seconds_txt[80] = {0};
432 snprintf(seconds_txt, sizeof(seconds_txt), "Second-%d",
434 p_http->request().put("NT", "upnp:event");
435 p_http->request().put("TIMEOUT", seconds_txt);
436 p_http->request().put("CALLBACK", p_local_url ? p_local_url->url() : "");
437 // For re-subscribe, include existing SID header if present
438 if (!service.subscription_id.isEmpty()) {
439 p_http->request().put("SID", service.subscription_id.c_str());
440 }
441
442 int rc = p_http->subscribe(url);
443 if (rc == 200) {
444 const char* sid = p_http->reply().get("SID");
445 if (sid != nullptr) {
446 service.subscription_id = sid;
448 service.time_subscription_started = millis();
449 service.time_subscription_confirmed = millis();
451 millis() + (event_subscription_duration_sec * 1000);
453 DlnaLogger.log(DlnaLogLevel::Info, "Subscribe %s -> rc=%d", url.url(),
454 rc);
455 } else {
456 DlnaLogger.log(DlnaLogLevel::Warning,
457 "Subscribe %s succeeded but no SID returned", url.url());
458 return false;
459 }
460 } else {
461 // failed to subscribe
462 DlnaLogger.log(DlnaLogLevel::Error,
463 "Failed to subscribe to service %s, rc=%d",
464 service.service_id, rc);
465 return false;
466 }
467 return true;
468 }
469
482 if (StrView(service.event_sub_url).isEmpty()) {
483 return false;
484 }
485 // If already subscribed and not expired, nothing to do
487 return true;
488 }
489
490 Url url(service.event_sub_url);
491 int rc = p_http->unsubscribe(url, service.subscription_id.c_str());
492 if (rc == 200) {
493 DlnaLogger.log(DlnaLogLevel::Info, "Unsubscribe %s -> rc=%d", url.url(),
494 rc);
495 // clear SID and metadata regardless of remote rc
496 service.subscription_id = "";
498 service.time_subscription_confirmed = 0;
499 service.time_subscription_expires = 0;
501 } else {
502 DlnaLogger.log(DlnaLogLevel::Error,
503 "Failed to unsubscribe from service %s, rc=%d",
504 service.service_id, rc);
505 return false;
506 }
507 return true;
508 }
509
530 static void notifyHandler(IHttpServer* server, const char* requestPath,
531 HttpRequestHandlerLine* handlerLine) {
532 if (handlerLine == nullptr || handlerLine->contextCount < 1) return;
533 void* ctx0 = handlerLine->context[0];
535 static_cast<SubscriptionMgrControlPoint*>(ctx0);
536 if (!mgr) return;
537 void* reference = mgr->event_callback_ref;
538
539 // read SID header and body
540 const char* sid = server->requestHeader().get("SID");
541
542 // Use XMLParserPrint to incrementally parse the NOTIFY body and extract
543 // property child elements. XMLParserPrint accumulates the buffer and
544 // exposes a parse() method that returns the next fragment.
545
546 // mark notify received early so subscription metadata gets updated
547 mgr->updateReceived(sid);
548
549 XMLParserPrint xml_parser;
550 uint8_t buffer[XML_PARSER_BUFFER_SIZE];
551 Str nodeName;
552 Vector<Str> path;
553 Str text;
554 Str attrs;
555 xml_parser.setExpandEncoded(true);
556 Client& client = server->client();
557
558 while (client.available()) {
559 int read = client.read(buffer, XML_PARSER_BUFFER_SIZE);
560 xml_parser.write(buffer, read);
561
562 while (xml_parser.parse(nodeName, path, text, attrs)) {
563 // We care about text nodes that are children of a <property> element
564 if (text.isEmpty()) continue;
565
566 // dispatch
568 if (mgr->event_callback) {
569 const char* sid_c = sid ? sid : "";
570 mgr->event_callback(sid_c, nodeName.c_str(), text.c_str(), reference);
571 }
572 }
573 }
574
575 // reply OK to the NOTIFY
576 server->replyOK();
577 }
578};
579
580} // 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:25
Vector< DLNAServiceInfo > & getServices()
Provides all service definitions.
Definition: DLNADeviceInfo.h:203
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:15
void ** context
Definition: HttpRequestHandlerLine.h:40
int contextCount
Definition: HttpRequestHandlerLine.h:41
Abstract interface for HTTP client request functionality.
Definition: IHttpRequest.h:21
virtual HttpRequestHeader & request()=0
Get reference to request header.
virtual HttpReplyHeader & reply()=0
Get reference to reply header.
virtual int subscribe(Url &url)=0
Send SUBSCRIBE request for UPnP events.
virtual int unsubscribe(Url &url, const char *sid)=0
Send UNSUBSCRIBE request for UPnP events.
Abstract interface for HTTP server functionality.
Definition: IHttpServer.h:30
virtual void on(const char *url, TinyMethodID method, web_callback_fn fn, void *ctx[]=nullptr, int ctxCount=0)=0
Register callback for HTTP request with context.
virtual void replyOK()=0
Send 200 OK response.
virtual Client & client()=0
Get reference to current client.
virtual HttpRequestHeader & requestHeader()=0
Get reference to request header.
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
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:370
bool subscribeToService(DLNAServiceInfo &service)
Subscribe to a single eventing service described by service.
Definition: SubscriptionMgrControlPoint.h:419
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
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:354
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:392
IHttpServer * p_http_server
Definition: SubscriptionMgrControlPoint.h:230
bool loop()
Periodic work to manage subscription renewals and retries.
Definition: SubscriptionMgrControlPoint.h:198
static void notifyHandler(IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *handlerLine)
HTTP NOTIFY handler registered with HttpServer.
Definition: SubscriptionMgrControlPoint.h:530
bool is_active
Definition: SubscriptionMgrControlPoint.h:220
bool unsubscribeFromService(DLNAServiceInfo &service)
Unsubscribe from a single service.
Definition: SubscriptionMgrControlPoint.h:481
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:84
void end()
Tear down the manager. This clears the internal setup state but does not perform network unsubscribe ...
Definition: SubscriptionMgrControlPoint.h:121
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
void setEventSubscriptionActive(bool active)
Turn automatic event subscription on or off.
Definition: SubscriptionMgrControlPoint.h:172
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:25
Definition: Allocator.h:13
@ T_NOTIFY
Definition: HttpHeader.h:48
SubscriptionState
Subscription State for DLNA eventing.
Definition: DLNACommon.h:13