//
|
|
//
|
|
//
|
|
|
|
#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;
|