From 429e1ce61ace063868edec4dfcd4c8334caecb5b Mon Sep 17 00:00:00 2001 From: Carsten Schmiemann Date: Sat, 16 Jan 2021 23:33:54 +0100 Subject: [PATCH] First booting version --- README.md | 11 + include/README | 0 lib/ESP/ESPId.cpp | 17 + lib/ESP/ESPId.h | 8 + lib/MQTT/HomeAssistantDiscoveryClient.cpp | 7 +- lib/MQTT/HomeAssistantDiscoveryClient.h | 1 + lib/MQTT/MqttClient.cpp | 3 +- lib/MQTT/MqttClient.h | 1 + lib/MiLightState/GroupStatePersistence.cpp | 5 +- lib/Radio/PL1167_nRF24.cpp | 2 +- lib/SSDP/New_ESP8266SSDP.cpp | 441 +++++++++++++++++++++ lib/SSDP/New_ESP8266SSDP.h | 128 ++++++ lib/Settings/AboutHelper.cpp | 21 +- lib/Settings/Settings.cpp | 10 +- lib/Settings/Settings.h | 8 +- lib/Udp/MiLightDiscoveryServer.cpp | 13 +- lib/Udp/MiLightUdpServer.cpp | 7 +- lib/Udp/V6MiLightUdpServer.cpp | 7 +- lib/WebServer/MiLightHttpServer.cpp | 26 +- lib/WebServer/MiLightHttpServer.h | 5 +- platformio.ini | 19 +- src/main.cpp | 160 ++++---- 22 files changed, 779 insertions(+), 121 deletions(-) delete mode 100644 include/README create mode 100644 lib/ESP/ESPId.cpp create mode 100644 lib/ESP/ESPId.h create mode 100644 lib/SSDP/New_ESP8266SSDP.cpp create mode 100644 lib/SSDP/New_ESP8266SSDP.h diff --git a/README.md b/README.md index 5a45175..c40406c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +# TODO's for ESP32 + +- Implement handleFirmwarePost() and handleFirmwareUpload() in MiLightHttpServer.cpp +- Erase config in MiLightHttpServer.cpp +- Wifi set hostname in main.cpp +- Set Wifi physical (setPhyMode) in main.cpp +- SSDP (Service discovery) support in main.cpp +- LED callback in WiFiManager loop in main.cpp +- Implement other TODO's in main.cpp +- Reset reason in AboutHelper.cpp + # esp8266_milight_hub [![Build Status](https://travis-ci.org/sidoh/esp8266_milight_hub.svg?branch=master)](https://travis-ci.org/sidoh/esp8266_milight_hub) [![License][shield-license]][info-license] This is a replacement for a Milight/LimitlessLED remote/gateway hosted on an ESP8266. Leverages [Henryk Plötz's awesome reverse-engineering work](https://hackaday.io/project/5888-reverse-engineering-the-milight-on-air-protocol). diff --git a/include/README b/include/README deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ESP/ESPId.cpp b/lib/ESP/ESPId.cpp new file mode 100644 index 0000000..51ff0bf --- /dev/null +++ b/lib/ESP/ESPId.cpp @@ -0,0 +1,17 @@ +#include + +#ifdef ESP8266 +uint32_t getESPId() +{ + return ESP.getChipId(); +} +#elif ESP32 +uint32_t getESPId() +{ + uint32_t id = 0; + for(int i=0; i<17; i=i+8) { + id |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; + } + return id; +} +#endif \ No newline at end of file diff --git a/lib/ESP/ESPId.h b/lib/ESP/ESPId.h new file mode 100644 index 0000000..96e8c14 --- /dev/null +++ b/lib/ESP/ESPId.h @@ -0,0 +1,8 @@ +#ifndef _ESPID_H +#define _ESPID_H + +#include + +uint32_t getESPId(); + +#endif \ No newline at end of file diff --git a/lib/MQTT/HomeAssistantDiscoveryClient.cpp b/lib/MQTT/HomeAssistantDiscoveryClient.cpp index 4e0de0b..55a9604 100644 --- a/lib/MQTT/HomeAssistantDiscoveryClient.cpp +++ b/lib/MQTT/HomeAssistantDiscoveryClient.cpp @@ -37,7 +37,7 @@ void HomeAssistantDiscoveryClient::addConfig(const char* alias, const BulbId& bu DynamicJsonDocument config(1024); char uniqidBuffer[30]; - //sprintf_P(uniqidBuffer, PSTR("%X-%s"), ESP.getChipId(), alias); + sprintf_P(uniqidBuffer, PSTR("%X-%s"), getESPId(), alias); config[F("schema")] = F("json"); config[F("name")] = alias; @@ -50,7 +50,8 @@ void HomeAssistantDiscoveryClient::addConfig(const char* alias, const BulbId& bu deviceMetadata[F("sw_version")] = QUOTE(MILIGHT_HUB_VERSION); JsonArray identifiers = deviceMetadata.createNestedArray(F("identifiers")); - //identifiers.add(ESP.getChipId()); + identifiers.add(getESPId()); + bulbId.serialize(identifiers); // HomeAssistant only supports simple client availability @@ -142,7 +143,7 @@ String HomeAssistantDiscoveryClient::buildTopic(const BulbId& bulbId) { topic += "light/"; // Use a static ID that doesn't depend on configuration. - topic += "milight_hub_"; //+ String(ESP.getChipId()); + topic += "milight_hub_" + String(getESPId()); // make the object ID based on the actual parameters rather than the alias. topic += "/"; diff --git a/lib/MQTT/HomeAssistantDiscoveryClient.h b/lib/MQTT/HomeAssistantDiscoveryClient.h index ee6fc79..17fef90 100644 --- a/lib/MQTT/HomeAssistantDiscoveryClient.h +++ b/lib/MQTT/HomeAssistantDiscoveryClient.h @@ -2,6 +2,7 @@ #include #include +#include #include class HomeAssistantDiscoveryClient { diff --git a/lib/MQTT/MqttClient.cpp b/lib/MQTT/MqttClient.cpp index f9b2440..d9eed6f 100644 --- a/lib/MQTT/MqttClient.cpp +++ b/lib/MQTT/MqttClient.cpp @@ -56,7 +56,8 @@ void MqttClient::begin() { bool MqttClient::connect() { char nameBuffer[30]; - sprintf_P(nameBuffer, "milight-hub-esp32"); //ESP.getChipId() + sprintf_P(nameBuffer, PSTR("milight-hub-%u"), getESPId()); + #ifdef MQTT_DEBUG Serial.println(F("MqttClient - connecting using name")); diff --git a/lib/MQTT/MqttClient.h b/lib/MQTT/MqttClient.h index dc601c4..b5a2bf2 100644 --- a/lib/MQTT/MqttClient.h +++ b/lib/MQTT/MqttClient.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifndef MQTT_CONNECTION_ATTEMPT_FREQUENCY #define MQTT_CONNECTION_ATTEMPT_FREQUENCY 5000 diff --git a/lib/MiLightState/GroupStatePersistence.cpp b/lib/MiLightState/GroupStatePersistence.cpp index df8b365..75572ad 100644 --- a/lib/MiLightState/GroupStatePersistence.cpp +++ b/lib/MiLightState/GroupStatePersistence.cpp @@ -1,6 +1,9 @@ #include #include -#include + +#ifdef ESP32 + #include +#endif static const char FILE_PREFIX[] = "group_states/"; diff --git a/lib/Radio/PL1167_nRF24.cpp b/lib/Radio/PL1167_nRF24.cpp index dc85474..574c0db 100644 --- a/lib/Radio/PL1167_nRF24.cpp +++ b/lib/Radio/PL1167_nRF24.cpp @@ -180,7 +180,7 @@ int PL1167_nRF24::internal_receive() { // Currently, the syncword width is set to 5 in order to include the // PL1167 trailer. The trailer is 4 bits, which pushes packet data -// out of byte-alignment. +// out of byte-alignment.b // // The following code reads un-byte-aligned packet data. // diff --git a/lib/SSDP/New_ESP8266SSDP.cpp b/lib/SSDP/New_ESP8266SSDP.cpp new file mode 100644 index 0000000..481cd97 --- /dev/null +++ b/lib/SSDP/New_ESP8266SSDP.cpp @@ -0,0 +1,441 @@ +/* +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. + +*/ +#define LWIP_OPEN_SRC +#include +#include "New_ESP8266SSDP.h" +#include "WiFiUdp.h" +#include "debug.h" + +extern "C" { + #include "osapi.h" + #include "ets_sys.h" + #include "user_interface.h" +} + +#include "lwip/opt.h" +#include "lwip/udp.h" +#include "lwip/inet.h" +#include "lwip/igmp.h" +#include "lwip/mem.h" +#include "include/UdpContext.h" + +// #define DEBUG_SSDP Serial + +#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 +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + + + +static const char _ssdp_response_template[] PROGMEM = + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n"; + +static const char _ssdp_notify_template[] PROGMEM = + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NTS: ssdp:alive\r\n"; + +static const char _ssdp_packet_template[] PROGMEM = + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber + "USN: uuid:%s\r\n" // _uuid + "%s: %s\r\n" // "NT" or "ST", _deviceType + "LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL + "\r\n"; + +static const char _ssdp_schema_template[] PROGMEM = + "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" + "" + "http://%u.%u.%u.%u:%u/" // WiFi.localIP(), _port + "" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "%s" + "uuid:%s" + "" +// "" +// "" +// "image/png" +// "48" +// "48" +// "24" +// "icon48.png" +// "" +// "" +// "image/png" +// "120" +// "120" +// "24" +// "icon120.png" +// "" +// "" + "\r\n" + "\r\n"; + + +struct SSDPTimer { + ETSTimer timer; +}; + +SSDPClass::SSDPClass() : +_server(0), +_timer(new SSDPTimer), +_port(80), +_ttl(SSDP_MULTICAST_TTL), +_respondToPort(0), +_pending(false), +_delay(0), +_process_time(0), +_notify_time(0) +{ + _uuid[0] = '\0'; + _modelNumber[0] = '\0'; + sprintf(_deviceType, "urn:schemas-upnp-org:device:Basic:1"); + _friendlyName[0] = '\0'; + _presentationURL[0] = '\0'; + _serialNumber[0] = '\0'; + _modelName[0] = '\0'; + _modelURL[0] = '\0'; + _manufacturer[0] = '\0'; + _manufacturerURL[0] = '\0'; + sprintf(_schemaURL, "ssdp/schema.xml"); +} + +SSDPClass::~SSDPClass(){ + delete _timer; +} + +bool SSDPClass::begin(){ + _pending = false; + + uint32_t chipId = ESP.getChipId(); + sprintf(_uuid, "38323636-4558-4dda-9188-cda0e6%02x%02x%02x", + (uint16_t) ((chipId >> 16) & 0xff), + (uint16_t) ((chipId >> 8) & 0xff), + (uint16_t) chipId & 0xff ); + +#ifdef DEBUG_SSDP + DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid); +#endif + + if (_server) { + _server->unref(); + _server = 0; + } + + _server = new UdpContext; + _server->ref(); + + ip_addr_t ifaddr; + ifaddr.addr = WiFi.localIP(); + ip_addr_t multicast_addr; + multicast_addr.addr = (uint32_t) SSDP_MULTICAST_ADDR; + if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK ) { + DEBUGV("SSDP failed to join igmp group"); + return false; + } + + if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(ifaddr); + _server->setMulticastTTL(_ttl); + _server->onRx(std::bind(&SSDPClass::_update, this)); + if (!_server->connect(multicast_addr, SSDP_PORT)) { + return false; + } + + _startTimer(); + + return true; +} + +void SSDPClass::_send(ssdp_method_t method){ + char buffer[1460]; + uint32_t ip = WiFi.localIP(); + + char valueBuffer[strlen(_ssdp_notify_template)+1]; + strcpy_P(valueBuffer, (method == NONE)?_ssdp_response_template:_ssdp_notify_template); + + int len = snprintf_P(buffer, sizeof(buffer), + _ssdp_packet_template, + valueBuffer, + SSDP_INTERVAL, + _modelName, _modelNumber, + _uuid, + (method == NONE)?"ST":"NT", + _deviceType, + IP2STR(&ip), _port, _schemaURL + ); + + _server->append(buffer, len); + + ip_addr_t remoteAddr; + uint16_t remotePort; + if(method == NONE) { + remoteAddr.addr = _respondToAddr; + remotePort = _respondToPort; +#ifdef DEBUG_SSDP + DEBUG_SSDP.print("Sending Response to "); +#endif + } else { + remoteAddr.addr = SSDP_MULTICAST_ADDR; + remotePort = SSDP_PORT; +#ifdef DEBUG_SSDP + DEBUG_SSDP.println("Sending Notify to "); +#endif + } +#ifdef DEBUG_SSDP + DEBUG_SSDP.print(IPAddress(remoteAddr.addr)); + DEBUG_SSDP.print(":"); + DEBUG_SSDP.println(remotePort); +#endif + + _server->send(&remoteAddr, remotePort); +} + +void SSDPClass::schema(WiFiClient client){ + uint32_t ip = WiFi.localIP(); + char buffer[strlen(_ssdp_schema_template)+1]; + strcpy_P(buffer, _ssdp_schema_template); + client.printf(buffer, + IP2STR(&ip), _port, + _deviceType, + _friendlyName, + _presentationURL, + _serialNumber, + _modelName, + _modelNumber, + _modelURL, + _manufacturer, + _manufacturerURL, + _uuid + ); +} + +void SSDPClass::_update(){ + if(!_pending && _server->next()) { + ssdp_method_t method = NONE; + + _respondToAddr = _server->getRemoteAddress(); + _respondToPort = _server->getRemotePort(); + + 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->getSize() > 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 START: + break; + 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 + } + // if the search type matches our type, we should respond instead of ABORT + if(strcmp(buffer, _deviceType) == 0){ + _pending = true; + _process_time = millis(); + state = KEY; + } + 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; + } + } + } + + 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); + } + + if (_pending) { + while (_server->next()) + _server->flush(); + } + +} + +void SSDPClass::setSchemaURL(const char *url){ + strlcpy(_schemaURL, url, sizeof(_schemaURL)); +} + +void SSDPClass::setHTTPPort(uint16_t port){ + _port = port; +} + +void SSDPClass::setDeviceType(const char *deviceType){ + strlcpy(_deviceType, deviceType, sizeof(_deviceType)); +} + +void SSDPClass::setName(const char *name){ + strlcpy(_friendlyName, name, sizeof(_friendlyName)); +} + +void SSDPClass::setURL(const char *url){ + strlcpy(_presentationURL, url, sizeof(_presentationURL)); +} + +void SSDPClass::setSerialNumber(const char *serialNumber){ + strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber)); +} + +void SSDPClass::setSerialNumber(const uint32_t serialNumber){ + snprintf(_serialNumber, sizeof(uint32_t)*2+1, "%08X", serialNumber); +} + +void SSDPClass::setModelName(const char *name){ + strlcpy(_modelName, name, sizeof(_modelName)); +} + +void SSDPClass::setModelNumber(const char *num){ + strlcpy(_modelNumber, num, sizeof(_modelNumber)); +} + +void SSDPClass::setModelURL(const char *url){ + strlcpy(_modelURL, url, sizeof(_modelURL)); +} + +void SSDPClass::setManufacturer(const char *name){ + strlcpy(_manufacturer, name, sizeof(_manufacturer)); +} + +void SSDPClass::setManufacturerURL(const char *url){ + strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL)); +} + +void SSDPClass::setTTL(const uint8_t ttl){ + _ttl = ttl; +} + +void SSDPClass::_onTimerStatic(SSDPClass* self) { + self->_update(); +} + +void SSDPClass::_startTimer() { + ETSTimer* tm = &(_timer->timer); + const int interval = 1000; + os_timer_disarm(tm); + os_timer_setfn(tm, reinterpret_cast(&SSDPClass::_onTimerStatic), reinterpret_cast(this)); + os_timer_arm(tm, interval, 1 /* repeat */); +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP) +SSDPClass SSDP; +#endif diff --git a/lib/SSDP/New_ESP8266SSDP.h b/lib/SSDP/New_ESP8266SSDP.h new file mode 100644 index 0000000..74d8bba --- /dev/null +++ b/lib/SSDP/New_ESP8266SSDP.h @@ -0,0 +1,128 @@ +/* +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 + +class UdpContext; + +#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 + +typedef enum { + NONE, + SEARCH, + NOTIFY +} ssdp_method_t; + + +struct SSDPTimer; + +class SSDPClass{ + public: + SSDPClass(); + ~SSDPClass(); + + bool begin(); + + void schema(WiFiClient client); + + 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); + + protected: + void _send(ssdp_method_t method); + void _update(); + void _startTimer(); + static void _onTimerStatic(SSDPClass* self); + + UdpContext* _server; + SSDPTimer* _timer; + uint16_t _port; + uint8_t _ttl; + + IPAddress _respondToAddr; + uint16_t _respondToPort; + + bool _pending; + unsigned short _delay; + unsigned long _process_time; + unsigned long _notify_time; + + char _schemaURL[SSDP_SCHEMA_URL_SIZE]; + char _uuid[SSDP_UUID_SIZE]; + char _deviceType[SSDP_DEVICE_TYPE_SIZE]; + char _friendlyName[SSDP_FRIENDLY_NAME_SIZE]; + char _serialNumber[SSDP_SERIAL_NUMBER_SIZE]; + char _presentationURL[SSDP_PRESENTATION_URL_SIZE]; + char _manufacturer[SSDP_MANUFACTURER_SIZE]; + char _manufacturerURL[SSDP_MANUFACTURER_URL_SIZE]; + char _modelName[SSDP_MODEL_NAME_SIZE]; + char _modelURL[SSDP_MODEL_URL_SIZE]; + char _modelNumber[SSDP_MODEL_VERSION_SIZE]; +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP) +extern SSDPClass SSDP; +#endif + +#endif diff --git a/lib/Settings/AboutHelper.cpp b/lib/Settings/AboutHelper.cpp index 53115f7..72e098a 100644 --- a/lib/Settings/AboutHelper.cpp +++ b/lib/Settings/AboutHelper.cpp @@ -1,7 +1,12 @@ #include #include #include -#include + +#ifdef ESP8266 + #include +#elif ESP32 + #include +#endif String AboutHelper::generateAboutString(bool abbreviated) { DynamicJsonDocument buffer(1024); @@ -17,12 +22,20 @@ String AboutHelper::generateAboutString(bool abbreviated) { void AboutHelper::generateAboutObject(JsonDocument& obj, bool abbreviated) { obj["firmware"] = QUOTE(FIRMWARE_NAME); obj["version"] = QUOTE(MILIGHT_HUB_VERSION); - obj["ip_address"] = ETH.localIP().toString(); - //obj["reset_reason"] = ESP.getResetReason(); + obj["ip_address"] = WiFi.localIP().toString(); +#ifdef ESP8266 + obj["reset_reason"] = ESP.getResetReason(); +#elif ESP32 + // TODO get reset reason +#endif if (! abbreviated) { obj["variant"] = QUOTE(FIRMWARE_VARIANT); obj["free_heap"] = ESP.getFreeHeap(); - //obj["arduino_version"] = ESP.getCoreVersion(); +#ifdef ESP8266 + obj["arduino_version"] = ESP.getCoreVersion(); +#elif ESP32 + obj["arduino_version"] = ESP.getSdkVersion(); +#endif } } \ No newline at end of file diff --git a/lib/Settings/Settings.cpp b/lib/Settings/Settings.cpp index 754cd48..5d01975 100644 --- a/lib/Settings/Settings.cpp +++ b/lib/Settings/Settings.cpp @@ -1,11 +1,14 @@ #include #include #include -#include #include #include #include +#ifdef ESP32 + #include +#endif + #define PORT_POSITION(s) ( s.indexOf(':') ) GatewayConfig::GatewayConfig(uint16_t deviceId, uint16_t port, uint8_t protocolVersion) @@ -209,10 +212,11 @@ void Settings::dumpGroupIdAliases(JsonObject json) { } void Settings::load(Settings& settings) { + if (SPIFFS.exists(SETTINGS_FILE)) { // Clear in-memory settings settings = Settings(); - + File f = SPIFFS.open(SETTINGS_FILE, "r"); DynamicJsonDocument json(MILIGHT_HUB_SETTINGS_BUFFER_SIZE); @@ -221,7 +225,7 @@ void Settings::load(Settings& settings) { if (! error) { JsonObject parsedSettings = json.as(); - settings.patch(parsedSettings); + settings.patch(parsedSettings); } else { Serial.print(F("Error parsing saved settings file: ")); Serial.println(error.c_str()); diff --git a/lib/Settings/Settings.h b/lib/Settings/Settings.h index bb95d38..58a9d4e 100644 --- a/lib/Settings/Settings.h +++ b/lib/Settings/Settings.h @@ -50,9 +50,9 @@ #define WEB_INDEX_FILENAME "/web/index.html" -#define MILIGHT_GITHUB_USER "sidoh" -#define MILIGHT_GITHUB_REPO "esp8266_milight_hub" -#define MILIGHT_REPO_WEB_PATH "/data/web/index.html" +//#define MILIGHT_GITHUB_USER "sidoh" +//#define MILIGHT_GITHUB_REPO "esp8266_milight_hub" +//#define MILIGHT_REPO_WEB_PATH "/data/web/index.html" #define MINIMUM_RESTART_PERIOD 1 #define DEFAULT_MQTT_PORT 1883 @@ -113,7 +113,7 @@ public: ledModeOperating(LEDStatus::LEDMode::SlowBlip), ledModePacket(LEDStatus::LEDMode::Flicker), ledModePacketCount(3), - hostname("milight-hub"), + hostname("esp32-milight-hub"), rf24PowerLevel(RF24PowerLevelHelpers::defaultValue()), rf24Channels(RF24ChannelHelpers::allValues()), groupStateFields(DEFAULT_GROUP_STATE_FIELDS), diff --git a/lib/Udp/MiLightDiscoveryServer.cpp b/lib/Udp/MiLightDiscoveryServer.cpp index d5ac3af..9f869c5 100644 --- a/lib/Udp/MiLightDiscoveryServer.cpp +++ b/lib/Udp/MiLightDiscoveryServer.cpp @@ -1,6 +1,11 @@ #include #include -#include + +#ifdef ESP8266 + #include +#elif ESP32 + #include +#endif const char V3_SEARCH_STRING[] = "Link_Wi-Fi"; const char V6_SEARCH_STRING[] = "HF-A11ASSISTHREAD"; @@ -85,6 +90,10 @@ void MiLightDiscoveryServer::sendResponse(char* buffer) { #endif socket.beginPacket(socket.remoteIP(), socket.remotePort()); - socket.write(int(buffer)); +#ifdef ESP8266 + socket.write(buffer); +#elif ESP32 + socket.write(*buffer); +#endif socket.endPacket(); } diff --git a/lib/Udp/MiLightUdpServer.cpp b/lib/Udp/MiLightUdpServer.cpp index 0824c20..8a51bc9 100644 --- a/lib/Udp/MiLightUdpServer.cpp +++ b/lib/Udp/MiLightUdpServer.cpp @@ -1,7 +1,12 @@ #include #include #include -#include + +#ifdef ESP8266 + #include +#elif ESP32 + #include +#endif MiLightUdpServer::MiLightUdpServer(MiLightClient*& client, uint16_t port, uint16_t deviceId) : client(client), diff --git a/lib/Udp/V6MiLightUdpServer.cpp b/lib/Udp/V6MiLightUdpServer.cpp index 2f23924..b4d7403 100644 --- a/lib/Udp/V6MiLightUdpServer.cpp +++ b/lib/Udp/V6MiLightUdpServer.cpp @@ -1,9 +1,14 @@ #include -#include #include #include #include +#ifdef ESP8266 + #include +#elif ESP32 + #include +#endif + #define MATCHES_PACKET(packet1) ( \ matchesPacket(packet1, size(packet1), packet, packetSize) \ ) diff --git a/lib/WebServer/MiLightHttpServer.cpp b/lib/WebServer/MiLightHttpServer.cpp index 1c57454..f07eecb 100644 --- a/lib/WebServer/MiLightHttpServer.cpp +++ b/lib/WebServer/MiLightHttpServer.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include #include #include @@ -11,6 +9,10 @@ #include #include +#ifdef ESP32 + #include +#endif + using namespace std::placeholders; void MiLightHttpServer::begin() { @@ -103,7 +105,7 @@ WiFiClient MiLightHttpServer::client() { return server.client(); } -void MiLightHttpServer::on(const char* path, HTTPMethod method, WebServer::THandlerFunction handler) { +void MiLightHttpServer::on(const char* path, HTTPMethod method, THandlerFunction handler) { server.on(path, method, handler); } @@ -127,7 +129,11 @@ void MiLightHttpServer::handleSystemPost(RequestContext& request) { server.send_P(200, TEXT_PLAIN, PSTR("true")); delay(100); - //ESP.eraseConfig(); +#ifdef ESP8266 + ESP.eraseConfig(); +#elif ESP32 + // TODO erase config +#endif delay(100); ESP.restart(); @@ -227,6 +233,7 @@ void MiLightHttpServer::handleUpdateSettingsPost(RequestContext& request) { } void MiLightHttpServer::handleFirmwarePost() { +#ifdef ESP8266 server.sendHeader("Connection", "close"); server.sendHeader("Access-Control-Allow-Origin", "*"); @@ -247,12 +254,16 @@ void MiLightHttpServer::handleFirmwarePost() { delay(1000); ESP.restart(); +#elif ESP32 + // TODO implement firmware post +#endif } void MiLightHttpServer::handleFirmwareUpload() { +#ifdef ESP8266 HTTPUpload& upload = server.upload(); if(upload.status == UPLOAD_FILE_START){ - //WiFiUDP::stopAll(); + WiFiUDP::stopAll(); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if(!Update.begin(maxSketchSpace)){//start with max available size Update.printError(Serial); @@ -268,6 +279,9 @@ void MiLightHttpServer::handleFirmwareUpload() { } } yield(); +#elif ESP32 + // TODO implement firmware upload +#endif } @@ -449,7 +463,7 @@ void MiLightHttpServer::handleUpdateGroupAlias(RequestContext& request) { if (config == NULL) { char buffer[40]; - sprintf_P(buffer, PSTR("Unknown device type: %u"), bulbId.deviceType); + sprintf_P(buffer, PSTR("Unknown device type: %s"), bulbId.deviceType); request.response.setCode(400); request.response.json["error"] = buffer; return; diff --git a/lib/WebServer/MiLightHttpServer.h b/lib/WebServer/MiLightHttpServer.h index 2f22863..48ebfb3 100644 --- a/lib/WebServer/MiLightHttpServer.h +++ b/lib/WebServer/MiLightHttpServer.h @@ -14,6 +14,7 @@ typedef std::function SettingsSavedHandler; typedef std::function GroupDeletedHandler; +typedef std::function THandlerFunction; using RichHttpConfig = RichHttp::Generics::Configs::EspressifBuiltin; using RequestContext = RichHttpConfig::RequestContextType; @@ -47,7 +48,7 @@ public: void handleClient(); void onSettingsSaved(SettingsSavedHandler handler); void onGroupDeleted(GroupDeletedHandler handler); - void on(const char* path, HTTPMethod method, WebServer::THandlerFunction handler); + void on(const char* path, HTTPMethod method, THandlerFunction handler); void handlePacketSent(uint8_t* packet, const MiLightRemoteConfig& config); WiFiClient client(); @@ -101,7 +102,7 @@ protected: GroupStateStore*& stateStore; SettingsSavedHandler settingsSavedHandler; GroupDeletedHandler groupDeletedHandler; - WebServer::THandlerFunction _handleRootPage; + THandlerFunction _handleRootPage; PacketSender*& packetSender; RadioSwitchboard*& radios; TransitionController& transitions; diff --git a/platformio.ini b/platformio.ini index 973a07a..5c69a17 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,17 +10,16 @@ [common] framework = arduino -platform = espressif8266@~1.8 board_f_cpu = 160000000L lib_deps_builtin = + SPI lib_deps_external = - luc-github/ESP32SSDP@^1.1.1 RF24@~1.3.2 - ArduinoJson@~6.17.2 - PubSubClient@~2.8 - https://github.com/ratkins/RGBConverter.git#07010f2 - WebSockets@~2.3.3 - rlogiacco/CircularBuffer@~1.3.3 + ArduinoJson@~6.10.1 + PubSubClient@~2.7 + https://github.com/ratkins/RGBConverter.git#07010f2605d9f787c6c55e62df10fec14e5894e4 + WebSockets@~2.2.0 + CircularBuffer@~1.2.0 PathVariableHandlers@~2.0.0 RichHttpServer@~2.0.2 extra_scripts = @@ -41,10 +40,12 @@ build_flags = platform = espressif32 framework = ${common.framework} upload_speed = ${common.upload_speed} -board = esp32-poe build_flags = ${common.build_flags} extra_scripts = ${common.extra_scripts} +test_ignore = ${common.test_ignore} +board = esp32-poe lib_deps = ${common.lib_deps_builtin} ${common.lib_deps_external} -test_ignore = ${common.test_ignore} + https://github.com/tzapu/WiFiManager.git#2.0.3-alpha +monitor_speed = 115200 diff --git a/src/main.cpp b/src/main.cpp index 7c0e9ba..04ade48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,12 @@ #ifndef UNIT_TEST +#define DEBUG_PRINTF +#define DEBUG_SERIAL -#include #include -//#include +#include #include #include #include -#include "SPIFFS.h" -#include #include #include #include @@ -19,10 +18,6 @@ #include #include #include -//#include -#include "ESP32SSDP.h" -//#include -#include "ESPmDNS.h" #include #include #include @@ -32,53 +27,24 @@ #include #include #include +#include + +#ifdef ESP8266 + #include + #include +#elif ESP32 + #include + #include +#endif #include #include -//WiFiManager wifiManager; +WiFiManager wifiManager; // because of callbacks, these need to be in the higher scope :( -//WiFiManagerParameter* wifiStaticIP = NULL; -//WiFiManagerParameter* wifiStaticIPNetmask = NULL; -//WiFiManagerParameter* wifiStaticIPGateway = NULL; - -static bool eth_connected = false; -void WiFiEvent(WiFiEvent_t event) -{ - switch (event) { - case SYSTEM_EVENT_ETH_START: - Serial.println("ETH Started"); - //set eth hostname here - ETH.setHostname("esp32-ethernet-milight-gateway"); - break; - case SYSTEM_EVENT_ETH_CONNECTED: - Serial.println("ETH Connected"); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - Serial.print("ETH MAC: "); - Serial.print(ETH.macAddress()); - Serial.print(", IPv4: "); - Serial.print(ETH.localIP()); - if (ETH.fullDuplex()) { - Serial.print(", FULL_DUPLEX"); - } - Serial.print(", "); - Serial.print(ETH.linkSpeed()); - Serial.println("Mbps"); - eth_connected = true; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - Serial.println("ETH Disconnected"); - eth_connected = false; - break; - case SYSTEM_EVENT_ETH_STOP: - Serial.println("ETH Stopped"); - eth_connected = false; - break; - default: - break; - } -} +WiFiManagerParameter* wifiStaticIP = NULL; +WiFiManagerParameter* wifiStaticIPNetmask = NULL; +WiFiManagerParameter* wifiStaticIPGateway = NULL; static LEDStatus *ledStatus; @@ -310,6 +276,7 @@ void applySettings() { if (settings.discoveryPort != 0) { discoveryServer = new MiLightDiscoveryServer(settings); discoveryServer->begin(); + } // update LED pin and operating mode @@ -317,24 +284,23 @@ void applySettings() { ledStatus->changePin(settings.ledPin); ledStatus->continuous(settings.ledModeOperating); } -/* - WiFi.hostname(settings.hostname); - WiFiPhyMode_t wifiMode; - switch (settings.wifiMode) { - case WifiMode::B: - wifiMode = WIFI_PHY_MODE_11B; - break; - case WifiMode::G: - wifiMode = WIFI_PHY_MODE_11G; - break; - default: - case WifiMode::N: - wifiMode = WIFI_PHY_MODE_11N; - break; - } - WiFi.setPhyMode(wifiMode); - */ + // WiFi.hostname(settings.hostname); + + // WiFiPhyMode_t wifiMode; + // switch (settings.wifiMode) { + // case WifiMode::B: + // wifiMode = WIFI_PHY_MODE_11B; + // break; + // case WifiMode::G: + // wifiMode = WIFI_PHY_MODE_11G; + // break; + // default: + // case WifiMode::N: + // wifiMode = WIFI_PHY_MODE_11N; + // break; + // } + // WiFi.setPhyMode(wifiMode); } /** @@ -352,14 +318,14 @@ bool shouldRestart() { void handleLED() { ledStatus->handle(); } -/* + void wifiExtraSettingsChange() { settings.wifiStaticIP = wifiStaticIP->getValue(); settings.wifiStaticIPNetmask = wifiStaticIPNetmask->getValue(); settings.wifiStaticIPGateway = wifiStaticIPGateway->getValue(); settings.save(); } -*/ + // Called when a group is deleted via the REST API. Will publish an empty message to // the MQTT topic to delete retained state void onGroupDeleted(const BulbId& id) { @@ -374,13 +340,23 @@ void onGroupDeleted(const BulbId& id) { } void setup() { - Serial.begin(38400); - //String ssid = "ESP" + String(ESP.getChipId()); + Serial.begin(115200); + + String ssid = "ESP" + String(getESPId()); // load up our persistent settings from the file system +#ifdef ESP8266 SPIFFS.begin(); +#elif ESP32 + if(!SPIFFS.begin(true)){ + Serial.println(F("Error while mounting SPIFFS")); + } +#endif + Settings::load(settings); +#ifdef ESP8266 applySettings(); +#endif // set up the LED status for wifi configuration ledStatus = new LEDStatus(settings.ledPin); @@ -395,11 +371,15 @@ void setup() { // allows the "autoConnect" method to be non-blocking which can implement this same functionality. However, // that change is only on the development branch so we are going to continue to use this fork until // that is merged and ready. - //wifiManager.setSetupLoopCallback(handleLED); +#ifdef ESP8266 + wifiManager.setSetupLoopCallback(handleLED); +#elif ESP32 + // TODO check if the non-blocking implementation can be used or create a version with setSetupLoopCallback +#endif // Allows us to have static IP config in the captive portal. Yucky pointers to pointers, just to have the settings carry through - //wifiManager.setSaveConfigCallback(wifiExtraSettingsChange); -/* + wifiManager.setSaveConfigCallback(wifiExtraSettingsChange); + wifiStaticIP = new WiFiManagerParameter( "staticIP", "Static IP (Leave blank for dhcp)", @@ -423,7 +403,7 @@ void setup() { MAX_IP_ADDR_LEN ); wifiManager.addParameter(wifiStaticIPGateway); -*/ + // We have a saved static IP, let's try and use it. if (settings.wifiStaticIP.length() > 0) { Serial.printf_P(PSTR("We have a static IP: %s\n"), settings.wifiStaticIP.c_str()); @@ -432,14 +412,10 @@ void setup() { _ip.fromString(settings.wifiStaticIP); _subnet.fromString(settings.wifiStaticIPNetmask); _gw.fromString(settings.wifiStaticIPGateway); - ETH.begin(); - ETH.config(_ip, _gw, _subnet, _gw, _gw); - //wifiManager.setSTAStaticIPConfig(_ip,_gw,_subnet); - } else { - ETH.begin(); + wifiManager.setSTAStaticIPConfig(_ip,_gw,_subnet); } -/* + wifiManager.setConfigPortalTimeout(180); if (wifiManager.autoConnect(ssid.c_str(), "milightHub")) { @@ -449,25 +425,42 @@ void setup() { // if the config portal was started, make sure to turn off the config AP WiFi.mode(WIFI_STA); + +#ifdef ESP32 + applySettings(); +#endif } else { // set LED mode for Wifi failed ledStatus->continuous(settings.ledModeWifiFailed); Serial.println(F("Wifi failed. Restarting in 10 seconds.\n")); -*/ + + delay(10000); + ESP.restart(); + } + MDNS.addService("http", "tcp", 80); +#ifdef ESP8266 SSDP.setSchemaURL("description.xml"); SSDP.setHTTPPort(80); SSDP.setName("ESP8266 MiLight Gateway"); - //SSDP.setSerialNumber(ESP.getChipId()); + SSDP.setSerialNumber(ESP.getChipId()); SSDP.setURL("/"); SSDP.setDeviceType("upnp:rootdevice"); SSDP.begin(); +#elif ESP32 + // TODO SSDP +#endif + httpServer = new MiLightHttpServer(settings, milightClient, stateStore, packetSender, radios, transitions); httpServer->onSettingsSaved(applySettings); httpServer->onGroupDeleted(onGroupDeleted); +#ifdef ESP8266 httpServer->on("/description.xml", HTTP_GET, []() { SSDP.schema(httpServer->client()); }); +#elif ESP32 + // TODO SSDP +#endif httpServer->begin(); transitions.addListener( @@ -486,6 +479,7 @@ void setup() { } void loop() { + httpServer->handleClient(); if (mqttClient) {