|
|
@ -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; |