#if SSDP_SUPPORT #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; #endif