Arduino DLNA Server
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
DLNAControlPointMgr.h
Go to the documentation of this file.
1 #pragma once
2 
4 #include "DLNADevice.h"
5 #include "DLNADeviceMgr.h"
6 #include "Schedule.h"
7 #include "Scheduler.h"
8 #include "basic/StrPrint.h"
9 #include "basic/Url.h"
10 #include "http/HttpServer.h"
11 #include "xml/XMLDeviceParser.h"
12 
13 namespace tiny_dlna {
14 
15 class DLNAControlPointMgr;
17 
33  public:
36  void setParseDevice(bool flag) { is_parse_device = flag; }
38  void setLocalURL(Url url) { local_url = url; }
39 
52  bool begin(DLNAHttpRequest& http, IUDPService &udp,
53  const char* searchTarget = "ssdp:all", uint32_t processingTime = 0,
54  bool stopWhenFound = true) {
55  DlnaLogger.log(DlnaInfo, "DLNADevice::begin");
56  search_target = searchTarget;
57  is_active = true;
58  p_udp = &udp;
59  p_http = &http;
60 
61  // setup multicast UDP
62  if (!(p_udp->begin(DLNABroadcastAddress))) {
63  DlnaLogger.log(DlnaError, "UDP begin failed");
64  return false;
65  }
66 
67  // Send MSearch request via UDP
68  MSearchSchedule* search =
69  new MSearchSchedule(DLNABroadcastAddress, searchTarget);
70  search->end_time = millis() + 3000;
71  search->repeat_ms = 1000;
72  scheduler.add(search);
73 
74  // if processingTime > 0 we do some loop processing already here
75  uint64_t end = millis() + processingTime;
76  while (millis() < end) {
77  if (stopWhenFound && devices.size() > 0) break;
78  loop();
79  }
80 
81  DlnaLogger.log(DlnaInfo, "Control Point started with %d devices found",
82  devices.size());
83  return devices.size() > 0;
84  }
85 
87  void end() {
88  // p_server->end();
89  for (auto& device : devices) device.clear();
90  is_active = false;
91  }
92 
95  actions.push_back(act);
96  return actions[actions.size() - 1];
97  }
98 
101  ActionReply result = postAllActions();
102  actions.clear();
103  return result;
104  }
105 
107  bool subscribe(const char* serviceName, int seconds) {
108  auto service = getService(serviceName);
109  if (!service) {
110  DlnaLogger.log(DlnaError, "No service found for %s", serviceName);
111  return false;
112  }
113 
114  auto& device = getDevice(service);
115  if (!device) {
116  DlnaLogger.log(DlnaError, "Device not found");
117  return false;
118  }
119 
120  if (StrView(local_url.url()).isEmpty()) {
121  DlnaLogger.log(DlnaError, "Local URL not defined");
122  return false;
123  }
124  char url_buffer[200] = {0};
125  char seconds_txt[80] = {0};
126  Url url{getUrl(device, service.event_sub_url, url_buffer, 200)};
127  snprintf(seconds_txt, 80, "Second-%d", seconds);
128  p_http->request().put("NT", "upnp:event");
129  p_http->request().put("TIMEOUT", seconds_txt);
130  p_http->request().put("CALLBACK", local_url.url());
131  int rc = p_http->subscribe(url);
132  DlnaLogger.log(DlnaInfo, "Http rc: %s", rc);
133  return rc == 200;
134  }
135 
136  // /// Provides the actual state value
137  // const char* getStateValue(const char* name) {
138  // for (auto& st : state) {
139  // if (st.name == name) {
140  // return st.value.c_str();
141  // }
142  // }
143  // return nullptr;
144  // }
145 
148  bool loop() {
149  if (!is_active) return false;
151 
152  // process UDP requests
153  RequestData req = p_udp->receive();
154  if (req) {
155  Schedule* schedule = parser.parse(req);
156  if (schedule != nullptr) {
157  // handle NotifyReplyCP
158  if (StrView(schedule->name()).equals("NotifyReplyCP")) {
159  NotifyReplyCP& notify_schedule = *(NotifyReplyCP*)schedule;
160  notify_schedule.callback = processDevice;
161  }
162  scheduler.add(schedule);
163  }
164  }
165 
166  // execute scheduled udp replys
168 
169  // be nice, if we have other tasks
170  delay(5);
171  return true;
172  }
173 
175  DLNAServiceInfo& getService(const char* id) {
176  static DLNAServiceInfo no_service(false);
177  for (auto& dev : devices) {
178  DLNAServiceInfo& result = dev.getService(id);
179  if (result) return result;
180  }
181  return no_service;
182  }
183 
185  DLNADevice& getDevice(int deviceIdx = 0) { return devices[deviceIdx]; }
186 
189  for (auto& dev : devices) {
190  for (auto& srv : dev.getServices()) {
191  if (srv == srv) return dev;
192  }
193  }
194  return NO_DEVICE;
195  }
196 
198  DLNADevice& getDevice(Url location) {
199  for (auto& dev : devices) {
200  if (dev.getDeviceURL() == location) {
201  return dev;
202  }
203  }
204  return NO_DEVICE;
205  }
206 
208 
210  bool addDevice(DLNADevice dev) {
211  dev.updateTimestamp();
212  for (auto& existing_device : devices) {
213  if (dev.getUDN() == existing_device.getUDN()) {
214  DlnaLogger.log(DlnaInfo, "Device '%s' already exists", dev.getUDN());
215  return false;
216  }
217  }
218  DlnaLogger.log(DlnaInfo, "Device '%s' has been added", dev.getUDN());
219  devices.push_back(dev);
220  return true;
221  }
222 
224  bool addDevice(Url url) {
225  DLNADevice& device = getDevice(url);
226  if (device != NO_DEVICE) {
227  // device already exists
228  device.setActive(true);
229  return true;
230  }
231  // http get
232  StrPrint xml;
233  DLNAHttpRequest req;
234  int rc = req.get(url, "text/xml");
235 
236  if (rc != 200) {
237  DlnaLogger.log(DlnaError, "Http get to '%s' failed with %d", url.url(),
238  rc);
239  req.stop();
240  return false;
241  }
242  // get xml
243  uint8_t buffer[512];
244  while (true) {
245  int len = req.read(buffer, 512);
246  if (len == 0) break;
247  xml.write(buffer, len);
248  }
249  req.stop();
250 
251  // parse xml
252  DLNADevice new_device;
253  XMLDeviceParser parser;
254  parser.parse(new_device, strings, xml.c_str());
255  new_device.device_url = url;
256  devices.push_back(new_device);
257  return true;
258  }
259 
261  void setActive(bool flag) { is_active = flag; }
262 
264  bool isActive() { return is_active; }
265 
266  protected:
269  IUDPService* p_udp = nullptr;
273  bool is_active = false;
274  bool is_parse_device = false;
276  const char* search_target;
279 
281  static bool processDevice(NotifyReplyCP& data) {
282  Str& nts = data.nts;
283  if (nts.equals("ssdp:byebye")) {
285  return true;
286  }
287  if (nts.equals("ssdp:alive")) {
288  bool select = selfDLNAControlPoint->matches(data.usn.c_str());
289  DlnaLogger.log(DlnaInfo, "addDevice: %s -> %s", data.usn.c_str(),
290  select ? "added" : "filtered");
291  Url url{data.location.c_str()};
293  return true;
294  }
295  return false;
296  }
297 
299  bool matches(const char* usn) {
300  if (StrView(search_target).equals("ssdp:all")) return true;
301  return StrView(usn).contains(search_target);
302  }
303 
305  bool processBye(Str& usn) {
306  for (auto& dev : devices) {
307  if (usn.startsWith(dev.getUDN())) {
308  for (auto& srv : dev.getServices()) {
309  srv.is_active = false;
310  if (usn.endsWith(srv.service_type)) {
311  if (srv.is_active) {
312  DlnaLogger.log(DlnaInfo, "removeDevice: %s", usn);
313  srv.is_active = false;
314  }
315  }
316  }
317  }
318  }
319  return false;
320  }
321 
337  size_t createXML(ActionRequest& action) {
338  size_t result = xml.printXMLHeader();
339 
340  result += xml.printNodeBegin(
341  "Envelope",
342  "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
343  "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"",
344  "s");
345  result += xml.printNodeBegin("Body", nullptr, "s");
346 
347  char ns[200];
348  StrView namespace_str(ns, 200);
349  namespace_str = "xmlns:u=\"%1\"";
350  bool ok = namespace_str.replace("%1", action.getServiceType());
351  DlnaLogger.log(DlnaDebug, "ns = '%s'", namespace_str.c_str());
352 
353  // assert(ok);
354  result += xml.printNodeBegin(action.action, namespace_str.c_str(), "u");
355  for (auto arg : action.arguments) {
356  result += xml.printNode(arg.name, arg.value.c_str());
357  }
358  result += xml.printNodeEnd(action.action, "u");
359 
360  result += xml.printNodeEnd("Body", "s");
361  result += xml.printNodeEnd("Envelope", "s");
362  return result;
363  }
364 
366  ActionReply result;
367  for (auto& action : actions) {
368  if (action.getServiceType() != nullptr) result.add(postAction(action));
369  }
370  return result;
371  }
372 
374  DLNAServiceInfo& service = *action.p_service;
375  DLNADevice& device = getDevice(service);
376 
377  // create XML
378  StrPrint str_print;
379  xml.setOutput(str_print);
380  createXML(action);
381 
382  // create SOAPACTION header
383  char act[200];
384  StrView action_str{act, 200};
385  action_str = "\"";
386  action_str.add(action.getServiceType());
387  action_str.add("#");
388  action_str.add(action.action);
389  action_str.add("\"");
390 
391  p_http->request().put("SOAPACTION", action_str.c_str());
392 
393  // crate control url
394  char url_buffer[200] = {0};
395  Url post_url{getUrl(device, service.control_url, url_buffer, 200)};
396 
397  // post the request
398  int rc = p_http->post(post_url, "text/xml", str_print.c_str(),
399  str_print.length());
400 
401  // check result
402  DlnaLogger.log(DlnaInfo, "==> http rc %d", rc);
403  ActionReply result(rc == 200);
404  if (rc != 200) {
405  p_http->stop();
406  return result;
407  }
408 
409  // log xml request
410  DlnaLogger.log(DlnaDebug, str_print.c_str());
411 
412  // receive result
413  str_print.reset();
414  uint8_t buffer[200];
415  while (p_http->client()->available()) {
416  int len = p_http->client()->read(buffer, 200);
417  str_print.write(buffer, len);
418  }
419 
420  // log result
421  DlnaLogger.log(DlnaDebug, str_print.c_str());
422  p_http->stop();
423 
424  return result;
425  }
426 
427  const char* getUrl(DLNADevice& device, const char* suffix, const char* buffer,
428  int len) {
429  StrView url_str{(char*)buffer, len};
430  url_str = device.getBaseURL();
431  if (url_str == nullptr) {
432  url_str.add(device.getDeviceURL().protocol());
433  url_str.add("://");
434  url_str.add(device.getDeviceURL().host());
435  url_str.add(":");
436  url_str.add(device.getDeviceURL().port());
437  }
438  url_str.add(suffix);
439  return buffer;
440  }
441 };
442 
443 } // namespace tiny_dlna
Definition: Action.h:38
void add(ActionReply alt)
Definition: Action.h:44
Definition: Action.h:55
Vector< Argument > arguments
Definition: Action.h:72
const char * getServiceType()
Definition: Action.h:75
DLNAServiceInfo * p_service
Definition: Action.h:70
const char * action
Definition: Action.h:71
Setup of a Basic DLNA Control Point. The control point.
Definition: DLNAControlPointMgr.h:32
void setParseDevice(bool flag)
Requests the parsing of the device information.
Definition: DLNAControlPointMgr.h:36
DLNADevice & getDevice(int deviceIdx=0)
Provides the device information by index.
Definition: DLNAControlPointMgr.h:185
size_t createXML(ActionRequest &action)
Definition: DLNAControlPointMgr.h:337
bool matches(const char *usn)
checks if the usn contains the search target
Definition: DLNAControlPointMgr.h:299
bool isActive()
Checks if the scheduler is active.
Definition: DLNAControlPointMgr.h:264
DLNADevice & getDevice(Url location)
Get a device for a Url.
Definition: DLNAControlPointMgr.h:198
DLNADevice & getDevice(DLNAServiceInfo &service)
Provides the device for a service.
Definition: DLNAControlPointMgr.h:188
bool loop()
Definition: DLNAControlPointMgr.h:148
const char * search_target
Definition: DLNAControlPointMgr.h:276
bool addDevice(Url url)
Adds the device from the device xml url if it does not already exist.
Definition: DLNAControlPointMgr.h:224
const char * getUrl(DLNADevice &device, const char *suffix, const char *buffer, int len)
Definition: DLNAControlPointMgr.h:427
DLNADevice NO_DEVICE
Definition: DLNAControlPointMgr.h:275
ActionReply postAllActions()
Definition: DLNAControlPointMgr.h:365
ActionRequest & addAction(ActionRequest act)
Registers a method that will be called.
Definition: DLNAControlPointMgr.h:94
bool addDevice(DLNADevice dev)
Adds a new device.
Definition: DLNAControlPointMgr.h:210
Vector< DLNADevice > devices
Definition: DLNAControlPointMgr.h:270
ActionReply postAction(ActionRequest &action)
Definition: DLNAControlPointMgr.h:373
static bool processDevice(NotifyReplyCP &data)
Processes a NotifyReplyCP message.
Definition: DLNAControlPointMgr.h:281
DLNAHttpRequest * p_http
Definition: DLNAControlPointMgr.h:268
StringRegistry strings
Definition: DLNAControlPointMgr.h:277
DLNAControlPointMgr()
Definition: DLNAControlPointMgr.h:34
bool is_parse_device
Definition: DLNAControlPointMgr.h:274
Scheduler scheduler
Definition: DLNAControlPointMgr.h:267
IUDPService * p_udp
Definition: DLNAControlPointMgr.h:269
bool subscribe(const char *serviceName, int seconds)
Subscribe to changes.
Definition: DLNAControlPointMgr.h:107
bool begin(DLNAHttpRequest &http, IUDPService &udp, const char *searchTarget="ssdp:all", uint32_t processingTime=0, bool stopWhenFound=true)
Definition: DLNAControlPointMgr.h:52
XMLPrinter xml
Definition: DLNAControlPointMgr.h:272
Url local_url
Definition: DLNAControlPointMgr.h:278
Vector< ActionRequest > actions
Definition: DLNAControlPointMgr.h:271
void setActive(bool flag)
We can activate/deactivate the scheduler.
Definition: DLNAControlPointMgr.h:261
bool processBye(Str &usn)
processes a bye-bye message
Definition: DLNAControlPointMgr.h:305
Vector< DLNADevice > & getDevices()
Definition: DLNAControlPointMgr.h:207
bool is_active
Definition: DLNAControlPointMgr.h:273
void setLocalURL(Url url)
Defines the lacal url (needed for subscriptions)
Definition: DLNAControlPointMgr.h:38
void end()
Stops the processing and releases the resources.
Definition: DLNAControlPointMgr.h:87
ActionReply executeActions()
Executes all registered methods.
Definition: DLNAControlPointMgr.h:100
DLNAServiceInfo & getService(const char *id)
Provide addess to the service information.
Definition: DLNAControlPointMgr.h:175
Translates DLNA UDP Requests to Schedule so that we can schedule a reply.
Definition: DLNAControlPointRequestParser.h:15
Schedule * parse(RequestData &req)
Definition: DLNAControlPointRequestParser.h:17
Device Attributes and generation of XML using urn:schemas-upnp-org:device-1-0. We could just return a...
Definition: DLNADevice.h:27
void setActive(bool flag)
Definition: DLNADevice.h:158
Url & getDeviceURL()
This method returns base url/device.xml.
Definition: DLNADevice.h:67
Url device_url
Definition: DLNADevice.h:170
const char * getBaseURL()
Provides the base url.
Definition: DLNADevice.h:58
const char * getUDN()
Provide the udn uuid.
Definition: DLNADevice.h:52
void updateTimestamp()
Update the timestamp.
Definition: DLNADevice.h:153
Attributes needed for the DLNA Service Definition.
Definition: DLNAServiceInfo.h:16
const char * control_url
Definition: DLNAServiceInfo.h:36
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:103
Simple API to process get, put, post, del http requests I tried to use Arduino HttpClient,...
Definition: HttpRequest.h:21
virtual HttpRequestHeader & request()
Definition: HttpRequest.h:116
virtual int subscribe(Url &url)
Definition: HttpRequest.h:89
virtual int get(Url &url, const char *acceptMime=nullptr, const char *data=nullptr, int len=-1)
Definition: HttpRequest.h:75
Client * client()
Definition: HttpRequest.h:128
virtual int post(Url &url, const char *mime, const char *data, int len=-1)
Definition: HttpRequest.h:59
virtual void stop()
Definition: HttpRequest.h:54
virtual int read(uint8_t *str, int len)
Definition: HttpRequest.h:95
Abstract Interface for UDP API.
Definition: IUDPService.h:34
virtual RequestData receive()=0
virtual bool begin(int port)=0
void log(DlnaLogLevel current_level, const char *fmt...)
Print log message.
Definition: Logger.h:40
Str usn
Definition: Schedule.h:125
Str location
Definition: Schedule.h:124
Send MSearch request.
Definition: Schedule.h:39
Definition: Schedule.h:135
std::function< bool(NotifyReplyCP &ref)> callback
Definition: Schedule.h:146
Str nts
Definition: Schedule.h:138
Scheduler which processes all due Schedules (to send out UDP replies)
Definition: Scheduler.h:15
void execute(IUDPService &udp)
Execute all due schedules.
Definition: Scheduler.h:25
void add(Schedule *schedule)
Add a schedule to the scheduler.
Definition: Scheduler.h:18
Definition: StrPrint.h:11
size_t length()
Definition: StrPrint.h:32
size_t write(uint8_t ch) override
Definition: StrPrint.h:14
void reset()
Definition: StrPrint.h:34
const char * c_str()
Definition: StrPrint.h:30
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:19
virtual void add(int value)
adds a int value
Definition: StrView.h:124
virtual bool isEmpty()
checks if the string is empty
Definition: StrView.h:376
virtual bool startsWith(const char *str)
checks if the string starts with the indicated substring
Definition: StrView.h:179
virtual const char * c_str()
provides the string value as const char*
Definition: StrView.h:369
virtual bool endsWith(const char *str)
checks if the string ends with the indicated substring
Definition: StrView.h:187
virtual bool replace(const char *toReplace, const int replaced)
Replaces the first instance of toReplace with replaced.
Definition: StrView.h:388
virtual bool equals(const char *str)
checks if the string equals indicated parameter string
Definition: StrView.h:173
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: StrView.h:279
String implementation which keeps the data on the heap. We grow the allocated memory only if the copy...
Definition: Str.h:22
const char * c_str()
provides the string value as const char*
Definition: Str.h:188
Definition: StringRegistry.h:9
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:18
int port()
Definition: Url.h:51
const char * protocol()
Definition: Url.h:49
const char * url()
Definition: Url.h:46
const char * host()
Definition: Url.h:48
Vector implementation which provides the most important methods as defined by std::vector....
Definition: Vector.h:21
Parses an DLNA device xml string to fill the DLNADevice data structure.
Definition: XMLDeviceParser.h:17
void parse(DLNADevice &result, StringRegistry &strings, const char *xmlStr)
Definition: XMLDeviceParser.h:19
Definition: Allocator.h:6
@ DlnaDebug
Definition: Logger.h:16
@ DlnaInfo
Definition: Logger.h:16
@ DlnaError
Definition: Logger.h:16
DLNAControlPointMgr * selfDLNAControlPoint
Definition: DLNAControlPointMgr.h:16
LoggerClass DlnaLogger
Definition: Logger.cpp:5
Provides information of the received UDP which consists of the (xml) data and the peer address and po...
Definition: IUDPService.h:23
An individual Schedule (to send out UDP messages)
Definition: Schedule.h:17
uint64_t end_time
Definition: Schedule.h:24
virtual const char * name()
Definition: Schedule.h:30
uint32_t repeat_ms
Definition: Schedule.h:22
Functions to efficiently output XML. XML data contains a lot of redundancy so it is more memory effic...
Definition: XMLPrinter.h:31
size_t printNodeBegin(const char *node, const char *attributes=nullptr, const char *ns=nullptr)
Definition: XMLPrinter.h:97
size_t printNode(XMLNode node)
Definition: XMLPrinter.h:43
size_t printNodeEnd(const char *node, const char *ns=nullptr)
Definition: XMLPrinter.h:122
void setOutput(Print &output)
Defines the output.
Definition: XMLPrinter.h:36
size_t printXMLHeader()
Definition: XMLPrinter.h:38