Browse Source

Added preliminary SSDP support. Untested

fastled
Xose Pérez 7 years ago
parent
commit
760a8ab39f
6 changed files with 628 additions and 1 deletions
  1. +1
    -0
      code/espurna/config/arduino.h
  2. +5
    -1
      code/espurna/config/general.h
  3. +9
    -0
      code/espurna/espurna.ino
  4. +376
    -0
      code/espurna/libs/SSDPDevice.cpp
  5. +196
    -0
      code/espurna/libs/SSDPDevice.h
  6. +41
    -0
      code/espurna/ssdp.ino

+ 1
- 0
code/espurna/config/arduino.h View File

@ -78,6 +78,7 @@
//#define NTP_SUPPORT 0 //#define NTP_SUPPORT 0
//#define RF_SUPPORT 1 //#define RF_SUPPORT 1
//#define SPIFFS_SUPPORT 1 //#define SPIFFS_SUPPORT 1
//#define SSDP_SUPPORT 1
//#define TELNET_SUPPORT 0 //#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0 //#define TERMINAL_SUPPORT 0
//#define WEB_SUPPORT 0 //#define WEB_SUPPORT 0

+ 5
- 1
code/espurna/config/general.h View File

@ -322,7 +322,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time) #define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// MDNS & LLMNR
// MDNS / LLMNR / NETBIOS / SSDP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef MDNS_SUPPORT #ifndef MDNS_SUPPORT
@ -337,6 +337,10 @@ PROGMEM const char* const custom_reset_string[] = {
#define NETBIOS_SUPPORT 0 // Publish device using NetBIOS protocol by default (1.26Kb) - requires 2.4.0 #define NETBIOS_SUPPORT 0 // Publish device using NetBIOS protocol by default (1.26Kb) - requires 2.4.0
#endif #endif
#ifndef SSDP_SUPPORT
#define SSDP_SUPPORT 0 // Publish device using SSDP protocol by default (3.32Kb)
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SPIFFS // SPIFFS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 9
- 0
code/espurna/espurna.ino View File

@ -200,6 +200,9 @@ void welcome() {
#if SPIFFS_SUPPORT #if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS")); DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif #endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT #if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET")); DEBUG_MSG_P(PSTR(" TELNET"));
#endif #endif
@ -293,6 +296,9 @@ void setup() {
#if NETBIOS_SUPPORT #if NETBIOS_SUPPORT
netbiosSetup(); netbiosSetup();
#endif #endif
#if SSDP_SUPPORT
ssdpSetup();
#endif
#if NTP_SUPPORT #if NTP_SUPPORT
ntpSetup(); ntpSetup();
#endif #endif
@ -375,6 +381,9 @@ void loop() {
#if POWER_PROVIDER != POWER_PROVIDER_NONE #if POWER_PROVIDER != POWER_PROVIDER_NONE
powerLoop(); powerLoop();
#endif #endif
#if SSDP_SUPPORT
ssdpLoop();
#endif
#if NTP_SUPPORT #if NTP_SUPPORT
ntpLoop(); ntpLoop();
#endif #endif


+ 376
- 0
code/espurna/libs/SSDPDevice.cpp View File

@ -0,0 +1,376 @@
//
//
//
#include "SSDPDevice.h"
#include "lwip/igmp.h"
SSDPDeviceClass::SSDPDeviceClass() :
m_server(0),
m_port(80),
m_ttl(SSDP_MULTICAST_TTL)
{
m_uuid[0] = '\0';
m_modelNumber[0] = '\0';
sprintf(m_deviceType, "urn:schemas-upnp-org:device:Basic:1");
m_friendlyName[0] = '\0';
m_presentationURL[0] = '\0';
m_serialNumber[0] = '\0';
m_modelName[0] = '\0';
m_modelURL[0] = '\0';
m_manufacturer[0] = '\0';
m_manufacturerURL[0] = '\0';
sprintf(m_schemaURL, "ssdp/schema.xml");
uint32_t chipId = ESP.getChipId();
sprintf(m_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x",
(uint16_t)((chipId >> 16) & 0xff),
(uint16_t)((chipId >> 8) & 0xff),
(uint16_t)chipId & 0xff);
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
m_queue[i].time = 0;
}
}
void SSDPDeviceClass::update() {
postNotifyUpdate();
}
bool SSDPDeviceClass::readLine(String &value) {
char buffer[65];
int bufferPos = 0;
while (1) {
int c = m_server->read();
if (c < 0) {
buffer[bufferPos] = '\0';
break;
}
if (c == '\r' && m_server->peek() == '\n') {
m_server->read();
buffer[bufferPos] = '\0';
break;
}
if (bufferPos < 64) {
buffer[bufferPos++] = c;
}
}
value = String(buffer);
return bufferPos > 0;
}
bool SSDPDeviceClass::readKeyValue(String &key, String &value) {
char buffer[65];
int bufferPos = 0;
while (1) {
int c = m_server->read();
if (c < 0) {
if (bufferPos == 0) return false;
buffer[bufferPos] = '\0';
break;
}
if (c == ':') {
buffer[bufferPos] = '\0';
while (m_server->peek() == ' ') m_server->read();
break;
}
else if (c == '\r' && m_server->peek() == '\n') {
m_server->read();
if (bufferPos == 0) return false;
buffer[bufferPos] = '\0';
key = String();
value = String(buffer);
return true;
}
if (bufferPos < 64) {
buffer[bufferPos++] = c;
}
}
key = String(buffer);
readLine(value);
return true;
}
void SSDPDeviceClass::postNotifyALive() {
unsigned long time = millis();
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10);
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55);
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80);
post(NOTIFY_ALIVE_INIT, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 210);
post(NOTIFY_ALIVE_INIT, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 255);
post(NOTIFY_ALIVE_INIT, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 280);
post(NOTIFY_ALIVE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 610);
post(NOTIFY_ALIVE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 655);
post(NOTIFY_ALIVE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 680);
}
void SSDPDeviceClass::postNotifyUpdate() {
unsigned long time = millis();
post(NOTIFY_UPDATE, ROOT_FOR_ALL, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 10);
post(NOTIFY_UPDATE, ROOT_BY_UUID, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 55);
post(NOTIFY_UPDATE, ROOT_BY_TYPE, SSDP_MULTICAST_ADDR, SSDP_PORT, time + 80);
}
void SSDPDeviceClass::postResponse(long mx) {
unsigned long time = millis();
unsigned long delay = random(0, mx) * 900L; // 1000 ms - 100 ms
IPAddress address = m_server->remoteIP();
uint16_t port = m_server->remotePort();
post(RESPONSE, ROOT_FOR_ALL, address, port, time + delay / 3);
post(RESPONSE, ROOT_BY_UUID, address, port, time + delay / 3 * 2);
post(RESPONSE, ROOT_BY_TYPE, address, port, time + delay);
}
void SSDPDeviceClass::postResponse(ssdp_udn_t udn, long mx) {
post(RESPONSE, udn, m_server->remoteIP(), m_server->remotePort(), millis() + random(0, mx) * 900L); // 1000 ms - 100 ms
}
void SSDPDeviceClass::post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time) {
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
if (m_queue[i].time == 0) {
m_queue[i].type = type;
m_queue[i].udn = udn;
m_queue[i].address = address;
m_queue[i].port = port;
m_queue[i].time = time;
break;
}
}
}
void SSDPDeviceClass::send(ssdp_send_parameters_t *parameters) {
char buffer[1460];
unsigned int ip = WiFi.localIP();
const char *typeTemplate;
const char *uri, *usn1, *usn2, *usn3;
switch (parameters->type) {
case NOTIFY_ALIVE_INIT:
case NOTIFY_ALIVE:
typeTemplate = SSDP_NOTIFY_ALIVE_TEMPLATE;
break;
case NOTIFY_UPDATE:
typeTemplate = SSDP_NOTIFY_UPDATE_TEMPLATE;
break;
default: // RESPONSE
typeTemplate = SSDP_RESPONSE_TEMPLATE;
break;
}
String uuid = "uuid:" + String(m_uuid);
switch (parameters->udn) {
case ROOT_FOR_ALL:
uri = "upnp:rootdevice";
usn1 = uuid.c_str();
usn2 = "::";
usn3 = "upnp:rootdevice";
break;
case ROOT_BY_UUID:
uri = uuid.c_str();
usn1 = uuid.c_str();
usn2 = "";
usn3 = "";
break;
case ROOT_BY_TYPE:
uri = m_deviceType;
usn1 = uuid.c_str();
usn2 = "::";
usn3 = m_deviceType;
break;
}
int len = snprintf_P(buffer, sizeof(buffer),
SSDP_PACKET_TEMPLATE, typeTemplate,
SSDP_INTERVAL, m_modelName, m_modelNumber, usn1, usn2, usn3, parameters->type == RESPONSE ? "ST" : "NT", uri,
IP2STR(&ip), m_port, m_schemaURL
);
if (parameters->address == SSDP_MULTICAST_ADDR) {
m_server->beginPacketMulticast(parameters->address, parameters->port, m_ttl);
}
else {
m_server->beginPacket(parameters->address, parameters->port);
}
m_server->write(buffer, len);
m_server->endPacket();
parameters->time = parameters->type == NOTIFY_ALIVE ? parameters->time + SSDP_INTERVAL * 900L : 0; // 1000 ms - 100 ms
}
String SSDPDeviceClass::schema() {
char buffer[1024];
uint32_t ip = WiFi.localIP();
snprintf(buffer, sizeof(buffer), SSDP_SCHEMA_TEMPLATE,
IP2STR(&ip), m_port, m_schemaURL,
m_deviceType,
m_friendlyName,
m_presentationURL,
m_serialNumber,
m_modelName,
m_modelNumber,
m_modelURL,
m_manufacturer,
m_manufacturerURL,
m_uuid
);
return String(buffer);
}
void SSDPDeviceClass::handleClient() {
IPAddress current = WiFi.localIP();
if (m_last != current) {
m_last = current;
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
m_queue[i].time = 0;
}
if (current != INADDR_NONE) {
if (!m_server) m_server = new WiFiUDP();
m_server->beginMulticast(current, SSDP_MULTICAST_ADDR, SSDP_PORT);
postNotifyALive();
}
else if (m_server) {
m_server->stop();
}
}
if (m_server && m_server->parsePacket()) {
String value;
if (readLine(value) && value.equalsIgnoreCase("M-SEARCH * HTTP/1.1")) {
String key, st;
bool host = false, man = false;
long mx = 0;
while (readKeyValue(key, value)) {
if (key.equalsIgnoreCase("HOST") && value.equals("239.255.255.250:1900")) {
host = true;
}
else if (key.equalsIgnoreCase("MAN") && value.equals("\"ssdp:discover\"")) {
man = true;
}
else if (key.equalsIgnoreCase("ST")) {
st = value;
}
else if (key.equalsIgnoreCase("MX")) {
mx = value.toInt();
}
}
if (host && man && mx > 0) {
if (st.equals("ssdp:all")) {
postResponse(mx);
}
else if (st.equals("upnp:rootdevice")) {
postResponse(ROOT_FOR_ALL, mx);
}
else if (st.equals("uuid:" + String(m_uuid))) {
postResponse(ROOT_BY_UUID, mx);
}
else if (st.equals(m_deviceType)) {
postResponse(ROOT_BY_TYPE, mx);
}
}
}
m_server->flush();
}
else {
unsigned long time = millis();
for (int i = 0; i < SSDP_QUEUE_SIZE; i++) {
if (m_queue[i].time > 0 && m_queue[i].time < time) {
send(&m_queue[i]);
}
}
}
}
void SSDPDeviceClass::setSchemaURL(const char *url) {
strlcpy(m_schemaURL, url, sizeof(m_schemaURL));
}
void SSDPDeviceClass::setHTTPPort(uint16_t port) {
m_port = port;
}
void SSDPDeviceClass::setDeviceType(const char *deviceType) {
strlcpy(m_deviceType, deviceType, sizeof(m_deviceType));
}
void SSDPDeviceClass::setName(const char *name) {
strlcpy(m_friendlyName, name, sizeof(m_friendlyName));
}
void SSDPDeviceClass::setURL(const char *url) {
strlcpy(m_presentationURL, url, sizeof(m_presentationURL));
}
void SSDPDeviceClass::setSerialNumber(const char *serialNumber) {
strlcpy(m_serialNumber, serialNumber, sizeof(m_serialNumber));
}
void SSDPDeviceClass::setSerialNumber(const uint32_t serialNumber) {
snprintf(m_serialNumber, sizeof(uint32_t) * 2 + 1, "%08X", serialNumber);
}
void SSDPDeviceClass::setModelName(const char *name) {
strlcpy(m_modelName, name, sizeof(m_modelName));
}
void SSDPDeviceClass::setModelNumber(const char *num) {
strlcpy(m_modelNumber, num, sizeof(m_modelNumber));
}
void SSDPDeviceClass::setModelURL(const char *url) {
strlcpy(m_modelURL, url, sizeof(m_modelURL));
}
void SSDPDeviceClass::setManufacturer(const char *name) {
strlcpy(m_manufacturer, name, sizeof(m_manufacturer));
}
void SSDPDeviceClass::setManufacturerURL(const char *url) {
strlcpy(m_manufacturerURL, url, sizeof(m_manufacturerURL));
}
void SSDPDeviceClass::setTTL(const uint8_t ttl) {
m_ttl = ttl;
}
SSDPDeviceClass SSDPDevice;

