Browse Source

Merge branch 'dev' into sensors

Conflicts:
	code/espurna/config/general.h
	code/espurna/data/index.html.gz
	code/espurna/static/index.html.gz.h
	code/espurna/ws.ino
fastled
Xose Pérez 7 years ago
parent
commit
d2670cd06b
16 changed files with 3582 additions and 2868 deletions
  1. +1
    -0
      code/espurna/config/arduino.h
  2. +11
    -2
      code/espurna/config/general.h
  3. +1
    -0
      code/espurna/config/prototypes.h
  4. BIN
      code/espurna/data/index.html.gz
  5. +9
    -0
      code/espurna/espurna.ino
  6. +24
    -0
      code/espurna/libs/EmbedisWrap.h
  7. +376
    -0
      code/espurna/libs/SSDPDevice.cpp
  8. +196
    -0
      code/espurna/libs/SSDPDevice.h
  9. +158
    -115
      code/espurna/settings.ino
  10. +40
    -0
      code/espurna/ssdp.ino
  11. +2573
    -2570
      code/espurna/static/index.html.gz.h
  12. +55
    -0
      code/espurna/web.ino
  13. +3
    -0
      code/espurna/wifi.ino
  14. +91
    -147
      code/espurna/ws.ino
  15. +40
    -26
      code/html/custom.js
  16. +4
    -8
      code/html/index.html

+ 1
- 0
code/espurna/config/arduino.h View File

@ -74,6 +74,7 @@
//#define NTP_SUPPORT 0 //#define NTP_SUPPORT 0
//#define RF_SUPPORT 1 //#define RF_SUPPORT 1
//#define SPIFFS_SUPPORT 1 //#define SPIFFS_SUPPORT 1
//#define SSDP_SUPPORT 1
//#define TELNET_SUPPORT 0 //#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0 //#define TERMINAL_SUPPORT 0
//#define WEB_SUPPORT 0 //#define WEB_SUPPORT 0


+ 11
- 2
code/espurna/config/general.h View File

@ -140,6 +140,7 @@
#define CUSTOM_RESET_MQTT 4 // Reset via MQTT #define CUSTOM_RESET_MQTT 4 // Reset via MQTT
#define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP) #define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP)
#define CUSTOM_RESET_OTA 6 // Reset after successful OTA update #define CUSTOM_RESET_OTA 6 // Reset after successful OTA update
#define CUSTOM_RESET_HTTP 7 // Reset via HTTP GET
#define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update #define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update
#define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface #define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface
#define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal #define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal
@ -152,13 +153,15 @@ PROGMEM const char custom_reset_terminal[] = "Reboot from terminal";
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT"; PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT";
PROGMEM const char custom_reset_rpc[] = "Reboot from RPC"; PROGMEM const char custom_reset_rpc[] = "Reboot from RPC";
PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update"; PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update";
PROGMEM const char custom_reset_http[] = "Reboot from HTTP";
PROGMEM const char custom_reset_nofuss[] = "Reboot after successful NoFUSS update"; PROGMEM const char custom_reset_nofuss[] = "Reboot after successful NoFUSS update";
PROGMEM const char custom_reset_upgrade[] = "Reboot after successful web update"; PROGMEM const char custom_reset_upgrade[] = "Reboot after successful web update";
PROGMEM const char custom_reset_factory[] = "Factory reset"; PROGMEM const char custom_reset_factory[] = "Factory reset";
PROGMEM const char* const custom_reset_string[] = { PROGMEM const char* const custom_reset_string[] = {
custom_reset_hardware, custom_reset_web, custom_reset_terminal, custom_reset_hardware, custom_reset_web, custom_reset_terminal,
custom_reset_mqtt, custom_reset_rpc, custom_reset_ota, custom_reset_mqtt, custom_reset_rpc, custom_reset_ota,
custom_reset_nofuss, custom_reset_upgrade, custom_reset_factory
custom_reset_http, custom_reset_nofuss, custom_reset_upgrade,
custom_reset_factory
}; };
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -327,7 +330,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define UI_TAG_SELECT 2 #define UI_TAG_SELECT 2
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// MDNS & LLMNR
// MDNS / LLMNR / NETBIOS / SSDP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef MDNS_SUPPORT #ifndef MDNS_SUPPORT
@ -342,6 +345,10 @@ PROGMEM const char* const custom_reset_string[] = {
#define NETBIOS_SUPPORT 0 // Publish device using NetBIOS protocol by default (1.26Kb) - requires 2.4.0 #define NETBIOS_SUPPORT 0 // Publish device using NetBIOS protocol by default (1.26Kb) - requires 2.4.0
#endif #endif
#ifndef SSDP_SUPPORT
#define SSDP_SUPPORT 0 // Publish device using SSDP protocol by default (3.32Kb)
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SPIFFS // SPIFFS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -496,6 +503,8 @@ PROGMEM const char* const custom_reset_string[] = {
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit #define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#endif #endif
#define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// LIGHT // LIGHT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 1
- 0
code/espurna/config/prototypes.h View File

@ -47,6 +47,7 @@ template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value); template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue); template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue); template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
bool settingsRestore(JsonObject& data);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// I2C // I2C


