From 4fdba1b635871611fcb13b6baf4908b80b7cb70c Mon Sep 17 00:00:00 2001 From: John Doe Date: Sat, 4 Jul 2015 22:55:48 +0300 Subject: [PATCH] Add SSDP Library and let Print::printf to handle longer strings --- cores/esp8266/Print.cpp | 4 +- libraries/ESP8266SSDP/ESP8266SSDP.cpp | 320 ++++++++++++++++++++++++ libraries/ESP8266SSDP/ESP8266SSDP.h | 100 ++++++++ libraries/ESP8266SSDP/README.md | 22 ++ libraries/ESP8266SSDP/examples/SSDP.ino | 57 +++++ libraries/ESP8266SSDP/keywords.txt | 53 ++++ 6 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 libraries/ESP8266SSDP/ESP8266SSDP.cpp create mode 100644 libraries/ESP8266SSDP/ESP8266SSDP.h create mode 100644 libraries/ESP8266SSDP/README.md create mode 100644 libraries/ESP8266SSDP/examples/SSDP.ino create mode 100644 libraries/ESP8266SSDP/keywords.txt diff --git a/cores/esp8266/Print.cpp b/cores/esp8266/Print.cpp index efd6b9f17..06c51e0c1 100644 --- a/cores/esp8266/Print.cpp +++ b/cores/esp8266/Print.cpp @@ -47,8 +47,8 @@ size_t ICACHE_FLASH_ATTR Print::write(const uint8_t *buffer, size_t size) { size_t Print::printf(const char *format, ...) { va_list arg; va_start(arg, format); - char temp[256]; - size_t len = ets_vsnprintf(temp, 256, format, arg); + char temp[1460]; + size_t len = ets_vsnprintf(temp, 1460, format, arg); len = print(temp); va_end(arg); return len; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.cpp b/libraries/ESP8266SSDP/ESP8266SSDP.cpp new file mode 100644 index 000000000..1077f66fb --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.cpp @@ -0,0 +1,320 @@ +/* +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include "ESP8266SSDP.h" + +extern "C" { + #include "ip_addr.h" + #include "user_interface.h" + #include "mem.h" +} +#include "lwip/igmp.h" + +#define SSDP_INTERVAL 1200 +#define SSDP_PORT 1900 +#define SSDP_METHOD_SIZE 10 +#define SSDP_URI_SIZE 2 +#define SSDP_BUFFER_SIZE 64 + +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + +const char* _ssdp_response_template = + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n" + "ST: upnp:rootdevice\r\n"; + +const char* _ssdp_notify_template = + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NT: upnp:rootdevice\r\n" + "NTS: ssdp:alive\r\n"; + +const char* _ssdp_packet_template = + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 %s/%u.%u\r\n" // _modelName, _modelNumber->major, _modelNumber->minor + "USN: uuid:%s-%02X%02X%02X%02X%02X%02X\r\n" // _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + "LOCATION: http://%u.%u.%u.%u/ssdp/schema.xml\r\n" // WiFi.localIP() + "\r\n"; + +const char* _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" + "" + "" + "" + "1" + "0" + "" + "" + "urn:schemas-upnp-org:device:Basic:1" + "%s" + "%s" + "%s" + "%s" + "%u.%u" + "%s" + "%s" + "%s" + "%s-%02X%02X%02X%02X%02X%02X" //uuid:UUID + "" + "\r\n" + "\r\n"; + +SSDPClass::SSDPClass(){ + _base = (char*)os_malloc(SSDP_BASE_SIZE); + _mac = (byte*)os_malloc(6); + _friendlyName = (char*)os_malloc(SSDP_FRIENDLY_NAME_SIZE); + _presentationURL = (char*)os_malloc(SSDP_PRESENTATION_URL_SIZE); + _serialNumber = (char*)os_malloc(SSDP_SERIAL_NUMBER_SIZE); + _modelName = (char*)os_malloc(SSDP_MODEL_NAME_SIZE); + _modelNumber = (ssdp_version_t*)os_malloc(SSDP_MODEL_VERSION_SIZE); + _modelURL = (char*)os_malloc(SSDP_MODEL_URL_SIZE); + _manufacturer = (char*)os_malloc(SSDP_MANUFACTURER_SIZE); + _manufacturerURL = (char*)os_malloc(SSDP_MANUFACTURER_URL_SIZE); + + _modelNumber->major = 0; + _modelNumber->minor = 0; + _friendlyName[0] = '\0'; + _presentationURL[0] = '\0'; + _serialNumber[0] = '\0'; + _modelName[0] = '\0'; + _modelURL[0] = '\0'; + _manufacturer[0] = '\0'; + _manufacturerURL[0] = '\0'; + _pending = false; +} + +SSDPClass::~SSDPClass(){ + os_free(_base); + os_free(_mac); + os_free(_friendlyName); + os_free(_presentationURL); + os_free(_serialNumber); + os_free(_modelName); + os_free(_modelNumber); + os_free(_modelURL); + os_free(_manufacturer); + os_free(_manufacturerURL); + _pending = false; +} + +void SSDPClass::begin(char *base){ + ip_addr_t ifaddr; + ip_addr_t multicast_addr; + + _pending = false; + strcpy(_base, base); + + WiFi.macAddress(_mac); + ifaddr.addr = WiFi.localIP(); + multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; + igmp_joingroup(&ifaddr, &multicast_addr); + + _server.begin(SSDP_PORT); +} + +void SSDPClass::send(ssdp_method_t method){ +#ifdef DEBUG_SSDP + if(method == NONE){ + DEBUG_SSDP.print("Sending Response to "); + DEBUG_SSDP.print(_server.remoteIP()); + DEBUG_SSDP.print(":"); + DEBUG_SSDP.println(_server.remotePort()); + }else if(method == NOTIFY){ + DEBUG_SSDP.println("Sending Notify to 239.255.255.250:1900"); + } +#endif + + if(method == NONE){ + _server.beginPacket(_server.remoteIP(), _server.remotePort()); + } else { + _server.beginPacket(SSDP_MULTICAST_ADDR, SSDP_PORT); + } + + uint32_t ip = WiFi.localIP(); + + _server.printf(_ssdp_packet_template, + (method == NONE)?_ssdp_response_template:_ssdp_notify_template, + SSDP_INTERVAL, + _modelName, _modelNumber->major, _modelNumber->minor, + _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5], + (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), (uint8_t)((ip >> 24) & 0xFF) + ); + + _server.endPacket(); +} + +void SSDPClass::schema(WiFiClient client){ + client.printf(_ssdp_schema_template, + _friendlyName, + _presentationURL, + _serialNumber, + _modelName, + _modelNumber->major, _modelNumber->minor, + _modelURL, + _manufacturer, + _manufacturerURL, + _base, _mac[0], _mac[1], _mac[2], _mac[3], _mac[4], _mac[5] + ); +} + +uint8_t SSDPClass::update(){ + if(!_pending && _server.parsePacket() > 0){ + ssdp_method_t method = NONE; + + typedef enum {METHOD, URI, PROTO, KEY, VALUE, ABORT} states; + states state = METHOD; + + typedef enum {START, MAN, ST, MX} headers; + headers header = START; + + uint8_t cursor = 0; + uint8_t cr = 0; + + char buffer[SSDP_BUFFER_SIZE] = {0}; + + while(_server.available() > 0){ + char c = _server.read(); + + (c == '\r' || c == '\n') ? cr++ : cr = 0; + + switch(state){ + case METHOD: + if(c == ' '){ + if(strcmp(buffer, "M-SEARCH") == 0) method = SEARCH; + else if(strcmp(buffer, "NOTIFY") == 0) method = NOTIFY; + + if(method == NONE) state = ABORT; + else state = URI; + cursor = 0; + + }else if(cursor < SSDP_METHOD_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case URI: + if(c == ' '){ + if(strcmp(buffer, "*")) state = ABORT; + else state = PROTO; + cursor = 0; + }else if(cursor < SSDP_URI_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case PROTO: + if(cr == 2){ state = KEY; cursor = 0; } + break; + case KEY: + if(cr == 4){ _pending = true; _process_time = millis(); } + else if(c == ' '){ cursor = 0; state = VALUE; } + else if(c != '\r' && c != '\n' && c != ':' && cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + break; + case VALUE: + if(cr == 2){ + switch(header){ + case MAN: +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("MAN: %s\n", (char *)buffer); +#endif + break; + case ST: + if(strcmp(buffer, "ssdp:all")){ + state = ABORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("REJECT: %s\n", (char *)buffer); +#endif + } + break; + case MX: + _delay = random(0, atoi(buffer)) * 1000L; + break; + } + + if(state != ABORT){ state = KEY; header = START; cursor = 0; } + }else if(c != '\r' && c != '\n'){ + if(header == START){ + if(strncmp(buffer, "MA", 2) == 0) header = MAN; + else if(strcmp(buffer, "ST") == 0) header = ST; + else if(strcmp(buffer, "MX") == 0) header = MX; + } + + if(cursor < SSDP_BUFFER_SIZE - 1){ buffer[cursor++] = c; buffer[cursor] = '\0'; } + } + break; + case ABORT: + _pending = false; _delay = 0; + break; + } + } + + _server.flush(); + } + + if(_pending && (millis() - _process_time) > _delay){ + _pending = false; _delay = 0; + send(NONE); + }else if(_notify_time == 0 || (millis() - _notify_time) > (SSDP_INTERVAL * 1000L)){ + _notify_time = millis(); + send(NOTIFY); + } +} + +void SSDPClass::setName(char *name){ + strcpy(_friendlyName, name); +} + +void SSDPClass::setURL(char *url){ + strcpy(_presentationURL, url); +} + +void SSDPClass::setSerialNumber(char *serialNumber){ + strcpy(_serialNumber, serialNumber); +} + +void SSDPClass::setModelName(char *name){ + strcpy(_modelName, name); +} + +void SSDPClass::setModelNumber(uint8_t major, uint8_t minor){ + _modelNumber->major = major; + _modelNumber->minor = minor; +} + +void SSDPClass::setModelURL(char *url){ + strcpy(_modelURL, url); +} + +void SSDPClass::setManufacturer(char *name){ + strcpy(_manufacturer, name); +} + +void SSDPClass::setManufacturerURL(char *url){ + strcpy(_manufacturerURL, url); +} + +SSDPClass SSDP; diff --git a/libraries/ESP8266SSDP/ESP8266SSDP.h b/libraries/ESP8266SSDP/ESP8266SSDP.h new file mode 100644 index 000000000..765809179 --- /dev/null +++ b/libraries/ESP8266SSDP/ESP8266SSDP.h @@ -0,0 +1,100 @@ +/* +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov + +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +License (MIT license): + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#ifndef ESP8266SSDP_H +#define ESP8266SSDP_H + +#include +#include +#include + +#define DEBUG_SSDP Serial + +#define SSDP_BASE_SIZE 24 +#define SSDP_FRIENDLY_NAME_SIZE 32 +#define SSDP_SERIAL_NUMBER_SIZE 32 +#define SSDP_PRESENTATION_URL_SIZE 32 +#define SSDP_MODEL_NAME_SIZE 32 +#define SSDP_MODEL_URL_SIZE 32 +#define SSDP_MODEL_VERSION_SIZE 2 +#define SSDP_MANUFACTURER_SIZE 32 +#define SSDP_MANUFACTURER_URL_SIZE 32 + +typedef enum { + NONE, + SEARCH, + NOTIFY +} ssdp_method_t; + +typedef struct { + uint8_t major; + uint8_t minor; +} ssdp_version_t; + + +class SSDPClass{ + public: + SSDPClass(); + ~SSDPClass(); + + void begin(char *base); + uint8_t update(); + void send(ssdp_method_t method); + void schema(WiFiClient client); + + void setName(char *name); + void setURL(char *url); + void setSerialNumber(char *serialNumber); + void setModelName(char *name); + void setModelNumber(uint8_t major, uint8_t minor); + void setModelURL(char *url); + void setManufacturer(char *name); + void setManufacturerURL(char *url); + + private: + WiFiUDP _server; + bool _pending; + unsigned short _delay; + unsigned long _process_time; + unsigned long _notify_time; + + uint8_t *_mac; + char *_base; + char *_friendlyName; + char *_serialNumber; + char *_presentationURL; + char *_manufacturer; + char *_manufacturerURL; + char *_modelName; + char *_modelURL; + ssdp_version_t *_modelNumber; +}; + +extern SSDPClass SSDP; + +#endif diff --git a/libraries/ESP8266SSDP/README.md b/libraries/ESP8266SSDP/README.md new file mode 100644 index 000000000..31d353052 --- /dev/null +++ b/libraries/ESP8266SSDP/README.md @@ -0,0 +1,22 @@ +ESP8266 Simple Service Discovery +Copyright (c) 2015 Hristo Gochkov +Original (Arduino) version by Filippo Sallemi, July 23, 2014. +Can be found at: https://github.com/nomadnt/uSSDP + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/libraries/ESP8266SSDP/examples/SSDP.ino b/libraries/ESP8266SSDP/examples/SSDP.ino new file mode 100644 index 000000000..fa7753622 --- /dev/null +++ b/libraries/ESP8266SSDP/examples/SSDP.ino @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +const char* ssid = "************"; +const char* password = "***********"; + +ESP8266WebServer HTTP(80); + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch..."); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if(WiFi.waitForConnectResult() == WL_CONNECTED){ + HTTP.on("/", HTTP_GET, [](){ + HTTP.sendHeader("Connection", "close"); + HTTP.sendHeader("Access-Control-Allow-Origin", "*"); + HTTP.send(200, "text/plain", "Hello World!"); + }); + HTTP.on("/ssdp/schema.xml", HTTP_GET, [](){ + SSDP.schema(HTTP.client()); + }); + HTTP.begin(); + + byte mac[6]; + char base[24]; + WiFi.macAddress(mac); + sprintf(base, "esp8266x-%02x%02x-%02x%02x-%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + Serial.printf("Starting SSDP: %s\n", base); + + SSDP.begin((char*)base); + SSDP.setName((char*)"ESP8266"); + SSDP.setSerialNumber((char*)"A0123456789"); + SSDP.setURL((char*)"/"); + SSDP.setModelName((char*)"ESP-12e"); + SSDP.setModelNumber(1, 0); + SSDP.setModelURL((char*)"http://12e.espressif.com"); + SSDP.setManufacturer((char*)"Espressif"); + SSDP.setManufacturerURL((char*)"http://espressif.com"); + + Serial.printf("Ready!\n"); + } else { + Serial.printf("WiFi Failed\n"); + while(1) delay(100); + } +} + +void loop() { + HTTP.handleClient(); + SSDP.update(); + delay(1); +} \ No newline at end of file diff --git a/libraries/ESP8266SSDP/keywords.txt b/libraries/ESP8266SSDP/keywords.txt new file mode 100644 index 000000000..505ddf4df --- /dev/null +++ b/libraries/ESP8266SSDP/keywords.txt @@ -0,0 +1,53 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP8266SSDP KEYWORD1 +SSDP KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +update KEYWORD2 +send KEYWORD2 +schema KEYWORD2 +setName KEYWORD2 +setURL KEYWORD2 +setSerialNumber KEYWORD2 +setModelName KEYWORD2 +setModelNumber KEYWORD2 +setModelURL KEYWORD2 +setManufacturer KEYWORD2 +setManufacturerURL KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +SSDP_INTERVAL LITERAL1 +SSDP_PORT LITERAL1 +SSDP_METHOD_SIZE LITERAL1 +SSDP_URI_SIZE LITERAL1 +SSDP_BUFFER_SIZE LITERAL1 +SSDP_BASE_SIZE LITERAL1 +SSDP_FRIENDLY_NAME_SIZE LITERAL1 +SSDP_SERIAL_NUMBER_SIZE LITERAL1 +SSDP_PRESENTATION_URL_SIZE LITERAL1 +SSDP_MODEL_NAME_SIZE LITERAL1 +SSDP_MODEL_URL_SIZE LITERAL1 +SSDP_MODEL_VERSION_SIZE LITERAL1 +SSDP_MANUFACTURER_SIZE LITERAL1 +SSDP_MANUFACTURER_URL_SIZE LITERAL1 +SEARCH LITERAL1 +NOTIFY LITERAL1 +BASIC LITERAL1 +MANAGEABLE LITERAL1 +SOLARPROTECTIONBLIND LITERAL1 +DIGITALSECURITYCAMERA LITERAL1 +HVAC LITERAL1 +LIGHTINGCONTROL LITERAL1