+ 196
- 0
code/espurna/libs/SSDPDevice.h View File

@ -0,0 +1,196 @@
// SSDPDevice.h
#ifndef _SSDPDEVICE_h
#define _SSDPDEVICE_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define SSDP_INTERVAL 1200
#define SSDP_PORT 1900
//#define SSDP_METHOD_SIZE 10
//#define SSDP_URI_SIZE 2
//#define SSDP_BUFFER_SIZE 64
#define SSDP_MULTICAST_TTL 2
#define SSDP_QUEUE_SIZE 21
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250);
#define SSDP_UUID_SIZE 37
#define SSDP_SCHEMA_URL_SIZE 64
#define SSDP_DEVICE_TYPE_SIZE 64
#define SSDP_FRIENDLY_NAME_SIZE 64
#define SSDP_SERIAL_NUMBER_SIZE 32
#define SSDP_PRESENTATION_URL_SIZE 128
#define SSDP_MODEL_NAME_SIZE 64
#define SSDP_MODEL_URL_SIZE 128
#define SSDP_MODEL_VERSION_SIZE 32
#define SSDP_MANUFACTURER_SIZE 64
#define SSDP_MANUFACTURER_URL_SIZE 128
static const char* PROGMEM SSDP_RESPONSE_TEMPLATE =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n";
static const char* PROGMEM SSDP_NOTIFY_ALIVE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:alive\r\n";
static const char* PROGMEM SSDP_NOTIFY_UPDATE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:update\r\n";
static const char* PROGMEM SSDP_PACKET_TEMPLATE =
"%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL
"SERVER: UPNP/1.1 %s/%s\r\n" // m_modelName, m_modelNumber
"USN: %s%s%s\r\n" // m_uuid
"%s: %s\r\n" // "NT" or "ST", m_deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), m_port, m_schemaURL
"\r\n";
static const char* PROGMEM SSDP_SCHEMA_TEMPLATE =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n"
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/%s</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>%s</presentationURL>"
"<serialNumber>%s</serialNumber>"
"<modelName>%s</modelName>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"</device>"
// "<iconList>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>48</height>"
// "<width>48</width>"
// "<depth>24</depth>"
// "<url>icon48.png</url>"
// "</icon>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>120</height>"
// "<width>120</width>"
// "<depth>24</depth>"
// "<url>icon120.png</url>"
// "</icon>"
// "</iconList>"
"</root>\r\n"
"\r\n";
typedef enum {
NOTIFY_ALIVE_INIT,
NOTIFY_ALIVE,
NOTIFY_UPDATE,
RESPONSE
} ssdp_message_t;
typedef enum {
ROOT_FOR_ALL,
ROOT_BY_UUID,
ROOT_BY_TYPE
} ssdp_udn_t;
typedef struct {
unsigned long time;
ssdp_message_t type;
ssdp_udn_t udn;
uint32_t address;
uint16_t port;
} ssdp_send_parameters_t;
class SSDPDeviceClass {
private:
WiFiUDP *m_server;
IPAddress m_last;
char m_schemaURL[SSDP_SCHEMA_URL_SIZE];
char m_uuid[SSDP_UUID_SIZE];
char m_deviceType[SSDP_DEVICE_TYPE_SIZE];
char m_friendlyName[SSDP_FRIENDLY_NAME_SIZE];
char m_serialNumber[SSDP_SERIAL_NUMBER_SIZE];
char m_presentationURL[SSDP_PRESENTATION_URL_SIZE];
char m_manufacturer[SSDP_MANUFACTURER_SIZE];
char m_manufacturerURL[SSDP_MANUFACTURER_URL_SIZE];
char m_modelName[SSDP_MODEL_NAME_SIZE];
char m_modelURL[SSDP_MODEL_URL_SIZE];
char m_modelNumber[SSDP_MODEL_VERSION_SIZE];
uint16_t m_port;
uint8_t m_ttl;
ssdp_send_parameters_t m_queue[SSDP_QUEUE_SIZE];
protected:
bool readLine(String &value);
bool readKeyValue(String &key, String &value);
void postNotifyALive();
void postNotifyUpdate();
void postResponse(long mx);
void postResponse(ssdp_udn_t udn, long mx);
void post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time);
void send(ssdp_send_parameters_t *parameters);
public:
SSDPDeviceClass();
void update();
String schema();
void handleClient();
void setDeviceType(const String& deviceType) { setDeviceType(deviceType.c_str()); }
void setDeviceType(const char *deviceType);
void setName(const String& name) { setName(name.c_str()); }
void setName(const char *name);
void setURL(const String& url) { setURL(url.c_str()); }
void setURL(const char *url);
void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); }
void setSchemaURL(const char *url);
void setSerialNumber(const String& serialNumber) { setSerialNumber(serialNumber.c_str()); }
void setSerialNumber(const char *serialNumber);
void setSerialNumber(const uint32_t serialNumber);
void setModelName(const String& name) { setModelName(name.c_str()); }
void setModelName(const char *name);
void setModelNumber(const String& num) { setModelNumber(num.c_str()); }
void setModelNumber(const char *num);
void setModelURL(const String& url) { setModelURL(url.c_str()); }
void setModelURL(const char *url);
void setManufacturer(const String& name) { setManufacturer(name.c_str()); }
void setManufacturer(const char *name);
void setManufacturerURL(const String& url) { setManufacturerURL(url.c_str()); }
void setManufacturerURL(const char *url);
void setHTTPPort(uint16_t port);
void setTTL(uint8_t ttl);
};
extern SSDPDeviceClass SSDPDevice;
#endif

+ 41
- 0
code/espurna/ssdp.ino View File

@ -0,0 +1,41 @@
/*
SSDP MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Uses SSDP library by PawelDino (https://github.com/PawelDino)
https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
*/
#if SSDP_SUPPORT
#include <libs/SSDPDevice.h>
void ssdpSetup() {
SSDPDevice.setName(getSetting("hostname"));
SSDPDevice.setDeviceType("urn:schemas-upnp-org:device:BinaryLight:1");
SSDPDevice.setSchemaURL("description.xml");
SSDPDevice.setSerialNumber(ESP.getChipId());
SSDPDevice.setURL("/");
SSDPDevice.setModelName(DEVICE);
SSDPDevice.setModelNumber("");
SSDPDevice.setManufacturer(MANUFACTURER);
#if WEB_SUPPORT
webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
DEBUG_MSG_P(PSTR("[SSDP] Schema request\n"));
String schema = SSDPDevice.schema();
Serial.println(schema);
request->send(200, "application/xml", schema.c_str());
});
#endif
}
void ssdpLoop() {
SSDPDevice.handleClient();
}
#endif // SSDP_SUPPORT

Loading…
Cancel
Save