BIN
code/espurna/data/index.html.gz View File


+ 9
- 0
code/espurna/espurna.ino View File

@ -188,6 +188,9 @@ void welcome() {
#if SPIFFS_SUPPORT #if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS")); DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif #endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT #if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET")); DEBUG_MSG_P(PSTR(" TELNET"));
#endif #endif
@ -332,6 +335,9 @@ void setup() {
#if NETBIOS_SUPPORT #if NETBIOS_SUPPORT
netbiosSetup(); netbiosSetup();
#endif #endif
#if SSDP_SUPPORT
ssdpSetup();
#endif
#if NTP_SUPPORT #if NTP_SUPPORT
ntpSetup(); ntpSetup();
#endif #endif
@ -409,6 +415,9 @@ void loop() {
#if POWER_PROVIDER != POWER_PROVIDER_NONE #if POWER_PROVIDER != POWER_PROVIDER_NONE
powerLoop(); powerLoop();
#endif #endif
#if SSDP_SUPPORT
ssdpLoop();
#endif
#if NTP_SUPPORT #if NTP_SUPPORT
ntpLoop(); ntpLoop();
#endif #endif


+ 24
- 0
code/espurna/libs/EmbedisWrap.h View File

@ -0,0 +1,24 @@
// -----------------------------------------------------------------------------
// Wrap class around Embedis (settings & terminal)
// -----------------------------------------------------------------------------
#pragma once
#include "Embedis.h"
class EmbedisWrap : public Embedis {
public:
EmbedisWrap(Stream& stream, size_t buflen = 128, size_t argvlen = 8): Embedis(stream, buflen, argvlen) {}
unsigned char getCommandsCount() {
return commands.size();
}
String getCommandName(unsigned char i) {
if (i < commands.size()) return commands[i].name;
return String();
}
};

+ 376
- 0
code/espurna/libs/SSDPDevice.cpp View File

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

+ 196
- 0
code/espurna/libs/SSDPDevice.h View File

@ -0,0 +1,196 @@
// SSDPDevice.h
#ifndef _SSDPDEVICE_h
#define _SSDPDEVICE_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#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
#define SSDP_QUEUE_SIZE 21
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250);
#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
static const char* PROGMEM SSDP_RESPONSE_TEMPLATE =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n";
static const char* PROGMEM SSDP_NOTIFY_ALIVE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:alive\r\n";
static const char* PROGMEM SSDP_NOTIFY_UPDATE_TEMPLATE =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:update\r\n";
static const char* PROGMEM SSDP_PACKET_TEMPLATE =
"%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL
"SERVER: UPNP/1.1 %s/%s\r\n" // m_modelName, m_modelNumber
"USN: %s%s%s\r\n" // m_uuid
"%s: %s\r\n" // "NT" or "ST", m_deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), m_port, m_schemaURL
"\r\n";
static const char* PROGMEM 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"
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/%s</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>%s</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>%s</presentationURL>"
"<serialNumber>%s</serialNumber>"
"<modelName>%s</modelName>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"</device>"
// "<iconList>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>48</height>"
// "<width>48</width>"
// "<depth>24</depth>"
// "<url>icon48.png</url>"
// "</icon>"
// "<icon>"
// "<mimetype>image/png</mimetype>"
// "<height>120</height>"
// "<width>120</width>"
// "<depth>24</depth>"
// "<url>icon120.png</url>"
// "</icon>"
// "</iconList>"
"</root>\r\n"
"\r\n";
typedef enum {
NOTIFY_ALIVE_INIT,
NOTIFY_ALIVE,
NOTIFY_UPDATE,
RESPONSE
} ssdp_message_t;
typedef enum {
ROOT_FOR_ALL,
ROOT_BY_UUID,
ROOT_BY_TYPE
} ssdp_udn_t;
typedef struct {
unsigned long time;
ssdp_message_t type;
ssdp_udn_t udn;
uint32_t address;
uint16_t port;
} ssdp_send_parameters_t;
class SSDPDeviceClass {
private:
WiFiUDP *m_server;
IPAddress m_last;
char m_schemaURL[SSDP_SCHEMA_URL_SIZE];
char m_uuid[SSDP_UUID_SIZE];
char m_deviceType[SSDP_DEVICE_TYPE_SIZE];
char m_friendlyName[SSDP_FRIENDLY_NAME_SIZE];
char m_serialNumber[SSDP_SERIAL_NUMBER_SIZE];
char m_presentationURL[SSDP_PRESENTATION_URL_SIZE];
char m_manufacturer[SSDP_MANUFACTURER_SIZE];
char m_manufacturerURL[SSDP_MANUFACTURER_URL_SIZE];
char m_modelName[SSDP_MODEL_NAME_SIZE];
char m_modelURL[SSDP_MODEL_URL_SIZE];
char m_modelNumber[SSDP_MODEL_VERSION_SIZE];
uint16_t m_port;
uint8_t m_ttl;
ssdp_send_parameters_t m_queue[SSDP_QUEUE_SIZE];
protected:
bool readLine(String &value);
bool readKeyValue(String &key, String &value);
void postNotifyALive();
void postNotifyUpdate();
void postResponse(long mx);
void postResponse(ssdp_udn_t udn, long mx);
void post(ssdp_message_t type, ssdp_udn_t udn, IPAddress address, uint16_t port, unsigned long time);
void send(ssdp_send_parameters_t *parameters);
public:
SSDPDeviceClass();
void update();
String schema();
void handleClient();
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);
};
extern SSDPDeviceClass SSDPDevice;
#endif

+ 158
- 115
code/espurna/settings.ino View File

@ -6,9 +6,9 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#include "Embedis.h"
#include <EEPROM.h> #include <EEPROM.h>
#include "spi_flash.h" #include "spi_flash.h"
#include "libs/EmbedisWrap.h"
#include <StreamString.h> #include <StreamString.h>
#if TELNET_SUPPORT #if TELNET_SUPPORT
@ -18,12 +18,12 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#else #else
StreamInjector _serial = StreamInjector(Serial); StreamInjector _serial = StreamInjector(Serial);
#endif #endif
Embedis embedis(_serial);
EmbedisWrap embedis(_serial);
#else #else
#ifdef DEBUG_PORT #ifdef DEBUG_PORT
Embedis embedis(DEBUG_PORT);
EmbedisWrap embedis(DEBUG_PORT);
#else #else
Embedis embedis(_serial);
EmbedisWrap embedis(_serial);
#endif #endif
#endif #endif
@ -90,6 +90,28 @@ String settingsKeyName(unsigned int index) {
} }
bool settingsRestore(JsonObject& data) {
const char* app = data["app"];
if (strcmp(app, APP_NAME) != 0) return false;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
saveSettings();
DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
return true;
}
void settingsFactoryReset() { void settingsFactoryReset() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF); EEPROM.write(i, 0xFF);
@ -97,6 +119,18 @@ void settingsFactoryReset() {
EEPROM.commit(); EEPROM.commit();
} }
void settingsHelp() {
unsigned char len = embedis.getCommandsCount();
DEBUG_MSG_P(PSTR("\nAvailable commands:\n\n"));
for (unsigned char i=0; i<len; i++) {
DEBUG_MSG_P(PSTR("* %s\n"), embedis.getCommandName(i).c_str());
if (embedis.getCommandName(i).equals("WRITE")) {
DEBUG_MSG_P(PSTR("\n"));
}
}
DEBUG_MSG_P(PSTR("\n"));
}
void settingsSetup() { void settingsSetup() {
EEPROM.begin(SPI_FLASH_SEC_SIZE); EEPROM.begin(SPI_FLASH_SEC_SIZE);
@ -126,33 +160,82 @@ void settingsSetup() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if MQTT_SUPPORT
Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
lightChannel(id, value);
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#endif #endif
Embedis::command( F("INFO"), [](Embedis* e) {
welcome();
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("COLOR"), [](Embedis* e) {
if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if DEBUG_SUPPORT
Embedis::command( F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("DUMP"), [](Embedis* e) {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
DEBUG_MSG_P(PSTR("+%s => %s\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
Embedis::command( F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
}
DEBUG_MSG_P(PSTR("\n+OK\n"));
}); });
Embedis::command( F("RESET"), [](Embedis* e) {
Embedis::command( F("EEPROM"), [](Embedis* e) {
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), settingsKeyCount());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
}); });
Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) { Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) {
@ -162,13 +245,6 @@ void settingsSetup() {
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494 *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
}); });
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
#endif
Embedis::command( F("FACTORY.RESET"), [](Embedis* e) { Embedis::command( F("FACTORY.RESET"), [](Embedis* e) {
settingsFactoryReset(); settingsFactoryReset();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
@ -179,6 +255,48 @@ void settingsSetup() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
Embedis::command( F("HELP"), [](Embedis* e) {
settingsHelp();
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("INFO"), [](Embedis* e) {
welcome();
wifiStatus();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("MIRED"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
#endif
Embedis::command( F("RELAY"), [](Embedis* e) { Embedis::command( F("RELAY"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
@ -196,105 +314,30 @@ void settingsSetup() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
Embedis::command( F("COLOR"), [](Embedis* e) {
if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("MIRED"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
Embedis::command( F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
Embedis::command( F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
lightChannel(id, value);
lightUpdate(true, true);
}
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
#if MQTT_SUPPORT
Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#endif #endif
Embedis::command( F("EEPROM"), [](Embedis* e) {
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), settingsKeyCount());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
Embedis::command( F("DUMP"), [](Embedis* e) {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
DEBUG_MSG_P(PSTR("+%s => %s\n"), key.c_str(), value.c_str());
}
Embedis::command( F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if DEBUG_SUPPORT
Embedis::command( F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
}
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE); DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[SETTINGS] Settings size: %d bytes\n"), settingsSize()); DEBUG_MSG_P(PSTR("[SETTINGS] Settings size: %d bytes\n"), settingsSize());


+ 40
- 0
code/espurna/ssdp.ino View File

@ -0,0 +1,40 @@
/*
SSDP MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
Uses SSDP library by PawelDino (https://github.com/PawelDino)
https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604
*/
#if SSDP_SUPPORT
#include <libs/SSDPDevice.h>
void ssdpSetup() {
SSDPDevice.setName(getSetting("hostname"));
SSDPDevice.setDeviceType("urn:schemas-upnp-org:device:BinaryLight:1");
SSDPDevice.setSchemaURL("description.xml");
SSDPDevice.setSerialNumber(ESP.getChipId());
SSDPDevice.setURL("/");
SSDPDevice.setModelName(DEVICE);
SSDPDevice.setModelNumber("");
SSDPDevice.setManufacturer(MANUFACTURER);
#if WEB_SUPPORT
webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) {
DEBUG_MSG_P(PSTR("[SSDP] Schema request\n"));
String schema = SSDPDevice.schema();
request->send(200, "application/xml", schema.c_str());
});
#endif
}
void ssdpLoop() {
SSDPDevice.handleClient();
}
#endif // SSDP_SUPPORT

+ 2573
- 2570
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 55
- 0
code/espurna/web.ino View File

@ -28,11 +28,18 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
AsyncWebServer * _server; AsyncWebServer * _server;
char _last_modified[50]; char _last_modified[50];
std::vector<uint8_t> * _webConfigBuffer;
bool _webConfigSuccess = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// HOOKS // HOOKS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _onReset(AsyncWebServerRequest *request) {
deferredReset(100, CUSTOM_RESET_HTTP);
request->send(200);
}
void _onGetConfig(AsyncWebServerRequest *request) { void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
@ -59,6 +66,52 @@ void _onGetConfig(AsyncWebServerRequest *request) {
} }
void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
request->send(_webConfigSuccess ? 200 : 400);
}
void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
// No buffer
if (final && (index == 0)) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) data);
if (root.success()) _webConfigSuccess = settingsRestore(root);
return;
}
// Buffer start => reset
if (index == 0) if (_webConfigBuffer) delete _webConfigBuffer;
// init buffer if it doesn't exist
if (!_webConfigBuffer) {
_webConfigBuffer = new std::vector<uint8_t>();
_webConfigSuccess = false;
}
// Copy
if (len > 0) {
_webConfigBuffer->reserve(_webConfigBuffer->size() + len);
_webConfigBuffer->insert(_webConfigBuffer->end(), data, data + len);
}
// Ending
if (final) {
_webConfigBuffer->push_back(0);
// Parse JSON
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) _webConfigBuffer->data());
if (root.success()) _webConfigSuccess = settingsRestore(root);
delete _webConfigBuffer;
}
}
#if WEB_EMBEDDED #if WEB_EMBEDDED
void _onHome(AsyncWebServerRequest *request) { void _onHome(AsyncWebServerRequest *request) {
@ -250,7 +303,9 @@ void webSetup() {
#if WEB_EMBEDDED #if WEB_EMBEDDED
_server->on("/index.html", HTTP_GET, _onHome); _server->on("/index.html", HTTP_GET, _onHome);
#endif #endif
_server->on("/reset", HTTP_GET, _onReset);
_server->on("/config", HTTP_GET, _onGetConfig); _server->on("/config", HTTP_GET, _onGetConfig);
_server->on("/config", HTTP_POST | HTTP_PUT, _onPostConfig, _onPostConfigData);
_server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData); _server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData);
// Serve static files // Serve static files


+ 3
- 0
code/espurna/wifi.ino View File

@ -84,6 +84,9 @@ void wifiConfigure() {
if (!systemCheck()) return; if (!systemCheck()) return;
#endif #endif
// Clean settings
wifiClean(WIFI_MAX_NETWORKS);
int i; int i;
for (i = 0; i< WIFI_MAX_NETWORKS; i++) { for (i = 0; i< WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break; if (getSetting("ssid" + String(i)).length() == 0) break;


+ 91
- 147
code/espurna/ws.ino View File

@ -33,6 +33,43 @@ void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload
} }
#endif #endif
bool _wsStore(String key, String value) {
// HTTP port
if (key == "webPort") {
if ((value.toInt() == 0) || (value.toInt() == 80)) {
return delSetting(key);
}
}
if (value != getSetting(key)) {
return setSetting(key, value);
}
return false;
}
bool _wsStore(String key, JsonArray& value) {
bool changed = false;
unsigned char index = 0;
for (auto element : value) {
if (_wsStore(key + index, element.as<String>())) changed = true;
index++;
}
// Delete further values
for (unsigned char i=index; i<SETTINGS_MAX_LIST_COUNT; i++) {
if (!delSetting(key, index)) break;
changed = true;
}
return changed;
}
void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) { void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : ""); //DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : "");
@ -51,171 +88,82 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
// Check actions ----------------------------------------------------------- // Check actions -----------------------------------------------------------
if (root.containsKey("action")) {
String action = root["action"];
JsonObject& data = root.containsKey("data") ? root["data"] : jsonBuffer.createObject();
const char* action = root["action"];
if (action) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action.c_str());
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action);
// Callbacks
for (unsigned char i = 0; i < _ws_on_action_callbacks.size(); i++) {
(_ws_on_action_callbacks[i])(action.c_str(), data);
}
if (action.equals("reboot")) deferredReset(100, CUSTOM_RESET_WEB);
if (action.equals("reconnect")) _web_defer.once_ms(100, wifiDisconnect);
if (strcmp(action, "reboot") == 0) deferredReset(100, CUSTOM_RESET_WEB);
if (strcmp(action, "reconnect") == 0) _web_defer.once_ms(100, wifiDisconnect);
if (action.equals("restore")) {
JsonObject& data = root["data"];
if (data.success()) {
if (!data.containsKey("app") || (data["app"] != APP_NAME)) {
wsSend_P(client_id, PSTR("{\"message\": 4}"));
return;
}
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
// Callbacks
for (unsigned char i = 0; i < _ws_on_action_callbacks.size(); i++) {
(_ws_on_action_callbacks[i])(action, data);
} }
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
// Restore configuration via websockets
if (strcmp(action, "restore") == 0) {
if (settingsRestore(data)) {
wsSend_P(client_id, PSTR("{\"message\": 5}"));
} else {
wsSend_P(client_id, PSTR("{\"message\": 4}"));
}
} }
saveSettings();
wsSend_P(client_id, PSTR("{\"message\": 5}"));
} }
}; };
// Check configuration ----------------------------------------------------- // Check configuration -----------------------------------------------------
if (root.containsKey("config") && root["config"].is<JsonArray&>()) {
JsonObject& config = root["config"];
if (config.success()) {
JsonArray& config = root["config"];
DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n")); DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n"));
unsigned char webMode = WEB_MODE_NORMAL;
String adminPass;
bool save = false; bool save = false;
bool changed = false;
#if MQTT_SUPPORT #if MQTT_SUPPORT
bool changedMQTT = false; bool changedMQTT = false;
#endif #endif
unsigned int wifiIdx = 0;
unsigned int dczRelayIdx = 0;
unsigned int mqttGroupIdx = 0;
String adminPass;
for (unsigned int i=0; i<config.size(); i++) {
String key = config[i]["name"];
String value = config[i]["value"];
// Skip firmware filename
if (key.equals("filename")) continue;
for (auto kv: config) {
// -----------------------------------------------------------------
// GENERAL
// -----------------------------------------------------------------
// Web mode (normal or password)
if (key == "webMode") {
webMode = value.toInt();
continue;
}
// HTTP port
if (key == "webPort") {
if ((value.toInt() == 0) || (value.toInt() == 80)) {
save = changed = true;
delSetting(key);
continue;
}
}
bool changed = false;
String key = kv.key;
JsonVariant& value = kv.value;
// Check password // Check password
if (key == "adminPass1") {
adminPass = value;
continue;
}
if (key == "adminPass2") {
if (!value.equals(adminPass)) {
if (key == "adminPass") {
if (!value.is<JsonArray&>()) continue;
JsonArray& values = value.as<JsonArray&>();
if (values.size() != 2) continue;
if (values[0].as<String>().equals(values[1].as<String>())) {
String password = values[0].as<String>();
if (password.length() > 0) {
setSetting(key, password);
save = true;
}
wsSend_P(client_id, PSTR("{\"action\": \"reload\"}"));
} else {
wsSend_P(client_id, PSTR("{\"message\": 7}")); wsSend_P(client_id, PSTR("{\"message\": 7}"));
return;
} }
if (value.length() == 0) continue;
wsSend_P(client_id, PSTR("{\"action\": \"reload\"}"));
key = String("adminPass");
continue;
} }
// -----------------------------------------------------------------
// DOMOTICZ
// -----------------------------------------------------------------
#if DOMOTICZ_SUPPORT
if (key == "dczRelayIdx") {
if (dczRelayIdx >= relayCount()) continue;
key = key + String(dczRelayIdx);
++dczRelayIdx;
}
#else
if (key.startsWith("dcz")) continue;
#endif
// -----------------------------------------------------------------
// MQTT GROUP TOPICS
// -----------------------------------------------------------------
#if MQTT_SUPPORT
if (key == "mqttGroup") {
key = key + String(mqttGroupIdx);
}
if (key == "mqttGroupInv") {
key = key + String(mqttGroupIdx);
++mqttGroupIdx;
}
#endif
// -----------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------
if (key == "ssid") {
key = key + String(wifiIdx);
}
if (key == "pass") {
key = key + String(wifiIdx);
}
if (key == "ip") {
key = key + String(wifiIdx);
// Store values
if (value.is<JsonArray&>()) {
if (_wsStore(key, value.as<JsonArray&>())) changed = true;
} else {
if (_wsStore(key, value.as<String>())) changed = true;
} }
if (key == "gw") {
key = key + String(wifiIdx);
}
if (key == "mask") {
key = key + String(wifiIdx);
}
if (key == "dns") {
key = key + String(wifiIdx);
++wifiIdx;
}
// -----------------------------------------------------------------
if (value != getSetting(key)) {
setSetting(key, value);
save = changed = true;
// Update flags if value has changed
if (changed) {
save = true;
#if MQTT_SUPPORT #if MQTT_SUPPORT
if (key.startsWith("mqtt")) changedMQTT = true; if (key.startsWith("mqtt")) changedMQTT = true;
#endif #endif
@ -223,10 +171,6 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
} }
if (webMode == WEB_MODE_NORMAL) {
if (wifiClean(wifiIdx)) save = changed = true;
}
// Save settings // Save settings
if (save) { if (save) {
@ -235,9 +179,9 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
(_ws_on_after_parse_callbacks[i])(); (_ws_on_after_parse_callbacks[i])();
} }
// This should got to callback as well
// but first change management has to be in place
#if MQTT_SUPPORT
// This should got to callback as well
// but first change management has to be in place
#if MQTT_SUPPORT
if (changedMQTT) { if (changedMQTT) {
mqttConfigure(); mqttConfigure();
mqttDisconnect(); mqttDisconnect();
@ -247,13 +191,12 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
// Persist settings // Persist settings
saveSettings(); saveSettings();
}
if (changed) {
wsSend_P(client_id, PSTR("{\"message\": 8}")); wsSend_P(client_id, PSTR("{\"message\": 8}"));
} else { } else {
wsSend_P(client_id, PSTR("{\"message\": 9}")); wsSend_P(client_id, PSTR("{\"message\": 9}"));
} }
} }
@ -262,10 +205,11 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
void _wsOnStart(JsonObject& root) { void _wsOnStart(JsonObject& root) {
bool changePassword = false;
#if WEB_FORCE_PASS_CHANGE #if WEB_FORCE_PASS_CHANGE
String adminPass = getSetting("adminPass", ADMIN_PASS); String adminPass = getSetting("adminPass", ADMIN_PASS);
if (adminPass.equals(ADMIN_PASS)) changePassword = true;
bool changePassword = adminPass.equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif #endif
if (changePassword) { if (changePassword) {


+ 40
- 26
code/html/custom.js View File

@ -79,13 +79,13 @@ function zeroPad(number, positions) {
function validateForm(form) { function validateForm(form) {
// password // password
var adminPass1 = $("input[name='adminPass1']", form).val();
var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !checkPassword(adminPass1)) { if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!"); alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
return false; return false;
} }
var adminPass2 = $("input[name='adminPass2']", form).val();
var adminPass2 = $("input[name='adminPass']", form).last().val();
if (adminPass1 != adminPass2) { if (adminPass1 != adminPass2) {
alert("Passwords are different!"); alert("Passwords are different!");
return false; return false;
@ -95,14 +95,40 @@ function validateForm(form) {
} }
function valueSet(data, name, value) {
for (var i in data) {
if (data[i]['name'] == name) {
data[i]['value'] = value;
return;
// These fields will always be a list of values
var is_group = ["ssid", "pass", "gw", "mask", "ip", "dns", "mqttGroup", "mqttGroupInv", "dczRelayIdx", "ledMode", "ntpServer", "adminPass"];
function getData(form) {
var data = {};
// Populate data
$("input,select", form).each(function() {
var name = $(this).attr("name");
if (name) {
if ($(this).attr('type') == 'checkbox') {
value = $(this).is(':checked') ? 1 : 0;
} else if ($(this).attr('type') == 'radio') {
if (!$(this).is(':checked')) return;
value = $(this).val();
} else {
value = $(this).val();
}
if (name in data) {
data[name].push(value);
} else if (is_group.indexOf(name) >= 0) {
data[name] = [value];
} else {
data[name] = value;
}
} }
}
data.push({'name': name, 'value': value});
});
// Delete unwanted fields
delete(data["filename"]);
return data;
} }
function randomString(length, chars) { function randomString(length, chars) {
@ -145,28 +171,19 @@ function doReload(milliseconds) {
function doUpdate() { function doUpdate() {
var form = $("#formSave"); var form = $("#formSave");
if (validateForm(form)) { if (validateForm(form)) {
// Get data // Get data
var data = form.serializeArray();
// Post-process
delete(data['filename']);
$("input[type='checkbox']").each(function() {
var name = $(this).attr("name");
if (name) {
valueSet(data, name, $(this).is(':checked') ? 1 : 0);
}
});
var data = getData(form);
websock.send(JSON.stringify({'config': data})); websock.send(JSON.stringify({'config': data}));
// Empty special fields
$(".pwrExpected").val(0); $(".pwrExpected").val(0);
$("input[name='pwrResetCalibration']") $("input[name='pwrResetCalibration']")
.prop("checked", false) .prop("checked", false)
.iphoneStyle("refresh"); .iphoneStyle("refresh");
// Change handling
numChanged = 0; numChanged = 0;
setTimeout(function() { setTimeout(function() {
@ -252,7 +269,7 @@ function doUpgrade() {
function doUpdatePassword() { function doUpdatePassword() {
var form = $("#formPassword"); var form = $("#formPassword");
if (validateForm(form)) { if (validateForm(form)) {
var data = form.serializeArray();
var data = getData(form);
websock.send(JSON.stringify({'config': data})); websock.send(JSON.stringify({'config': data}));
} }
return false; return false;
@ -973,10 +990,7 @@ function hasChanged() {
} }
function resetOriginals() { function resetOriginals() {
$("input").each(function() {
$(this).attr("original", $(this).val());
})
$("select").each(function() {
$("input,select").each(function() {
$(this).attr("original", $(this).val()); $(this).attr("original", $(this).val());
}) })
numReboot = numReconnect = numReload = 0; numReboot = numReconnect = numReload = 0;


+ 4
- 8
code/html/index.html View File

@ -26,8 +26,6 @@
<form id="formPassword" class="pure-form" action="/" method="post"> <form id="formPassword" class="pure-form" action="/" method="post">
<input class="pure-u-1 pure-u-sm-3-4" type="hidden" name="webMode" value="1" />
<div class="panel block"> <div class="panel block">
<div class="header"> <div class="header">
@ -41,7 +39,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4">Admin password</label> <label class="pure-u-1 pure-u-md-1-4">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="1" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="1" autocomplete="false" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint"> <div class="pure-u-1 pure-u-md-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br /> The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
@ -50,7 +48,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4">Admin password (repeat)</label> <label class="pure-u-1 pure-u-md-1-4">Admin password (repeat)</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="2" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="2" autocomplete="false" />
</div> </div>
<button class="pure-button button-update-password">Update</button> <button class="pure-button button-update-password">Update</button>
@ -232,8 +230,6 @@
<form id="formSave" class="pure-form" action="/" method="post" enctype="multipart/form-data"> <form id="formSave" class="pure-form" action="/" method="post" enctype="multipart/form-data">
<input class="pure-u-1 pure-u-sm-3-4" type="hidden" name="webMode" value="0" />
<div class="panel" id="panel-general"> <div class="panel" id="panel-general">
<div class="header"> <div class="header">
@ -479,7 +475,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4">Admin password</label> <label class="pure-u-1 pure-u-md-1-4">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" action="reboot" tabindex="11" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="password" action="reboot" tabindex="11" autocomplete="false" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint"> <div class="pure-u-1 pure-u-md-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br /> The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
@ -488,7 +484,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4">Repeat password</label> <label class="pure-u-1 pure-u-md-1-4">Repeat password</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" action="reboot" tabindex="12" autocomplete="false" />
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="password" action="reboot" tabindex="12" autocomplete="false" />
</div> </div>
<div class="pure-g"> <div class="pure-g">


Loading…
Cancel
Save