luc-github / esp32ssdp Goto Github PK
View Code? Open in Web Editor NEWSimple SSDP library for ESP32
License: GNU General Public License v3.0
Simple SSDP library for ESP32
License: GNU General Public License v3.0
The uuid needs to be able be changed from its default.
Hi, thankyou for the nice lib
I have a problem with this library when using WiFi in the mode of Access Point. When in WiFi Client or STA mode everything is OK. I don't know if this library can support working in AP mode or not?
Thankyou for the help.
By the way, ,i found this peace of code in the library file:
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
return IPAddress(ip.ip.addr);
}
SSDPClass::localIP() retrung wrong IP if WiFi.getMode() == WIFI_AP_STA.
This leads to wrong ip in description.xml as this case is not covered. Device will not be detected in Windows 10.
A partially solution is to add this case in the condition, but this solution will only work for devices connected via station not for devices connected to AP:
IPAddress SSDPClass::localIP(){ tcpip_adapter_ip_info_t ip; if (WiFi.getMode() == WIFI_STA) { if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) { return IPAddress(); } } else if (WiFi.getMode() == WIFI_AP_STA) { // added workaround if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) { return IPAddress(); } } else if (WiFi.getMode() == WIFI_OFF) { if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) { return IPAddress(); } } return IPAddress(ip.ip.addr); }
Describe the bug
Changes in rel 3.0.0 of esp32 result in compilation failures
. . . \libraries\ESP32SSDP-1.2.1\src\ESP32SSDP.cpp:158:5: error: 'tcpip_adapter_ip_info_t' was not declared in this scope; did you mean 'tcpip_adapter_if_t'?
158 | tcpip_adapter_ip_info_t ip;
| ^~~~~~~~~~~~~~~~~~~~~~~
. . . \libraries\ESP32SSDP-1.2.1\src\ESP32SSDP.cpp:160:13: error: 'tcpip_adapter_get_ip_info' was not declared in this scope; did you mean 'tcpip_adapter_if_t'?
160 | if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
| ^~~~~~~~~~~~~~~~~~~~~~~~~
To Reproduce
Use esp32 rel 3.0.0
Expected behavior
Compilation without errors
Firmware:
Board used (please complete the following information):
SSDP.schema() is deprecated but still used in your samples,
Can you please update the sample code with SSDP.getSchema() because I can not get it working this way.
In LICENSE file and project indicated this is GPL-3 project, however in source code, there is MIT in place.
I want to double confirm which LIC is for this project? Thank you
Currently the API accepts a single device type. UPNP clients normally look for a root device then read device xml. The xml contains the real device type but the search target header is
"ST: upnp:rootdevice"
So setting the device SSDP.setDeviceType() to say upnp:rootdevice works for discovery to start but fails because the xml device info schema will contain the wrong device type.
<deviceType>upnp:rootdevice</deviceType>
In my case my actual device type is "urn:schemas-upnp-org:device:Basic:1" but I need to respond to "upnp:rootdevice" and "ssdp:all" also the search term reply header is supposed to be the same as the ST in the search regardless of the actual device type.
Maybe this is best handled in a callback of some type? The ability to respond to multiple device types is a good feature but in most cases just one is enough provided the above rootdevice and ssdp:all are also returning a valid reply.
Please consider implementing a library version that is compatible with 3.x and pre-3.x.
using if (ESP_ARDUINO_VERSION_MAJOR < 3)
It is hard for typical Arduino users to distinguish between versions so a version 2.0 that runs with both ESP32 board adoptions is probably welcome.
e.g.:
IPAddress SSDPClass::localIP() {
#if (ESP_ARDUINO_VERSION_MAJOR < 3)
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
return IPAddress(ip.ip.addr);
}
#else
esp_netif_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_WIFI_STA), &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_ETH), &ip)) {
return IPAddress();
}
}
#endif
return IPAddress(ip.ip.addr);
}
Thanks for your library.
Describe the bug
Studying the universal plug and play device arhitecture HERE on page 48-49. I noticed why my details from description.xml is not displayed correctly. The iconList
attribute is outside of the device
attribute. That causes various clients to not be able to process the data that we need to share.
To Reproduce
The basic example present inside the project is enough to prove my point.
Take a look using WiFiman
Expected behavior
Details regarding my device to be displayed correctly since I comply with the protocol
Firmware:
Board used (please complete the following information):
Additional context
The following xml is provided
Following is what I get using the basic example, but fixed it by modifying the library you awesomely provided
<root>
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<URLBase>http://192.168.0.118:80/</URLBase>
<device>
<deviceType>my device name</deviceType>
<friendlyName>my device name</friendlyName>
<presentationURL>/</presentationURL>
<serialNumber>123456789</serialNumber>
<modelName>my model name</modelName>
<modelDescription>a model description</modelDescription>
<modelNumber>AA:BB:CC:11:22:33</modelNumber>
<modelURL>http://example.com</modelURL>
<manufacturer>a manufacturer</manufacturer>
<manufacturerURL>https://www.manufacturer.com</manufacturerURL>
<UDN>uuid:322345342-444s44-1242-812juinflsd</UDN>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>
<SCPDURL>/SwitchPower1.xml</SCPDURL>
<controlURL>/SwitchPower/Control</controlURL>
<eventSubURL>/SwitchPower/Event</eventSubURL>
</service>
</serviceList>
<iconList>
<icon>
<mimetype>image/png</mimetype>
<height>48</height>
<width>48</width>
<depth>24</depth>
<url>icon48.png</url>
</icon>
</iconList>
</device>
</root>
What you render is (take a look at the iconList location (is outside of the device tag):
<root>
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<URLBase>http://192.168.0.108:80/</URLBase>
<device>
<deviceType>my device type</deviceType>
<friendlyName>a very friendly name</friendlyName>
<presentationURL>/</presentationURL>
<serialNumber>1234567890</serialNumber>
<modelName>a model name</modelName>
<modelDescription/>
<modelNumber>AA:BB:CC:11:22:33</modelNumber>
<modelURL>https://www.model-url.com</modelURL>
<manufacturer>the manufacturer</manufacturer>
<manufacturerURL>https://www.manufacturer.com</manufacturerURL>
<UDN>uuid:322345342-444s44-1242-812juinflsd</UDN>
<serviceList/>
</device>
<iconList/>
</root>
Why is the name in library.properties "ESP32SSPD" instead of "ESP32SSDP" (last two characters transposed)?
IPAddress SSDPClass::localIP(){
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
return IPAddress(ip.ip.addr);
}
when closing should send a notification broadcast with : NTS: ssdp:byebye
It is not a big issue but better fix it to clear others devices cache more quickly
Need the ability to add sub elements to <device> such as <serviceList>
From the spec for the SERVER product token RFC docs.
"Server implementors are encouraged to make this field a configurable option."
It is partially a security issue but it also allows me to more clearly identify the service.
https://www.w3.org/Protocols/HTTP/1.1/rfc2616bis/draft-lafon-rfc2616bis-03.html#rfc.section.14.38
I use modelDescription for the code I am porting. It is not critical but seems to be one of the more common device elements.
Describe the bug
Using the example for the "async" method, I realized that I only get a response when using STA mode, in APSTA hybrid mode, responses are not sent to multicast (unlike what happens on the 8266, I'm migrating to esp32).
To Reproduce
I used the async web server example contained in ESPAsyncWebSrv.h and added the basics of the ESP32SSDP.h example (also async)
`//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebSrv.h>
#include "ESP32SSDP.h"
AsyncWebServer server(80);
const char* ssid = "XXXXXXXXXXXXX";
const char* password = "XXXXXXXXXXXXX";
const char* PARAM_MESSAGE = "message";
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_MODE_APSTA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
// Send a GET request to <IP>/get?message=<message>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
String message;
if (request->hasParam(PARAM_MESSAGE)) {
message = request->getParam(PARAM_MESSAGE)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, GET: " + message);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
String message;
if (request->hasParam(PARAM_MESSAGE, true)) {
message = request->getParam(PARAM_MESSAGE, true)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, POST: " + message);
});
server.on("/description.xml", HTTP_GET, [&](AsyncWebServerRequest *request) {
request->send(200, "text/xml", SSDP.schema(false));
});
server.onNotFound(notFound);
server.begin();
//set schema xml url, nees to match http handler
//"ssdp/schema.xml" if not set
SSDP.setSchemaURL("description.xml");
//set port
//80 if not set
SSDP.setHTTPPort(80);
//set device name
//Null string if not set
SSDP.setName("Philips hue clone");
//set Serial Number
//Null string if not set
SSDP.setSerialNumber("001788102201");
//set device url
//Null string if not set
SSDP.setURL("index.html");
//set model name
//Null string if not set
SSDP.setModelName("Philips hue bridge 2012");
//set model description
//Null string if not set
SSDP.setModelDescription("This device can be controled by WiFi");
//set model number
//Null string if not set
SSDP.setModelNumber("929000226503");
//set model url
//Null string if not set
SSDP.setModelURL("http://www.meethue.com");
//set model manufacturer name
//Null string if not set
SSDP.setManufacturer("Royal Philips Electronics");
//set model manufacturer url
//Null string if not set
SSDP.setManufacturerURL("http://www.philips.com");
//set device type
//"urn:schemas-upnp-org:device:Basic:1" if not set
SSDP.setDeviceType("rootdevice"); //to appear as root device, other examples: MediaRenderer, MediaServer ...
//set server name
//"Arduino/1.0" if not set
SSDP.setServerName("SSDPServer/1.0");
//set UUID, you can use https://www.uuidgenerator.net/
//use 38323636-4558-4dda-9188-cda0e6 + 4 last bytes of mac address if not set
//use SSDP.setUUID("daa26fa3-d2d4-4072-bc7a-a1b88ab4234a", false); for full UUID
SSDP.setUUID("daa26fa3-d2d4-4072-bc7a");
//Set icons list, NB: optional, this is ignored under windows
SSDP.setIcons( "<icon>"
"<mimetype>image/png</mimetype>"
"<height>48</height>"
"<width>48</width>"
"<depth>24</depth>"
"<url>icon48.png</url>"
"</icon>");
//Set service list, NB: optional for simple device
SSDP.setServices( "<service>"
"<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>"
"<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>"
"<SCPDURL>/SwitchPower1.xml</SCPDURL>"
"<controlURL>/SwitchPower/Control</controlURL>"
"<eventSubURL>/SwitchPower/Event</eventSubURL>"
"</service>");
Serial.println("Starting SSDP...");
if ( SSDP.begin()){
Serial.println("SSDP started");
}else{
Serial.println("SSDP Fail");
}
// WiFi.mode(WIFI_MODE_APSTA);
}
void loop() {
}`
Expected behavior
SSDP response sent to broadcast (Show in windows network) in both STA and APSTA modes.
Screenshots
Firmware:
Board used (please complete the following information):
I got report that sometimes ssdp crash esp3dlib, disabling ssdp solve the issue.
The decoding stack show ssdp
I am not able to reproduce as it may be linked to network environment.
My guess is the timer is overflowed by too many packets and the update happen when current update is still ongoing.
3 possibles solutions:
1 - disable timer when update start then enable when done
2 - move update to a dedicated task instead of using timer
3 - Use AsyncUdp
TBD
When SSDP library is used on ESP32 with other software that consumes lots of RAM, then it might happen that WiFiudp library tries to allocate 1460 Bytes using new operator (in parsePacket() function), but it throws an error (std::bad_alloc), that lead to reboot of ESP32. The reboot can be prevented if in ESP32SSDP.cpp the line number 322:
nbBytes= _server->parsePacket();
replaced by:
try {nbBytes= _server->parsePacket();} catch (const std::bad_alloc&) { return;}
Section 1.2.3 swww.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
"To be found, a device must send a UDP response to the source IP address and port that sent the request to the multicast channel. Devices respond if the ST header of the M-SEARCH request is “ssdp:all”, “upnp:rootdevice”, “uuid:” followed by a UUID that exactly matches one advertised by the device."
It will respond with the device type instead.
Line 210 in 4ac65d8
I suggest just keeping track of the ST and returning it "if" a matching device is found. This also is an issue with the current logic on match device types I will discuss in a feature request to replay to ssdp:all and rootdevice as if they are the same as asking for the actual device type.
Using platformio to compile https://github.com/sharandac/My-TTGO-Watch (which uses this library), gives a warning:
.pio\libdeps\ttgo-t-watch\ESP32SSPD\ESP32SSDP.cpp: In constructor 'SSDPClass::SSDPClass()':
.pio\libdeps\ttgo-t-watch\ESP32SSPD\ESP32SSDP.cpp:112:19: warning: large integer implicitly truncated to unsigned type [-Woverflow]
_notify_time(0)
^
This is actually because of the earlier line in the constructor:
_interval(SSDP_INTERVAL),
Where _interval is uint8_t and SSDP_INTERVAL is 1200
Should _interval be uint32_t or unsigned long instead?
Describe the bug
Duplicate global scope enumerations in libraries (or own code) provent compilation.
ESP32SSDP.h has a global scope enumeration at line 51 -
typedef enum {
NONE,
SEARCH,
NOTIFY
} ssdp_method_t;
The global scope enumeration elements (NONE, SEARCH, NOTIFY) will conflict with amy other variables or other enums and prevent compilation.
In my case this was another library ezTime.
ezTime.h enum at line 69 -
typedef enum {
NONE,
ERROR,
INFO,
DEBUG
} ezDebugLevel_t;
error: 'NONE' conflicts with a previous declaration is raised by the compiler
Moving the ESP32SSDP ssdp_method_t typedef to within the SSDPClass class resolves this conflict.
class SSDPClass
{
typedef enum {
NONE,
SEARCH,
NOTIFY
} ssdp_method_t;
public:
SSDPClass();
I will raise a similar comment with regard to global enums in the ezTime GitHub repository.
To Reproduce
Attempt compilation using:
#include <ezTime.h>
#include <ESP32SSDP.h>
Expected behavior
Successful compilation with local scoped enums
Firmware:
Board used (please complete the following information):
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.