mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2026-10-12 04:12:13 +02:00
init
This commit is contained in:
parent
eb75868adb
commit
a64b648f7a
56 changed files with 1003 additions and 0 deletions
1051
src/ConfigFile.cpp
Normal file
1051
src/ConfigFile.cpp
Normal file
File diff suppressed because it is too large
Load diff
99
src/ConfigFile.h
Normal file
99
src/ConfigFile.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#include <ArduinoJson.h>
|
||||
#include <LittleFS.h>
|
||||
#include "Somfy.h"
|
||||
#include "ConfigSettings.h"
|
||||
|
||||
#ifndef configfile_h
|
||||
#define configfile_h
|
||||
|
||||
#define CFG_VALUE_SEP ','
|
||||
#define CFG_REC_END '\n'
|
||||
#define CFG_TOK_NONE 0x00
|
||||
#define CFG_TOK_QUOTE '"'
|
||||
|
||||
|
||||
struct config_header_t {
|
||||
uint8_t version = 1;
|
||||
uint8_t repeaterRecords = 0;
|
||||
uint8_t repeaterRecordSize = 0;
|
||||
uint8_t roomRecords = 0;
|
||||
uint16_t roomRecordSize = 0;
|
||||
uint16_t shadeRecordSize = 0;
|
||||
uint8_t shadeRecords = 0;
|
||||
uint16_t groupRecordSize = 0;
|
||||
uint8_t groupRecords = 0;
|
||||
uint16_t settingsRecordSize = 0;
|
||||
uint16_t netRecordSize = 0;
|
||||
uint16_t transRecordSize = 0;
|
||||
char serverId[10] = ""; // This must match the server id size in the ConfigSettings.
|
||||
int8_t length = 0;
|
||||
};
|
||||
class ConfigFile {
|
||||
protected:
|
||||
File file;
|
||||
bool readOnly = false;
|
||||
bool begin(const char *filename, bool readOnly = false);
|
||||
uint32_t startRecPos = 0;
|
||||
bool _opened = false;
|
||||
public:
|
||||
config_header_t header;
|
||||
void end();
|
||||
bool isOpen();
|
||||
bool seekRecordByIndex(uint16_t ndx);
|
||||
bool readHeader();
|
||||
bool seekChar(const char val);
|
||||
bool writeHeader(const config_header_t &header);
|
||||
bool writeHeader();
|
||||
bool writeSeparator();
|
||||
bool writeRecordEnd();
|
||||
bool writeChar(const char val);
|
||||
bool writeInt8(const int8_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeUInt8(const uint8_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeUInt16(const uint16_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeUInt32(const uint32_t val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeBool(const bool val, const char tok = CFG_VALUE_SEP);
|
||||
bool writeFloat(const float val, const uint8_t prec, const char tok = CFG_VALUE_SEP);
|
||||
bool readString(char *buff, size_t len);
|
||||
bool readVarString(char *buff, size_t len);
|
||||
bool skipValue(size_t len);
|
||||
bool writeString(const char *val, size_t len, const char tok = CFG_VALUE_SEP);
|
||||
bool writeVarString(const char *val, const char tok = CFG_VALUE_SEP);
|
||||
char readChar(const char defVal = '\0');
|
||||
int8_t readInt8(const int8_t defVal = 0);
|
||||
uint8_t readUInt8(const uint8_t defVal = 0);
|
||||
uint16_t readUInt16(const uint16_t defVal = 0);
|
||||
uint32_t readUInt32(const uint32_t defVal = 0);
|
||||
bool readBool(const bool defVal = false);
|
||||
float readFloat(const float defVal = 0.00);
|
||||
};
|
||||
class ShadeConfigFile : public ConfigFile {
|
||||
protected:
|
||||
bool writeRepeaterRecord(SomfyShadeController *s);
|
||||
bool writeRoomRecord(SomfyRoom *room);
|
||||
bool writeShadeRecord(SomfyShade *shade);
|
||||
bool writeGroupRecord(SomfyGroup *group);
|
||||
bool writeSettingsRecord();
|
||||
bool writeNetRecord();
|
||||
bool writeTransRecord(transceiver_config_t &cfg);
|
||||
bool readRepeaterRecord(SomfyShadeController *s);
|
||||
bool readRoomRecord(SomfyRoom *room);
|
||||
bool readShadeRecord(SomfyShade *shade);
|
||||
bool readGroupRecord(SomfyGroup *group);
|
||||
bool readSettingsRecord();
|
||||
bool readNetRecord(restore_options_t &opts);
|
||||
bool readTransRecord(transceiver_config_t &cfg);
|
||||
public:
|
||||
static bool exists();
|
||||
static bool load(SomfyShadeController *somfy, const char *filename = "/shades.cfg");
|
||||
static bool restore(SomfyShadeController *somfy, const char *filename, restore_options_t &opts);
|
||||
bool begin(const char *filename, bool readOnly = false);
|
||||
bool begin(bool readOnly = false);
|
||||
bool save(SomfyShadeController *somfy);
|
||||
bool backup(SomfyShadeController *somfy);
|
||||
bool loadFile(SomfyShadeController *somfy, const char *filename = "/shades.cfg");
|
||||
bool restoreFile(SomfyShadeController *somfy, const char *filename, restore_options_t &opts);
|
||||
void end();
|
||||
//bool seekRecordById(uint8_t id);
|
||||
bool validate();
|
||||
};
|
||||
#endif
|
||||
755
src/ConfigSettings.cpp
Normal file
755
src/ConfigSettings.cpp
Normal file
|
|
@ -0,0 +1,755 @@
|
|||
#include <Arduino.h>
|
||||
#include <LittleFS.h> // https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS
|
||||
#include <time.h>
|
||||
#include <WiFi.h>
|
||||
#include <Preferences.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "esp_chip_info.h"
|
||||
|
||||
Preferences pref;
|
||||
|
||||
void restore_options_t::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("shades")) this->shades = obj["shades"];
|
||||
if(obj.containsKey("settings")) this->settings = obj["settings"];
|
||||
if(obj.containsKey("network")) this->network = obj["network"];
|
||||
if(obj.containsKey("transceiver")) this->transceiver = obj["transceiver"];
|
||||
if(obj.containsKey("repeaters")) this->repeaters = obj["repeaters"];
|
||||
if(obj.containsKey("mqtt")) this->mqtt = obj["mqtt"];
|
||||
}
|
||||
int8_t appver_t::compare(appver_t &ver) {
|
||||
if(this->major == ver.major && this->minor == ver.minor && this->build == ver.build) return 0;
|
||||
if(this->major > ver.major) return 1;
|
||||
else if(this->major < ver.major) return -1;
|
||||
else {
|
||||
if(this->minor > ver.minor) return 1;
|
||||
else if(this->minor < ver.minor) return -1;
|
||||
else {
|
||||
if(this->build > ver.build) return 1;
|
||||
else if(this->build < ver.build) return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void appver_t::copy(appver_t &ver) {
|
||||
strcpy(this->name, ver.name);
|
||||
this->major = ver.major;
|
||||
this->minor = ver.minor;
|
||||
this->build = ver.build;
|
||||
strcpy(this->suffix, ver.suffix);
|
||||
}
|
||||
void appver_t::parse(const char *ver) {
|
||||
// Now lets parse this pig.
|
||||
memset(this, 0x00, sizeof(appver_t));
|
||||
strlcpy(this->name, ver, sizeof(this->name));
|
||||
char num[3];
|
||||
uint8_t i = 0;
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(ver);) {
|
||||
char ch = ver[i++];
|
||||
// Trim off all the prefix.
|
||||
if(ch == '.') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
if(ch != '.')
|
||||
num[j++] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
this->major = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(ver);) {
|
||||
char ch = ver[i++];
|
||||
if(ch != '.')
|
||||
num[j++] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
this->minor = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 3 && i < strlen(ver);) {
|
||||
char ch = ver[i++];
|
||||
if(!isdigit(ch)) break;
|
||||
if(ch != '.')
|
||||
num[j++] = ch;
|
||||
else
|
||||
break;
|
||||
}
|
||||
this->build = static_cast<uint8_t>(atoi(num) & 0xFF);
|
||||
if(strlen(ver) < i) strlcpy(this->suffix, &ver[i], sizeof(this->suffix));
|
||||
}
|
||||
bool appver_t::toJSON(JsonObject &obj) {
|
||||
obj["name"] = this->name;
|
||||
obj["major"] = this->major;
|
||||
obj["minor"] = this->minor;
|
||||
obj["build"] = this->build;
|
||||
obj["suffix"] = this->suffix;
|
||||
return true;
|
||||
}
|
||||
void appver_t::toJSON(JsonResponse &json) {
|
||||
json.addElem("name", this->name);
|
||||
json.addElem("major", this->major);
|
||||
json.addElem("minor", this->minor);
|
||||
json.addElem("build", this->build);
|
||||
json.addElem("suffix", this->suffix);
|
||||
}
|
||||
void appver_t::toJSON(JsonSockEvent *json) {
|
||||
json->addElem("name", this->name);
|
||||
json->addElem("major", this->major);
|
||||
json->addElem("minor", this->minor);
|
||||
json->addElem("build", this->build);
|
||||
json->addElem("suffix", this->suffix);
|
||||
}
|
||||
|
||||
bool BaseSettings::load() { return true; }
|
||||
bool BaseSettings::loadFile(const char *filename) {
|
||||
size_t filesize = 10;
|
||||
String data = "";
|
||||
if(LittleFS.exists(filename)) {
|
||||
File file = LittleFS.open(filename, "r");
|
||||
filesize += file.size();
|
||||
while(file.available()) {
|
||||
char c = file.read();
|
||||
data += c;
|
||||
}
|
||||
DynamicJsonDocument doc(filesize);
|
||||
deserializeJson(doc, data);
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
this->fromJSON(obj);
|
||||
file.close();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool BaseSettings::saveFile(const char *filename) {
|
||||
File file = LittleFS.open(filename, "w");
|
||||
DynamicJsonDocument doc(2048);
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
this->toJSON(obj);
|
||||
serializeJson(doc, file);
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
bool BaseSettings::parseValueString(JsonObject &obj, const char *prop, char *pdest, size_t size) {
|
||||
if(obj.containsKey(prop)) strlcpy(pdest, obj[prop], size);
|
||||
return true;
|
||||
}
|
||||
bool BaseSettings::parseIPAddress(JsonObject &obj, const char *prop, IPAddress *pdest) {
|
||||
if(obj.containsKey(prop)) {
|
||||
char buff[16];
|
||||
strlcpy(buff, obj[prop], sizeof(buff));
|
||||
pdest->fromString(buff);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
int BaseSettings::parseValueInt(JsonObject &obj, const char *prop, int defVal) {
|
||||
if(obj.containsKey(prop)) return obj[prop];
|
||||
return defVal;
|
||||
}
|
||||
double BaseSettings::parseValueDouble(JsonObject &obj, const char *prop, double defVal) {
|
||||
if(obj.containsKey(prop)) return obj[prop];
|
||||
return defVal;
|
||||
}
|
||||
bool ConfigSettings::begin() {
|
||||
uint32_t chipId = 0;
|
||||
esp_chip_info_t ci;
|
||||
esp_chip_info(&ci);
|
||||
switch(ci.model) {
|
||||
case esp_chip_model_t::CHIP_ESP32:
|
||||
strcpy(this->chipModel, "");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32S3:
|
||||
strcpy(this->chipModel, "s3");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32S2:
|
||||
strcpy(this->chipModel, "s2");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32C3:
|
||||
strcpy(this->chipModel, "c3");
|
||||
break;
|
||||
// case esp_chip_model_t::CHIP_ESP32C2:
|
||||
// strcpy(this->chipModel, "c2");
|
||||
// break;
|
||||
// case esp_chip_model_t::CHIP_ESP32C6:
|
||||
// strcpy(this->chipModel, "c6");
|
||||
// break;
|
||||
case esp_chip_model_t::CHIP_ESP32H2:
|
||||
strcpy(this->chipModel, "h2");
|
||||
break;
|
||||
default:
|
||||
sprintf(this->chipModel, "UNK%d", static_cast<int>(ci.model));
|
||||
break;
|
||||
}
|
||||
Serial.printf("Chip Model ESP32-%s\n", this->chipModel);
|
||||
this->fwVersion.parse(FW_VERSION);
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
for(int i=0; i<17; i=i+8) {
|
||||
chipId |= ((mac >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
snprintf_P(this->serverId, sizeof(this->serverId), "%02X%02X%02X",
|
||||
(uint16_t)((chipId >> 16) & 0xff),
|
||||
(uint16_t)((chipId >> 8) & 0xff),
|
||||
(uint16_t)chipId & 0xff);
|
||||
this->load();
|
||||
this->Security.begin();
|
||||
this->IP.begin();
|
||||
this->WIFI.begin();
|
||||
this->Ethernet.begin();
|
||||
this->NTP.begin();
|
||||
this->MQTT.begin();
|
||||
this->print();
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::load() {
|
||||
this->fwVersion.parse(FW_VERSION);
|
||||
this->getAppVersion();
|
||||
pref.begin("CFG");
|
||||
pref.getString("hostname", this->hostname, sizeof(this->hostname));
|
||||
this->ssdpBroadcast = pref.getBool("ssdpBroadcast", true);
|
||||
this->checkForUpdate = pref.getBool("checkForUpdate", true);
|
||||
this->connType = static_cast<conn_types_t>(pref.getChar("connType", 0x00));
|
||||
//Serial.printf("Preference GFG Free Entries: %d\n", pref.freeEntries());
|
||||
pref.end();
|
||||
if(this->connType == conn_types_t::unset) {
|
||||
// We are doing this to convert the data from previous versions.
|
||||
this->connType = conn_types_t::wifi;
|
||||
pref.begin("WIFI");
|
||||
pref.getString("hostname", this->hostname, sizeof(this->hostname));
|
||||
this->ssdpBroadcast = pref.getBool("ssdpBroadcast", true);
|
||||
pref.remove("hostname");
|
||||
pref.remove("ssdpBroadcast");
|
||||
pref.end();
|
||||
this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::getAppVersion() {
|
||||
char app[15];
|
||||
if(!LittleFS.exists("/appversion")) return false;
|
||||
File f = LittleFS.open("/appversion", "r");
|
||||
memset(app, 0x00, sizeof(app));
|
||||
f.read((uint8_t *)app, sizeof(app) - 1);
|
||||
f.close();
|
||||
_trim(app);
|
||||
this->appVersion.parse(app);
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::save() {
|
||||
pref.begin("CFG");
|
||||
pref.putString("hostname", this->hostname);
|
||||
pref.putBool("ssdpBroadcast", this->ssdpBroadcast);
|
||||
pref.putChar("connType", static_cast<uint8_t>(this->connType));
|
||||
pref.putBool("checkForUpdate", this->checkForUpdate);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::toJSON(JsonObject &obj) {
|
||||
obj["ssdpBroadcast"] = this->ssdpBroadcast;
|
||||
obj["hostname"] = this->hostname;
|
||||
obj["connType"] = static_cast<uint8_t>(this->connType);
|
||||
obj["chipModel"] = this->chipModel;
|
||||
obj["checkForUpdate"] = this->checkForUpdate;
|
||||
return true;
|
||||
}
|
||||
void ConfigSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ssdpBroadcast", this->ssdpBroadcast);
|
||||
json.addElem("hostname", this->hostname);
|
||||
json.addElem("connType", static_cast<uint8_t>(this->connType));
|
||||
json.addElem("chipModel", this->chipModel);
|
||||
json.addElem("checkForUpdate", this->checkForUpdate);
|
||||
}
|
||||
|
||||
bool ConfigSettings::requiresAuth() { return this->Security.type != security_types::None; }
|
||||
bool ConfigSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("ssdpBroadcast")) this->ssdpBroadcast = obj["ssdpBroadcast"];
|
||||
if(obj.containsKey("hostname")) this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
if(obj.containsKey("connType")) this->connType = static_cast<conn_types_t>(obj["connType"].as<uint8_t>());
|
||||
if(obj.containsKey("checkForUpdate")) this->checkForUpdate = obj["checkForUpdate"];
|
||||
return true;
|
||||
}
|
||||
void ConfigSettings::print() {
|
||||
this->Security.print();
|
||||
Serial.printf("Connection Type: %u\n", (unsigned int) this->connType);
|
||||
this->NTP.print();
|
||||
if(this->connType == conn_types_t::wifi || this->connType == conn_types_t::unset) this->WIFI.print();
|
||||
if(this->connType == conn_types_t::ethernet || this->connType == conn_types_t::ethernetpref) this->Ethernet.print();
|
||||
}
|
||||
void ConfigSettings::emitSockets() {}
|
||||
void ConfigSettings::emitSockets(uint8_t num) {}
|
||||
uint16_t ConfigSettings::calcSettingsRecSize() {
|
||||
return strlen(this->fwVersion.name) + 3
|
||||
+ strlen(this->hostname) + 3
|
||||
+ strlen(this->NTP.ntpServer) + 3
|
||||
+ strlen(this->NTP.posixZone) + 3
|
||||
+ 6 // ssdpbroadcast
|
||||
+ 6; // updateCheck
|
||||
}
|
||||
uint16_t ConfigSettings::calcNetRecSize() {
|
||||
return 4 // connType
|
||||
+ 6 // dhcp
|
||||
+ this->IP.ip.toString().length() + 3
|
||||
+ this->IP.gateway.toString().length() + 3
|
||||
+ this->IP.subnet.toString().length() + 3
|
||||
+ this->IP.dns1.toString().length() + 3
|
||||
+ this->IP.dns2.toString().length() + 3
|
||||
+ strlen(this->MQTT.protocol) + 3
|
||||
+ strlen(this->MQTT.hostname) + 3
|
||||
+ 6 // MQTT Port
|
||||
+ 6 // PubDisco
|
||||
+ strlen(this->MQTT.rootTopic) + 3
|
||||
+ strlen(this->MQTT.discoTopic) + 3
|
||||
+ 4 // ETH.boardType
|
||||
+ 4 // ETH.phyType
|
||||
+ 4 // ETH.clkMode
|
||||
+ 5 // ETH.phyAddress
|
||||
+ 5 // ETH.PWRPin
|
||||
+ 5 // ETH.MDCPin
|
||||
+ 5; // ETH.MDIOPin
|
||||
}
|
||||
bool MQTTSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
void MQTTSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("enabled", this->enabled);
|
||||
json.addElem("pubDisco", this->pubDisco);
|
||||
json.addElem("protocol", this->protocol);
|
||||
json.addElem("hostname", this->hostname);
|
||||
json.addElem("port", (uint32_t)this->port);
|
||||
json.addElem("username", this->username);
|
||||
json.addElem("password", this->password);
|
||||
json.addElem("rootTopic", this->rootTopic);
|
||||
json.addElem("discoTopic", this->discoTopic);
|
||||
}
|
||||
|
||||
bool MQTTSettings::toJSON(JsonObject &obj) {
|
||||
obj["enabled"] = this->enabled;
|
||||
obj["pubDisco"] = this->pubDisco;
|
||||
obj["protocol"] = this->protocol;
|
||||
obj["hostname"] = this->hostname;
|
||||
obj["port"] = this->port;
|
||||
obj["username"] = this->username;
|
||||
obj["password"] = this->password;
|
||||
obj["rootTopic"] = this->rootTopic;
|
||||
obj["discoTopic"] = this->discoTopic;
|
||||
return true;
|
||||
}
|
||||
bool MQTTSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("enabled")) this->enabled = obj["enabled"];
|
||||
if(obj.containsKey("pubDisco")) this->pubDisco = obj["pubDisco"];
|
||||
this->parseValueString(obj, "protocol", this->protocol, sizeof(this->protocol));
|
||||
this->parseValueString(obj, "hostname", this->hostname, sizeof(this->hostname));
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
this->parseValueString(obj, "rootTopic", this->rootTopic, sizeof(this->rootTopic));
|
||||
this->parseValueString(obj, "discoTopic", this->discoTopic, sizeof(this->discoTopic));
|
||||
if(obj.containsKey("port")) this->port = obj["port"];
|
||||
return true;
|
||||
}
|
||||
bool MQTTSettings::save() {
|
||||
pref.begin("MQTT");
|
||||
pref.clear();
|
||||
pref.putString("protocol", this->protocol);
|
||||
pref.putString("hostname", this->hostname);
|
||||
pref.putShort("port", this->port);
|
||||
pref.putString("username", this->username);
|
||||
pref.putString("password", this->password);
|
||||
pref.putString("rootTopic", this->rootTopic);
|
||||
pref.putBool("enabled", this->enabled);
|
||||
pref.putBool("pubDisco", this->pubDisco);
|
||||
pref.putString("discoTopic", this->discoTopic);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool MQTTSettings::load() {
|
||||
pref.begin("MQTT");
|
||||
pref.getString("protocol", this->protocol, sizeof(this->protocol));
|
||||
pref.getString("hostname", this->hostname, sizeof(this->hostname));
|
||||
this->port = pref.getShort("port", 1883);
|
||||
pref.getString("username", this->username, sizeof(this->username));
|
||||
pref.getString("password", this->password, sizeof(this->password));
|
||||
pref.getString("rootTopic", this->rootTopic, sizeof(this->rootTopic));
|
||||
this->enabled = pref.getBool("enabled", false);
|
||||
this->pubDisco = pref.getBool("pubDisco", false);
|
||||
pref.getString("discoTopic", this->discoTopic, sizeof(this->discoTopic));
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool ConfigSettings::toJSON(DynamicJsonDocument &doc) {
|
||||
doc["fwVersion"] = this->fwVersion.name;
|
||||
JsonObject objWIFI = doc.createNestedObject("WIFI");
|
||||
this->WIFI.toJSON(objWIFI);
|
||||
JsonObject objNTP = doc.createNestedObject("NTP");
|
||||
this->NTP.toJSON(objNTP);
|
||||
JsonObject objMQTT = doc.createNestedObject("MQTT");
|
||||
this->MQTT.toJSON(objMQTT);
|
||||
return true;
|
||||
}
|
||||
bool NTPSettings::begin() {
|
||||
this->load();
|
||||
this->apply();
|
||||
return true;
|
||||
}
|
||||
bool NTPSettings::save() {
|
||||
pref.begin("NTP");
|
||||
pref.clear();
|
||||
pref.putString("ntpServer", this->ntpServer);
|
||||
pref.putString("posixZone", this->posixZone);
|
||||
pref.end();
|
||||
struct tm dt;
|
||||
configTime(0, 0, this->ntpServer);
|
||||
if(!getLocalTime(&dt)) return false;
|
||||
setenv("TZ", this->posixZone, 1);
|
||||
return true;
|
||||
}
|
||||
bool NTPSettings::load() {
|
||||
pref.begin("NTP");
|
||||
pref.getString("ntpServer", this->ntpServer, sizeof(this->ntpServer));
|
||||
pref.getString("posixZone", this->posixZone, sizeof(this->posixZone));
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
void NTPSettings::print() {
|
||||
Serial.println("NTP Settings ");
|
||||
Serial.print(this->ntpServer);
|
||||
Serial.print(" TZ:");
|
||||
Serial.println(this->posixZone);
|
||||
}
|
||||
bool NTPSettings::fromJSON(JsonObject &obj) {
|
||||
this->parseValueString(obj, "ntpServer", this->ntpServer, sizeof(this->ntpServer));
|
||||
this->parseValueString(obj, "posixZone", this->posixZone, sizeof(this->posixZone));
|
||||
return true;
|
||||
}
|
||||
void NTPSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ntpServer", this->ntpServer);
|
||||
json.addElem("posixZone", this->posixZone);
|
||||
}
|
||||
|
||||
bool NTPSettings::toJSON(JsonObject &obj) {
|
||||
obj["ntpServer"] = this->ntpServer;
|
||||
obj["posixZone"] = this->posixZone;
|
||||
return true;
|
||||
}
|
||||
bool NTPSettings::apply() {
|
||||
struct tm dt;
|
||||
configTime(0, 0, this->ntpServer);
|
||||
if(!getLocalTime(&dt)) return false;
|
||||
setenv("TZ", this->posixZone, 1);
|
||||
return true;
|
||||
}
|
||||
IPSettings::IPSettings() {}
|
||||
bool IPSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
bool IPSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("dhcp")) this->dhcp = obj["dhcp"];
|
||||
this->parseIPAddress(obj, "ip", &this->ip);
|
||||
this->parseIPAddress(obj, "gateway", &this->gateway);
|
||||
this->parseIPAddress(obj, "subnet", &this->subnet);
|
||||
this->parseIPAddress(obj, "dns1", &this->dns1);
|
||||
this->parseIPAddress(obj, "dns2", &this->dns2);
|
||||
return true;
|
||||
}
|
||||
bool IPSettings::toJSON(JsonObject &obj) {
|
||||
IPAddress ipEmpty(0,0,0,0);
|
||||
obj["dhcp"] = this->dhcp;
|
||||
obj["ip"] = this->ip == ipEmpty ? "" : this->ip.toString();
|
||||
obj["gateway"] = this->gateway == ipEmpty ? "" : this->gateway.toString();
|
||||
obj["subnet"] = this->subnet == ipEmpty ? "" : this->subnet.toString();
|
||||
obj["dns1"] = this->dns1 == ipEmpty ? "" : this->dns1.toString();
|
||||
obj["dns2"] = this->dns2 == ipEmpty ? "" : this->dns2.toString();
|
||||
return true;
|
||||
}
|
||||
void IPSettings::toJSON(JsonResponse &json) {
|
||||
IPAddress ipEmpty(0,0,0,0);
|
||||
json.addElem("dhcp", this->dhcp);
|
||||
json.addElem("ip", this->ip.toString().c_str());
|
||||
json.addElem("gateway", this->gateway.toString().c_str());
|
||||
json.addElem("subnet", this->subnet.toString().c_str());
|
||||
json.addElem("dns1", this->dns1.toString().c_str());
|
||||
json.addElem("dns2", this->dns2.toString().c_str());
|
||||
}
|
||||
|
||||
bool IPSettings::save() {
|
||||
pref.begin("IP");
|
||||
pref.clear();
|
||||
pref.putBool("dhcp", this->dhcp);
|
||||
pref.putString("ip", this->ip.toString());
|
||||
pref.putString("gateway", this->gateway.toString());
|
||||
pref.putString("subnet", this->subnet.toString());
|
||||
pref.putString("dns1", this->dns1.toString());
|
||||
pref.putString("dns2", this->dns2.toString());
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool IPSettings::load() {
|
||||
pref.begin("IP");
|
||||
this->dhcp = pref.getBool("dhcp", true);
|
||||
char buff[16];
|
||||
if(pref.isKey("ip")) {
|
||||
pref.getString("ip", buff, sizeof(buff));
|
||||
this->ip.fromString(buff);
|
||||
}
|
||||
if(pref.isKey("gateway")) {
|
||||
pref.getString("gateway", buff, sizeof(buff));
|
||||
this->gateway.fromString(buff);
|
||||
}
|
||||
if(pref.isKey("subnet")) {
|
||||
pref.getString("subnet", buff, sizeof(buff));
|
||||
this->subnet.fromString(buff);
|
||||
}
|
||||
if(pref.isKey("dns1")) {
|
||||
pref.getString("dns1", buff, sizeof(buff));
|
||||
this->dns1.fromString(buff);
|
||||
}
|
||||
if(pref.isKey("dns2")) {
|
||||
pref.getString("dns2", buff, sizeof(buff));
|
||||
this->dns2.fromString(buff);
|
||||
}
|
||||
Serial.printf("Preference IP Free Entries: %d\n", pref.freeEntries());
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("type")) this->type = static_cast<security_types>(obj["type"].as<uint8_t>());
|
||||
this->parseValueString(obj, "username", this->username, sizeof(this->username));
|
||||
this->parseValueString(obj, "password", this->password, sizeof(this->password));
|
||||
this->parseValueString(obj, "pin", this->pin, sizeof(this->pin));
|
||||
if(obj.containsKey("permissions")) this->permissions = obj["permissions"];
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::toJSON(JsonObject &obj) {
|
||||
obj["type"] = static_cast<uint8_t>(this->type);
|
||||
obj["username"] = this->username;
|
||||
obj["password"] = this->password;
|
||||
obj["pin"] = this->pin;
|
||||
obj["permissions"] = this->permissions;
|
||||
return true;
|
||||
}
|
||||
void SecuritySettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("type", static_cast<uint8_t>(this->type));
|
||||
json.addElem("username", this->username);
|
||||
json.addElem("password", this->password);
|
||||
json.addElem("pin", this->pin);
|
||||
json.addElem("permissions", this->permissions);
|
||||
}
|
||||
|
||||
bool SecuritySettings::save() {
|
||||
pref.begin("SEC");
|
||||
pref.clear();
|
||||
pref.putChar("type", static_cast<uint8_t>(this->type));
|
||||
pref.putString("username", this->username);
|
||||
pref.putString("password", this->password);
|
||||
pref.putString("pin", this->pin);
|
||||
pref.putChar("permissions", this->permissions);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool SecuritySettings::load() {
|
||||
pref.begin("SEC");
|
||||
this->type = static_cast<security_types>(pref.getChar("type", 0));
|
||||
if(pref.isKey("username")) pref.getString("username", this->username, sizeof(this->username));
|
||||
if(pref.isKey("password")) pref.getString("password", this->password, sizeof(this->password));
|
||||
if(pref.isKey("pin")) pref.getString("pin", this->pin, sizeof(this->pin));
|
||||
if(pref.isKey("permissions")) this->permissions = pref.getChar("permissions", this->permissions);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
void SecuritySettings::print() {
|
||||
Serial.print("SECURITY Type:");
|
||||
Serial.print(static_cast<uint8_t>(this->type));
|
||||
Serial.print(" Username:[");
|
||||
Serial.print(this->username);
|
||||
Serial.print("] Password:[");
|
||||
Serial.print(this->password);
|
||||
Serial.print("] Pin:[");
|
||||
Serial.print(this->pin);
|
||||
Serial.print("] Permissions:");
|
||||
Serial.println(this->permissions);
|
||||
}
|
||||
|
||||
WifiSettings::WifiSettings() {}
|
||||
bool WifiSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
bool WifiSettings::fromJSON(JsonObject &obj) {
|
||||
this->parseValueString(obj, "ssid", this->ssid, sizeof(this->ssid));
|
||||
this->parseValueString(obj, "passphrase", this->passphrase, sizeof(this->passphrase));
|
||||
if(obj.containsKey("roaming")) this->roaming = obj["roaming"];
|
||||
if(obj.containsKey("hidden")) this->hidden = obj["hidden"];
|
||||
return true;
|
||||
}
|
||||
bool WifiSettings::toJSON(JsonObject &obj) {
|
||||
obj["ssid"] = this->ssid;
|
||||
obj["passphrase"] = this->passphrase;
|
||||
obj["roaming"] = this->roaming;
|
||||
obj["hidden"] = this->hidden;
|
||||
return true;
|
||||
}
|
||||
void WifiSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("ssid", this->ssid);
|
||||
json.addElem("passphrase", this->passphrase);
|
||||
json.addElem("roaming", this->roaming);
|
||||
json.addElem("hidden", this->hidden);
|
||||
}
|
||||
|
||||
bool WifiSettings::save() {
|
||||
pref.begin("WIFI");
|
||||
pref.clear();
|
||||
pref.putString("ssid", this->ssid);
|
||||
pref.putString("passphrase", this->passphrase);
|
||||
pref.putBool("roaming", this->roaming);
|
||||
pref.putBool("hidden", this->hidden);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool WifiSettings::load() {
|
||||
pref.begin("WIFI");
|
||||
pref.getString("ssid", this->ssid, sizeof(this->ssid));
|
||||
pref.getString("passphrase", this->passphrase, sizeof(this->passphrase));
|
||||
this->ssid[sizeof(this->ssid) - 1] = '\0';
|
||||
this->passphrase[sizeof(this->passphrase) - 1] = '\0';
|
||||
this->roaming = pref.getBool("roaming", true);
|
||||
this->hidden = pref.getBool("hidden", false);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
String WifiSettings::mapEncryptionType(int type) {
|
||||
switch(type) {
|
||||
case WIFI_AUTH_OPEN:
|
||||
return "Open";
|
||||
case WIFI_AUTH_WEP:
|
||||
return "WEP";
|
||||
case WIFI_AUTH_WPA_PSK:
|
||||
return "WPA/PSK";
|
||||
case WIFI_AUTH_WPA2_PSK:
|
||||
return "WPA2/PSK";
|
||||
case WIFI_AUTH_WPA_WPA2_PSK:
|
||||
return "WPA/WPA2/PSK";
|
||||
case WIFI_AUTH_WPA2_ENTERPRISE:
|
||||
return "WPA/Enterprise";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
void WifiSettings::print() {
|
||||
Serial.println("WIFI Settings");
|
||||
Serial.print(" SSID: [");
|
||||
Serial.print(this->ssid);
|
||||
Serial.print("] PassPhrase: [");
|
||||
Serial.print(this->passphrase);
|
||||
Serial.println("]");
|
||||
}
|
||||
void WifiSettings::printNetworks() {
|
||||
int n = WiFi.scanNetworks(false, false);
|
||||
Serial.print("Scanned ");
|
||||
Serial.print(n);
|
||||
Serial.println(" Networks...");
|
||||
String network;
|
||||
for(int i = 0; i < n; i++) {
|
||||
if(WiFi.SSID(i).compareTo(this->ssid) == 0) Serial.print("*");
|
||||
else Serial.print(" ");
|
||||
Serial.print(i);
|
||||
Serial.print(": ");
|
||||
Serial.print(WiFi.SSID(i));
|
||||
Serial.print(" (");
|
||||
Serial.print(WiFi.RSSI(i));
|
||||
Serial.print("dBm) CH:");
|
||||
Serial.print(WiFi.channel(i));
|
||||
Serial.print(" MAC:");
|
||||
Serial.print(WiFi.BSSIDstr(i));
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
}
|
||||
bool WifiSettings::ssidExists(const char *ssid) {
|
||||
int n = WiFi.scanNetworks(false, true);
|
||||
for(int i = 0; i < n; i++) {
|
||||
if(WiFi.SSID(i).compareTo(ssid) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
EthernetSettings::EthernetSettings() {}
|
||||
bool EthernetSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
}
|
||||
bool EthernetSettings::fromJSON(JsonObject &obj) {
|
||||
if(obj.containsKey("boardType")) this->boardType = obj["boardType"];
|
||||
if(obj.containsKey("phyAddress")) this->phyAddress = obj["phyAddress"];
|
||||
if(obj.containsKey("CLKMode")) this->CLKMode = static_cast<eth_clock_mode_t>(obj["CLKMode"]);
|
||||
if(obj.containsKey("phyType")) this->phyType = static_cast<eth_phy_type_t>(obj["phyType"]);
|
||||
if(obj.containsKey("PWRPin")) this->PWRPin = obj["PWRPin"];
|
||||
if(obj.containsKey("MDCPin")) this->MDCPin = obj["MDCPin"];
|
||||
if(obj.containsKey("MDIOPin")) this->MDIOPin = obj["MDIOPin"];
|
||||
return true;
|
||||
}
|
||||
bool EthernetSettings::toJSON(JsonObject &obj) {
|
||||
obj["boardType"] = this->boardType;
|
||||
obj["phyAddress"] = this->phyAddress;
|
||||
obj["CLKMode"] = static_cast<uint8_t>(this->CLKMode);
|
||||
obj["phyType"] = static_cast<uint8_t>(this->phyType);
|
||||
obj["PWRPin"] = this->PWRPin;
|
||||
obj["MDCPin"] = this->MDCPin;
|
||||
obj["MDIOPin"] = this->MDIOPin;
|
||||
return true;
|
||||
}
|
||||
void EthernetSettings::toJSON(JsonResponse &json) {
|
||||
json.addElem("boardType", this->boardType);
|
||||
json.addElem("phyAddress", this->phyAddress);
|
||||
json.addElem("CLKMode", static_cast<uint8_t>(this->CLKMode));
|
||||
json.addElem("phyType", static_cast<uint8_t>(this->phyType));
|
||||
json.addElem("PWRPin", this->PWRPin);
|
||||
json.addElem("MDCPin", this->MDCPin);
|
||||
json.addElem("MDIOPin", this->MDIOPin);
|
||||
}
|
||||
|
||||
bool EthernetSettings::usesPin(uint8_t pin) {
|
||||
if((this->CLKMode == 0 || this->CLKMode == 1) && pin == 0) return true;
|
||||
else if(this->CLKMode == 2 && pin == 16) return true;
|
||||
else if(this->CLKMode == 3 && pin == 17) return true;
|
||||
else if(this->PWRPin == pin) return true;
|
||||
else if(this->MDCPin == pin) return true;
|
||||
else if(this->MDIOPin == pin) return true;
|
||||
return false;
|
||||
}
|
||||
bool EthernetSettings::save() {
|
||||
pref.begin("ETH");
|
||||
pref.clear();
|
||||
pref.putChar("boardType", this->boardType);
|
||||
pref.putChar("phyAddress", this->phyAddress);
|
||||
pref.putChar("phyType", static_cast<uint8_t>(this->phyType));
|
||||
pref.putChar("CLKMode", static_cast<uint8_t>(this->CLKMode));
|
||||
pref.putChar("PWRPin", this->PWRPin);
|
||||
pref.putChar("MDCPin", this->MDCPin);
|
||||
pref.putChar("MDIOPin", this->MDIOPin);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
bool EthernetSettings::load() {
|
||||
pref.begin("ETH");
|
||||
this->boardType = pref.getChar("boardType", this->boardType);
|
||||
this->phyType = static_cast<eth_phy_type_t>(pref.getChar("phyType", ETH_PHY_LAN8720));
|
||||
this->CLKMode = static_cast<eth_clock_mode_t>(pref.getChar("CLKMode", ETH_CLOCK_GPIO0_IN));
|
||||
this->phyAddress = pref.getChar("phyAddress", this->phyAddress);
|
||||
this->PWRPin = pref.getChar("PWRPin", this->PWRPin);
|
||||
this->MDCPin = pref.getChar("MDCPin", this->MDCPin);
|
||||
this->MDIOPin = pref.getChar("MDIOPin", this->MDIOPin);
|
||||
pref.end();
|
||||
return true;
|
||||
}
|
||||
void EthernetSettings::print() {
|
||||
Serial.println("Ethernet Settings");
|
||||
Serial.printf("Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d\n", this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin);
|
||||
}
|
||||
void ConfigSettings::printAvailHeap() {
|
||||
Serial.print("Max Heap: ");
|
||||
Serial.println(ESP.getMaxAllocHeap());
|
||||
Serial.print("Free Heap: ");
|
||||
Serial.println(ESP.getFreeHeap());
|
||||
Serial.print("Min Heap: ");
|
||||
Serial.println(ESP.getMinFreeHeap());
|
||||
}
|
||||
202
src/ConfigSettings.h
Normal file
202
src/ConfigSettings.h
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
#include <ArduinoJson.h>
|
||||
#include <ETH.h>
|
||||
#ifndef configsettings_h
|
||||
#define configsettings_h
|
||||
#include "WResp.h"
|
||||
#define FW_VERSION "v2.4.7"
|
||||
enum class conn_types_t : byte {
|
||||
unset = 0x00,
|
||||
wifi = 0x01,
|
||||
ethernet = 0x02,
|
||||
ethernetpref = 0x03,
|
||||
ap = 0x04
|
||||
};
|
||||
|
||||
enum DeviceStatus {
|
||||
DS_OK = 0,
|
||||
DS_ERROR = 1,
|
||||
DS_FWUPDATE = 2
|
||||
};
|
||||
struct restore_options_t {
|
||||
bool settings = false;
|
||||
bool shades = false;
|
||||
bool network = false;
|
||||
bool transceiver = false;
|
||||
bool repeaters = false;
|
||||
bool mqtt = false;
|
||||
void fromJSON(JsonObject &obj);
|
||||
};
|
||||
struct appver_t {
|
||||
char name[15] = "";
|
||||
uint8_t major = 0;
|
||||
uint8_t minor = 0;
|
||||
uint8_t build = 0;
|
||||
char suffix[4] = "";
|
||||
void parse(const char *ver);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
void toJSON(JsonSockEvent *json);
|
||||
int8_t compare(appver_t &ver);
|
||||
void copy(appver_t &ver);
|
||||
};
|
||||
|
||||
|
||||
class BaseSettings {
|
||||
public:
|
||||
bool loadFile(const char* filename);
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool parseIPAddress(JsonObject &obj, const char *prop, IPAddress *);
|
||||
bool parseValueString(JsonObject &obj, const char *prop, char *dest, size_t size);
|
||||
int parseValueInt(JsonObject &obj, const char *prop, int defVal);
|
||||
double parseValueDouble(JsonObject &obj, const char *prop, double defVal);
|
||||
bool saveFile(const char* filename);
|
||||
bool save();
|
||||
bool load();
|
||||
};
|
||||
class NTPSettings: BaseSettings {
|
||||
public:
|
||||
char ntpServer[65] = "pool.ntp.org";
|
||||
char posixZone[64] = "";
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool apply();
|
||||
bool begin();
|
||||
bool save();
|
||||
bool load();
|
||||
void print();
|
||||
};
|
||||
class WifiSettings: BaseSettings {
|
||||
public:
|
||||
WifiSettings();
|
||||
bool roaming = true;
|
||||
bool hidden = false;
|
||||
char ssid[65] = "";
|
||||
char passphrase[65] = "";
|
||||
//bool ssdpBroadcast = true;
|
||||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
String mapEncryptionType(int type);
|
||||
bool ssidExists(const char *ssid);
|
||||
void printNetworks();
|
||||
bool save();
|
||||
bool load();
|
||||
void print();
|
||||
|
||||
};
|
||||
class EthernetSettings: BaseSettings {
|
||||
public:
|
||||
EthernetSettings();
|
||||
uint8_t boardType = 0; // These board types are enumerated in the ui and used to set the chip settings.
|
||||
eth_phy_type_t phyType = ETH_PHY_LAN8720;
|
||||
eth_clock_mode_t CLKMode = ETH_CLOCK_GPIO0_IN;
|
||||
int8_t phyAddress = ETH_PHY_ADDR;
|
||||
int8_t PWRPin = ETH_PHY_POWER;
|
||||
int8_t MDCPin = ETH_PHY_MDC;
|
||||
int8_t MDIOPin = ETH_PHY_MDIO;
|
||||
|
||||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool load();
|
||||
bool save();
|
||||
void print();
|
||||
bool usesPin(uint8_t pin);
|
||||
};
|
||||
class IPSettings: BaseSettings {
|
||||
public:
|
||||
IPSettings();
|
||||
bool dhcp = true;
|
||||
IPAddress ip;
|
||||
IPAddress subnet = IPAddress(255,255,255,0);
|
||||
IPAddress gateway = IPAddress(0,0,0,0);
|
||||
IPAddress dns1 = IPAddress(0,0,0,0);
|
||||
IPAddress dns2 = IPAddress(0,0,0,0);
|
||||
bool begin();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool load();
|
||||
bool save();
|
||||
void print();
|
||||
};
|
||||
enum class security_types : byte {
|
||||
None = 0x00,
|
||||
PinEntry = 0x01,
|
||||
Password = 0x02
|
||||
};
|
||||
enum class security_permissions : byte {
|
||||
ConfigOnly = 0x01
|
||||
};
|
||||
class SecuritySettings: BaseSettings {
|
||||
public:
|
||||
security_types type = security_types::None;
|
||||
char username[33] = "";
|
||||
char password[33] = "";
|
||||
char pin[5] = "";
|
||||
uint8_t permissions = 0;
|
||||
bool begin();
|
||||
bool save();
|
||||
bool load();
|
||||
void print();
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool fromJSON(JsonObject &obj);
|
||||
};
|
||||
class MQTTSettings: BaseSettings {
|
||||
public:
|
||||
bool enabled = false;
|
||||
bool pubDisco = false;
|
||||
char hostname[65] = "ESPSomfyRTS";
|
||||
char protocol[10] = "mqtt://";
|
||||
uint16_t port = 1883;
|
||||
char username[33] = "";
|
||||
char password[33] = "";
|
||||
char rootTopic[65] = "";
|
||||
char discoTopic[65] = "homeassistant";
|
||||
bool begin();
|
||||
bool save();
|
||||
bool load();
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool fromJSON(JsonObject &obj);
|
||||
};
|
||||
class ConfigSettings: BaseSettings {
|
||||
public:
|
||||
static void printAvailHeap();
|
||||
char serverId[10] = "";
|
||||
char hostname[32] = "ESPSomfyRTS";
|
||||
char chipModel[10] = "ESP32";
|
||||
conn_types_t connType = conn_types_t::unset;
|
||||
appver_t fwVersion;
|
||||
appver_t appVersion;
|
||||
bool ssdpBroadcast = true;
|
||||
bool checkForUpdate = true;
|
||||
uint8_t status;
|
||||
IPSettings IP;
|
||||
WifiSettings WIFI;
|
||||
EthernetSettings Ethernet;
|
||||
NTPSettings NTP;
|
||||
MQTTSettings MQTT;
|
||||
SecuritySettings Security;
|
||||
bool requiresAuth();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
bool begin();
|
||||
bool save();
|
||||
bool load();
|
||||
void print();
|
||||
void emitSockets();
|
||||
void emitSockets(uint8_t num);
|
||||
bool toJSON(DynamicJsonDocument &doc);
|
||||
uint16_t calcSettingsRecSize();
|
||||
uint16_t calcNetRecSize();
|
||||
bool getAppVersion();
|
||||
};
|
||||
#endif
|
||||
588
src/GitOTA.cpp
Normal file
588
src/GitOTA.cpp
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
#include <WiFi.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <Update.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "GitOTA.h"
|
||||
#include "Utils.h"
|
||||
#include "Sockets.h"
|
||||
#include "Somfy.h"
|
||||
#include "Web.h"
|
||||
#include "WResp.h"
|
||||
#include "Network.h"
|
||||
|
||||
|
||||
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern SomfyShadeController somfy;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
extern Web webServer;
|
||||
extern Network net;
|
||||
|
||||
|
||||
|
||||
#define MAX_BUFF_SIZE 4096
|
||||
void GitRelease::setReleaseProperty(const char *key, const char *val) {
|
||||
if(strcmp(key, "id") == 0) this->id = atol(val);
|
||||
else if(strcmp(key, "draft") == 0) this->draft = toBoolean(val, false);
|
||||
else if(strcmp(key, "prerelease") == 0) this->preRelease = toBoolean(val, false);
|
||||
else if(strcmp(key, "name") == 0) strlcpy(this->name, val, sizeof(this->name));
|
||||
else if(strcmp(key, "tag_name") == 0) {
|
||||
this->version.parse(val);
|
||||
}
|
||||
else if(strcmp(key, "published_at") == 0) {
|
||||
//Serial.printf("Key:[%s] Value:[%s]\n", key, val);
|
||||
this->releaseDate = Timestamp::parseUTCTime(val);
|
||||
}
|
||||
}
|
||||
void GitRelease::setAssetProperty(const char *key, const char *val) {
|
||||
if(strcmp(key, "name") == 0) {
|
||||
//Serial.println(val);
|
||||
if(strstr(val, "littlefs.bin")) this->hasFS = true;
|
||||
else if(strstr(val, "ino.esp32.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "32");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32s3.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "s3");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32s2.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "s2");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32c3.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "c3");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32c2.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "c2");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32c6.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "c6");
|
||||
}
|
||||
else if(strstr(val, "ino.esp32h2.bin")) {
|
||||
if(strlen(this->hwVersions)) strcat(this->hwVersions, ",");
|
||||
strcat(this->hwVersions, "h2");
|
||||
}
|
||||
}
|
||||
}
|
||||
void GitRelease::toJSON(JsonResponse &json) {
|
||||
Timestamp ts;
|
||||
char buff[20];
|
||||
sprintf(buff, "%llu", this->id);
|
||||
json.addElem("id", buff);
|
||||
json.addElem("name", this->name);
|
||||
json.addElem("date", ts.getISOTime(this->releaseDate));
|
||||
json.addElem("draft", this->draft);
|
||||
json.addElem("preRelease", this->preRelease);
|
||||
json.addElem("main", this->main);
|
||||
json.addElem("hasFS", this->hasFS);
|
||||
json.addElem("hwVersions", this->hwVersions);
|
||||
json.beginObject("version");
|
||||
this->version.toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
#define ERR_CLIENT_OFFSET -50
|
||||
|
||||
int16_t GitRepo::getReleases(uint8_t num) {
|
||||
WiFiClientSecure sclient;
|
||||
sclient.setInsecure();
|
||||
sclient.setHandshakeTimeout(3);
|
||||
uint8_t ndx = 0;
|
||||
uint8_t count = min((uint8_t)GIT_MAX_RELEASES, num);
|
||||
char url[128];
|
||||
memset(this->releases, 0x00, sizeof(GitRelease) * GIT_MAX_RELEASES);
|
||||
sprintf(url, "https://api.github.com/repos/rstrouse/espsomfy-rts/releases?per_page=%d&page=1", count);
|
||||
GitRelease *main = &this->releases[GIT_MAX_RELEASES];
|
||||
main->releaseDate = Timestamp::now();
|
||||
main->id = 1;
|
||||
main->main = true;
|
||||
strcpy(main->version.name, "main");
|
||||
strcpy(main->name, "Main");
|
||||
strcpy(main->hwVersions, "32,s3");
|
||||
HTTPClient https;
|
||||
https.setReuse(false);
|
||||
if(https.begin(sclient, url)) {
|
||||
esp_task_wdt_reset();
|
||||
int httpCode = https.GET();
|
||||
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
|
||||
if(httpCode > 0) {
|
||||
int len = https.getSize();
|
||||
Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len);
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
|
||||
WiFiClient *stream = https.getStreamPtr();
|
||||
uint8_t buff[128] = {0};
|
||||
char jsonElem[32] = "";
|
||||
char jsonValue[128] = "";
|
||||
int arrTok = 0;
|
||||
int objTok = 0;
|
||||
bool inQuote = false;
|
||||
bool inElem = false;
|
||||
bool inValue = false;
|
||||
bool awaitValue = false;
|
||||
bool inAss = false;
|
||||
while(https.connected() && (len > 0 || len == -1) && ndx < count) {
|
||||
size_t size = stream->available();
|
||||
if(size) {
|
||||
esp_task_wdt_reset();
|
||||
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
|
||||
//Serial.write(buff, c);
|
||||
if(len > 0) len -= c;
|
||||
// Now we should have some data.
|
||||
for(uint8_t i = 0; i < c; i++) {
|
||||
// Read the buffer a byte at a time until we have a key value pair.
|
||||
char ch = static_cast<char>(buff[i]);
|
||||
if(ch == '[') {
|
||||
arrTok++;
|
||||
if(arrTok == 2 && strcmp(jsonElem, "assets") == 0) {
|
||||
inElem = inValue = awaitValue = false;
|
||||
inAss = true;
|
||||
//Serial.printf("%s: %d\n", jsonElem, arrTok);
|
||||
}
|
||||
else if(arrTok < 2) inAss = false;
|
||||
}
|
||||
else if(ch == ']') {
|
||||
arrTok--;
|
||||
if(arrTok < 2) inAss = false;
|
||||
}
|
||||
else if(ch == '{') {
|
||||
objTok++;
|
||||
if(objTok != 1 && !inAss) inElem = inValue = awaitValue = false;
|
||||
}
|
||||
else if(ch == '}') {
|
||||
objTok--;
|
||||
if(objTok == 0) ndx++;
|
||||
}
|
||||
else if(objTok == 1 || inAss) {
|
||||
// We only want data from the root object.
|
||||
//if(inAss) Serial.print(ch);
|
||||
if(ch == '\"') {
|
||||
inQuote = !inQuote;
|
||||
if(inElem) {
|
||||
inElem = false;
|
||||
awaitValue = true;
|
||||
}
|
||||
else if(inValue) {
|
||||
inValue = false;
|
||||
inElem = false;
|
||||
awaitValue = false;
|
||||
if(inAss)
|
||||
this->releases[ndx].setAssetProperty(jsonElem, jsonValue);
|
||||
else
|
||||
this->releases[ndx].setReleaseProperty(jsonElem, jsonValue);
|
||||
memset(jsonElem, 0x00, sizeof(jsonElem));
|
||||
memset(jsonValue, 0x00, sizeof(jsonValue));
|
||||
}
|
||||
else if(awaitValue) inValue = true;
|
||||
else {
|
||||
inElem = true;
|
||||
awaitValue = false;
|
||||
}
|
||||
}
|
||||
else if(awaitValue) {
|
||||
if(ch != ' ' && ch != ':') {
|
||||
strncat(jsonValue, &ch, 1);
|
||||
awaitValue = false;
|
||||
inValue = true;
|
||||
}
|
||||
}
|
||||
else if((!inQuote && ch == ',') || ch == '\r' || ch == '\n') {
|
||||
inElem = inValue = awaitValue = false;
|
||||
if(strlen(jsonElem) > 0) {
|
||||
if(inAss)
|
||||
this->releases[ndx].setAssetProperty(jsonElem, jsonValue);
|
||||
else
|
||||
this->releases[ndx].setReleaseProperty(jsonElem, jsonValue);
|
||||
}
|
||||
memset(jsonElem, 0x00, sizeof(jsonElem));
|
||||
memset(jsonValue, 0x00, sizeof(jsonValue));
|
||||
}
|
||||
else {
|
||||
if(inElem) {
|
||||
if(strlen(jsonElem) < sizeof(jsonElem) - 1) strncat(jsonElem, &ch, 1);
|
||||
}
|
||||
else if(inValue) {
|
||||
if(strlen(jsonValue) < sizeof(jsonValue) - 1) strncat(jsonValue, &ch, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
//else break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
https.end();
|
||||
sclient.stop();
|
||||
return httpCode;
|
||||
}
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
}
|
||||
settings.printAvailHeap();
|
||||
return 0;
|
||||
}
|
||||
void GitRepo::toJSON(JsonResponse &json) {
|
||||
json.beginObject("fwVersion");
|
||||
settings.fwVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("appVersion");
|
||||
settings.appVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginArray("releases");
|
||||
for(uint8_t i = 0; i < GIT_MAX_RELEASES + 1; i++) {
|
||||
if(this->releases[i].id == 0) continue;
|
||||
json.beginObject();
|
||||
this->releases[i].toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
json.endArray();
|
||||
}
|
||||
#define UPDATE_ERR_OFFSET 20
|
||||
#define ERR_DOWNLOAD_HTTP -40
|
||||
#define ERR_DOWNLOAD_BUFFER -41
|
||||
#define ERR_DOWNLOAD_CONNECTION -42
|
||||
|
||||
void GitUpdater::loop() {
|
||||
if(!net.connected()) return;
|
||||
if(this->status == GIT_STATUS_READY) {
|
||||
if(settings.checkForUpdate &&
|
||||
(millis() > net.connectTime + 60000) && // Wait a minute before checking after connection.
|
||||
(this->lastCheck + 86400000 < millis() || this->lastCheck == 0) && !rebootDelay.reboot) { // 1 day
|
||||
this->checkForUpdate();
|
||||
}
|
||||
}
|
||||
else if(this->status == GIT_AWAITING_UPDATE) {
|
||||
Serial.println("Starting update process.........");
|
||||
this->status = GIT_UPDATING;
|
||||
this->beginUpdate(this->targetRelease);
|
||||
this->status = GIT_STATUS_READY;
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
else if(this->status == GIT_UPDATE_CANCELLING) {
|
||||
Serial.println("Cancelling update process..........");
|
||||
if(!this->lockFS) {
|
||||
this->status = GIT_UPDATE_CANCELLED;
|
||||
this->cancelled = true;
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
void GitUpdater::checkForUpdate() {
|
||||
if(this->status != 0) return; // If we are already checking.
|
||||
Serial.println("Check github for updates...");
|
||||
|
||||
this->status = GIT_STATUS_CHECK;
|
||||
settings.printAvailHeap();
|
||||
this->lastCheck = millis();
|
||||
if(this->checkInternet() == 0) {
|
||||
GitRepo repo;
|
||||
this->updateAvailable = false;
|
||||
this->error = repo.getReleases(2);
|
||||
if(this->error == 0) { // Get 2 releases so we can filter our pre-releases
|
||||
this->setCurrentRelease(repo);
|
||||
}
|
||||
else {
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
}
|
||||
this->status = GIT_STATUS_READY;
|
||||
}
|
||||
void GitUpdater::setCurrentRelease(GitRepo &repo) {
|
||||
this->updateAvailable = false;
|
||||
for(uint8_t i = 0; i < 2; i++) {
|
||||
if(repo.releases[i].draft || repo.releases[i].preRelease || repo.releases[i].id == 0) continue;
|
||||
// Compare the versions.
|
||||
this->latest.copy(repo.releases[i].version);
|
||||
if(repo.releases[i].version.compare(settings.fwVersion) > 0) {
|
||||
// We have a new release.
|
||||
this->updateAvailable = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->emitUpdateCheck();
|
||||
}
|
||||
void GitUpdater::toJSON(JsonResponse &json) {
|
||||
json.addElem("available", this->updateAvailable);
|
||||
json.addElem("status", this->status);
|
||||
json.addElem("error", (int32_t)this->error);
|
||||
json.addElem("cancelled", this->cancelled);
|
||||
json.addElem("checkForUpdate", settings.checkForUpdate);
|
||||
json.addElem("inetAvailable", this->inetAvailable);
|
||||
json.beginObject("fwVersion");
|
||||
settings.fwVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("appVersion");
|
||||
settings.appVersion.toJSON(json);
|
||||
json.endObject();
|
||||
json.beginObject("latest");
|
||||
this->latest.toJSON(json);
|
||||
json.endObject();
|
||||
}
|
||||
void GitUpdater::emitUpdateCheck(uint8_t num) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("fwStatus");
|
||||
json->beginObject();
|
||||
json->addElem("available", this->updateAvailable);
|
||||
json->addElem("status", this->status);
|
||||
json->addElem("error", (int32_t)this->error);
|
||||
json->addElem("cancelled", this->cancelled);
|
||||
json->addElem("checkForUpdate", settings.checkForUpdate);
|
||||
json->addElem("inetAvailable", this->inetAvailable);
|
||||
json->beginObject("fwVersion");
|
||||
settings.fwVersion.toJSON(json);
|
||||
json->endObject();
|
||||
json->beginObject("appVersion");
|
||||
settings.appVersion.toJSON(json);
|
||||
json->endObject();
|
||||
json->beginObject("latest");
|
||||
this->latest.toJSON(json);
|
||||
json->endObject();
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
}
|
||||
int GitUpdater::checkInternet() {
|
||||
int err = 500;
|
||||
uint32_t t = millis();
|
||||
WiFiClientSecure sclient;
|
||||
sclient.setInsecure();
|
||||
sclient.setHandshakeTimeout(3);
|
||||
esp_task_wdt_reset();
|
||||
HTTPClient https;
|
||||
https.setReuse(false);
|
||||
if(https.begin(sclient, "https://github.com/rstrouse/ESPSomfy-RTS")) {
|
||||
https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
https.setTimeout(3000);
|
||||
esp_task_wdt_reset();
|
||||
int httpCode = https.sendRequest("HEAD");
|
||||
esp_task_wdt_reset();
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
|
||||
err = 0;
|
||||
Serial.printf("Internet is Available: %ldms\n", millis() - t);
|
||||
this->inetAvailable = true;
|
||||
}
|
||||
else {
|
||||
err = httpCode;
|
||||
Serial.printf("Internet is Unavailable: %d: %ldms\n", err, millis() - t);
|
||||
this->inetAvailable = false;
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
return err;
|
||||
}
|
||||
void GitUpdater::emitDownloadProgress(size_t total, size_t loaded, const char *evt) { this->emitDownloadProgress(255, total, loaded, evt); }
|
||||
void GitUpdater::emitDownloadProgress(uint8_t num, size_t total, size_t loaded, const char *evt) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit(evt);
|
||||
json->beginObject();
|
||||
json->addElem("ver", this->targetRelease);
|
||||
json->addElem("part", (int32_t)this->partition);
|
||||
json->addElem("file", this->currentFile);
|
||||
json->addElem("total", (uint32_t)total);
|
||||
json->addElem("loaded", (uint32_t)loaded);
|
||||
json->addElem("error", (uint32_t)this->error);
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
/*
|
||||
char buf[420];
|
||||
snprintf(buf, sizeof(buf), "{\"ver\":\"%s\",\"part\":%d,\"file\":\"%s\",\"total\":%d,\"loaded\":%d, \"error\":%d}", this->targetRelease, this->partition, this->currentFile, total, loaded, this->error);
|
||||
if(num >= 255) sockEmit.sendToClients(evt, buf);
|
||||
else sockEmit.sendToClient(num, evt, buf);
|
||||
*/
|
||||
sockEmit.loop();
|
||||
webServer.loop();
|
||||
}
|
||||
void GitUpdater::setFirmwareFile() {
|
||||
esp_chip_info_t ci;
|
||||
esp_chip_info(&ci);
|
||||
switch(ci.model) {
|
||||
case esp_chip_model_t::CHIP_ESP32S3:
|
||||
strcpy(this->currentFile, "SomfyController.ino.esp32s3.bin");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32S2:
|
||||
strcpy(this->currentFile, "SomfyController.ino.esp32s2.bin");
|
||||
break;
|
||||
case esp_chip_model_t::CHIP_ESP32C3:
|
||||
strcpy(this->currentFile, "SomfyController.ino.esp32c3.bin");
|
||||
break;
|
||||
default:
|
||||
strcpy(this->currentFile, "SomfyController.ino.esp32.bin");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GitUpdater::beginUpdate(const char *version) {
|
||||
Serial.println("Begin update called...");
|
||||
if(strcmp(version, "Main") == 0) strcpy(this->baseUrl, "https://raw.githubusercontent.com/rstrouse/ESPSomfy-RTS/master/");
|
||||
else sprintf(this->baseUrl, "https://github.com/rstrouse/ESPSomfy-RTS/releases/download/%s/", version);
|
||||
|
||||
strcpy(this->targetRelease, version);
|
||||
this->emitUpdateCheck();
|
||||
this->setFirmwareFile();
|
||||
this->partition = U_FLASH;
|
||||
this->lockFS = this->cancelled = false;
|
||||
this->error = 0;
|
||||
this->error = this->downloadFile();
|
||||
if(this->error == 0 && !this->cancelled) {
|
||||
somfy.commit();
|
||||
strcpy(this->currentFile, "SomfyController.littlefs.bin");
|
||||
this->partition = U_SPIFFS;
|
||||
this->lockFS = true;
|
||||
this->error = this->downloadFile();
|
||||
this->lockFS = false;
|
||||
if(this->error == 0) {
|
||||
settings.fwVersion.parse(version);
|
||||
delay(100);
|
||||
Serial.println("Committing Configuration...");
|
||||
somfy.commit();
|
||||
}
|
||||
rebootDelay.reboot = true;
|
||||
rebootDelay.rebootTime = millis() + 500;
|
||||
}
|
||||
this->status = GIT_UPDATE_COMPLETE;
|
||||
this->emitUpdateCheck();
|
||||
return true;
|
||||
}
|
||||
bool GitUpdater::recoverFilesystem() {
|
||||
sprintf(this->baseUrl, "https://github.com/rstrouse/ESPSomfy-RTS/releases/download/%s/", settings.fwVersion.name);
|
||||
strcpy(this->currentFile, "SomfyController.littlefs.bin");
|
||||
this->status = GIT_UPDATING;
|
||||
this->partition = U_SPIFFS;
|
||||
this->lockFS = true;
|
||||
this->error = this->downloadFile();
|
||||
this->lockFS = false;
|
||||
if(this->error == 0) {
|
||||
delay(100);
|
||||
Serial.println("Committing Configuration...");
|
||||
somfy.commit();
|
||||
}
|
||||
this->status = GIT_UPDATE_COMPLETE;
|
||||
rebootDelay.reboot = true;
|
||||
rebootDelay.rebootTime = millis() + 500;
|
||||
return true;
|
||||
}
|
||||
bool GitUpdater::endUpdate() { return true; }
|
||||
int8_t GitUpdater::downloadFile() {
|
||||
Serial.printf("Begin update %s\n", this->currentFile);
|
||||
WiFiClientSecure sclient;
|
||||
sclient.setInsecure();
|
||||
HTTPClient https;
|
||||
char url[196];
|
||||
sprintf(url, "%s%s", this->baseUrl, this->currentFile);
|
||||
Serial.println(url);
|
||||
esp_task_wdt_reset();
|
||||
if(https.begin(sclient, url)) {
|
||||
https.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
Serial.print("[HTTPS] GET...\n");
|
||||
int httpCode = https.GET();
|
||||
if(httpCode > 0) {
|
||||
size_t len = https.getSize();
|
||||
size_t total = 0;
|
||||
uint8_t pct = 0;
|
||||
Serial.printf("[HTTPS] GET... code: %d - %d\n", httpCode, len);
|
||||
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY || httpCode == HTTP_CODE_FOUND) {
|
||||
WiFiClient *stream = https.getStreamPtr();
|
||||
if(!Update.begin(len, this->partition)) {
|
||||
Serial.println("Update Error detected!!!!!");
|
||||
Update.printError(Serial);
|
||||
https.end();
|
||||
return -(Update.getError() + UPDATE_ERR_OFFSET);
|
||||
}
|
||||
uint8_t *buff = (uint8_t *)malloc(MAX_BUFF_SIZE);
|
||||
if(buff) {
|
||||
this->emitDownloadProgress(len, total);
|
||||
int timeouts = 0;
|
||||
while(https.connected() && (len > 0 || len == -1) && total < len) {
|
||||
size_t size = stream->available();
|
||||
esp_task_wdt_reset();
|
||||
if(size) {
|
||||
timeouts = 0;
|
||||
if(this->cancelled && !this->lockFS) {
|
||||
Update.abort();
|
||||
free(buff);
|
||||
https.end();
|
||||
return -(Update.getError() + UPDATE_ERR_OFFSET);
|
||||
}
|
||||
int c = stream->readBytes(buff, ((size > MAX_BUFF_SIZE) ? MAX_BUFF_SIZE : size));
|
||||
total += c;
|
||||
//Serial.println(total);
|
||||
if (Update.write(buff, c) != c) {
|
||||
Update.printError(Serial);
|
||||
Serial.printf("Upload of %s aborted invalid size %d\n", url, c);
|
||||
free(buff);
|
||||
https.end();
|
||||
sclient.stop();
|
||||
return -(Update.getError() + UPDATE_ERR_OFFSET);
|
||||
}
|
||||
// Calculate the percentage.
|
||||
uint8_t p = (uint8_t)floor(((float)total / (float)len) * 100.0f);
|
||||
if(p != pct) {
|
||||
pct = p;
|
||||
Serial.printf("LEN:%d TOTAL:%d %d%%\n", len, total, pct);
|
||||
this->emitDownloadProgress(len, total);
|
||||
}
|
||||
delay(1);
|
||||
if(total >= len) {
|
||||
if(!Update.end(true)) {
|
||||
Serial.println("Error downloading update...");
|
||||
Update.printError(Serial);
|
||||
}
|
||||
else {
|
||||
Serial.println("Update.end Called...");
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
timeouts++;
|
||||
if(timeouts >= 500) {
|
||||
Update.abort();
|
||||
https.end();
|
||||
free(buff);
|
||||
Serial.println("Stream timeout!!!");
|
||||
return -43;
|
||||
}
|
||||
sockEmit.loop();
|
||||
webServer.loop();
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
free(buff);
|
||||
if(len > total) {
|
||||
Update.abort();
|
||||
somfy.commit();
|
||||
Serial.println("Error downloading file!!!");
|
||||
return -42;
|
||||
}
|
||||
else
|
||||
Serial.printf("Update %s complete\n", this->currentFile);
|
||||
}
|
||||
else {
|
||||
// TODO: memory allocation error.
|
||||
Serial.println("Unable to allocate memory for update!!!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.printf("Invalid HTTP Code... %d", httpCode);
|
||||
return httpCode;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.printf("Invalid HTTP Code: %d\n", httpCode);
|
||||
}
|
||||
https.end();
|
||||
sclient.stop();
|
||||
Serial.printf("End update %s\n", this->currentFile);
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
return 0;
|
||||
}
|
||||
68
src/GitOTA.h
Normal file
68
src/GitOTA.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#ifndef GITOTA_H
|
||||
#define GITOTA_H
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "WResp.h"
|
||||
|
||||
#define GIT_MAX_RELEASES 5
|
||||
#define GIT_STATUS_READY 0
|
||||
#define GIT_STATUS_CHECK 1
|
||||
#define GIT_AWAITING_UPDATE 2
|
||||
#define GIT_UPDATING 3
|
||||
#define GIT_UPDATE_COMPLETE 4
|
||||
#define GIT_UPDATE_CANCELLING 5
|
||||
#define GIT_UPDATE_CANCELLED 6
|
||||
|
||||
class GitRelease {
|
||||
public:
|
||||
uint64_t id = 0;
|
||||
bool draft = false;
|
||||
bool preRelease = false;
|
||||
bool main = false;
|
||||
bool hasFS = false;
|
||||
char hwVersions[128] = "";
|
||||
time_t releaseDate;
|
||||
char name[32] = "";
|
||||
appver_t version;
|
||||
void setReleaseProperty(const char *key, const char *val);
|
||||
void setAssetProperty(const char *key, const char *val);
|
||||
void toJSON(JsonResponse &json);
|
||||
};
|
||||
class GitRepo {
|
||||
public:
|
||||
int16_t getReleases(uint8_t num = GIT_MAX_RELEASES);
|
||||
GitRelease releases[GIT_MAX_RELEASES + 1];
|
||||
void toJSON(JsonResponse &json);
|
||||
};
|
||||
class GitUpdater {
|
||||
public:
|
||||
bool lockFS = false;
|
||||
bool canCancel = true;
|
||||
uint8_t status = 0;
|
||||
uint32_t lastCheck = 0;
|
||||
bool updateAvailable = false;
|
||||
bool inetAvailable = false;
|
||||
appver_t latest;
|
||||
bool cancelled = false;
|
||||
int16_t error = 0;
|
||||
char targetRelease[32];
|
||||
char currentFile[64] = "";
|
||||
char baseUrl[128] = "";
|
||||
int partition = 0;
|
||||
void checkForUpdate();
|
||||
bool beginUpdate(const char *release);
|
||||
bool endUpdate();
|
||||
int8_t downloadFile();
|
||||
void setFirmwareFile();
|
||||
void setCurrentRelease(GitRepo &repo);
|
||||
void loop();
|
||||
void toJSON(JsonResponse &json);
|
||||
bool recoverFilesystem();
|
||||
int checkInternet();
|
||||
void emitUpdateCheck(uint8_t num=255);
|
||||
void emitDownloadProgress(size_t total, size_t loaded, const char *evt = "updateProgress");
|
||||
void emitDownloadProgress(uint8_t num, size_t total, size_t loaded, const char *evt = "updateProgress");
|
||||
};
|
||||
#endif
|
||||
371
src/MQTT.cpp
Normal file
371
src/MQTT.cpp
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
#include <WiFi.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "MQTT.h"
|
||||
#include "Somfy.h"
|
||||
#include "Network.h"
|
||||
#include "Utils.h"
|
||||
|
||||
WiFiClient tcpClient;
|
||||
PubSubClient mqttClient(tcpClient);
|
||||
|
||||
#define MQTT_MAX_RESPONSE 2048
|
||||
static char g_content[MQTT_MAX_RESPONSE];
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern SomfyShadeController somfy;
|
||||
extern Network net;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
|
||||
|
||||
bool MQTTClass::begin() {
|
||||
this->suspended = false;
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::end() {
|
||||
this->suspended = true;
|
||||
this->disconnect();
|
||||
return true;
|
||||
}
|
||||
void MQTTClass::reset() {
|
||||
this->disconnect();
|
||||
this->lastConnect = 0;
|
||||
this->connect();
|
||||
}
|
||||
bool MQTTClass::loop() {
|
||||
if(settings.MQTT.enabled && !rebootDelay.reboot && !this->suspended && !mqttClient.connected()) {
|
||||
esp_task_wdt_reset();
|
||||
if(!this->connected() && net.connected()) this->connect();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
if(settings.MQTT.enabled) mqttClient.loop();
|
||||
return true;
|
||||
}
|
||||
void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
Serial.print("MQTT Topic:");
|
||||
Serial.print(topic);
|
||||
Serial.print(" payload:");
|
||||
for(uint32_t i=0; i<length; i++)
|
||||
Serial.print((char)payload[i]);
|
||||
Serial.println();
|
||||
|
||||
// We need to start at the last slash in the data
|
||||
uint8_t len = strlen(topic);
|
||||
|
||||
uint8_t slashes = 0;
|
||||
uint16_t ndx = strlen(topic) - 1;
|
||||
while(ndx > 0) {
|
||||
if(topic[ndx] == '/') slashes++;
|
||||
if(slashes == 4) break;
|
||||
ndx--;
|
||||
}
|
||||
char entityId[4];
|
||||
char command[32];
|
||||
char entityType[7];
|
||||
char value[10];
|
||||
memset(command, 0x00, sizeof(command));
|
||||
memset(entityId, 0x00, sizeof(entityId));
|
||||
memset(entityType, 0x00, sizeof(entityType));
|
||||
memset(value, 0x00, sizeof(value));
|
||||
uint8_t i = 0;
|
||||
while(topic[ndx] == '/' && ndx < len) ndx++;
|
||||
while(ndx < len) {
|
||||
if(topic[ndx] != '/' && i < sizeof(entityType))
|
||||
entityType[i++] = topic[ndx];
|
||||
ndx++;
|
||||
if(topic[ndx] == '/') break;
|
||||
}
|
||||
i = 0;
|
||||
while(topic[ndx] == '/' && ndx < len) ndx++;
|
||||
while(ndx < len) {
|
||||
if(topic[ndx] != '/' && i < sizeof(entityId))
|
||||
entityId[i++] = topic[ndx];
|
||||
ndx++;
|
||||
if(topic[ndx] == '/') break;
|
||||
}
|
||||
i = 0;
|
||||
while(topic[ndx] == '/' && ndx < len) ndx++;
|
||||
while(ndx < len) {
|
||||
if(topic[ndx] != '/' && i < sizeof(command))
|
||||
command[i++] = topic[ndx];
|
||||
ndx++;
|
||||
if(topic[ndx] == '/') break;
|
||||
}
|
||||
for(uint8_t j = 0; j < length && j < sizeof(value); j++)
|
||||
value[j] = payload[j];
|
||||
|
||||
Serial.print("MQTT type:[");
|
||||
Serial.print(entityType);
|
||||
Serial.print("] command:[");
|
||||
Serial.print(command);
|
||||
Serial.print("] entityId:");
|
||||
Serial.print(entityId);
|
||||
Serial.print(" value:");
|
||||
Serial.println(value);
|
||||
if(strncmp(entityType, "shades", sizeof(entityType)) == 0) {
|
||||
SomfyShade* shade = somfy.getShadeById(atoi(entityId));
|
||||
if (shade) {
|
||||
int val = atoi(value);
|
||||
if(strncmp(command, "target", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->moveToTarget(shade->transformPosition(atoi(value)));
|
||||
}
|
||||
if(strncmp(command, "tiltTarget", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->moveToTiltTarget(atoi(value));
|
||||
}
|
||||
else if(strncmp(command, "direction", sizeof(command)) == 0) {
|
||||
if(val < 0)
|
||||
shade->sendCommand(somfy_commands::Up);
|
||||
else if(val > 0)
|
||||
shade->sendCommand(somfy_commands::Down);
|
||||
else
|
||||
shade->sendCommand(somfy_commands::My);
|
||||
}
|
||||
else if(strncmp(command, "mypos", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->setMyPosition(val);
|
||||
}
|
||||
else if(strncmp(command, "myTiltPos", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->setMyPosition(shade->myPos, val);
|
||||
}
|
||||
else if(strncmp(command, "sunFlag", sizeof(command)) == 0) {
|
||||
if(val > 0) shade->sendCommand(somfy_commands::SunFlag);
|
||||
else shade->sendCommand(somfy_commands::Flag);
|
||||
}
|
||||
else if(strncmp(command, "position", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100) {
|
||||
shade->target = shade->currentPos = shade->transformPosition((float)val);
|
||||
shade->emitState();
|
||||
}
|
||||
}
|
||||
else if(strncmp(command, "tiltPosition", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100) {
|
||||
shade->tiltTarget = shade->currentTiltPos = (float)val;
|
||||
shade->emitState();
|
||||
}
|
||||
}
|
||||
else if(strncmp(command, "sunny", sizeof(command)) == 0) {
|
||||
if(val >= 0) shade->sendSensorCommand(-1, val, shade->repeats);
|
||||
}
|
||||
else if(strncmp(command, "windy", sizeof(command)) == 0) {
|
||||
if(val >= 0) shade->sendSensorCommand(val, -1, shade->repeats);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(strncmp(entityType, "groups", sizeof(entityType)) == 0) {
|
||||
SomfyGroup* group = somfy.getGroupById(atoi(entityId));
|
||||
if (group) {
|
||||
int val = atoi(value);
|
||||
if(strncmp(command, "direction", sizeof(command)) == 0) {
|
||||
if(val < 0)
|
||||
group->sendCommand(somfy_commands::Up);
|
||||
else if(val > 0)
|
||||
group->sendCommand(somfy_commands::Down);
|
||||
else
|
||||
group->sendCommand(somfy_commands::My);
|
||||
}
|
||||
else if(strncmp(command, "sunFlag", sizeof(command)) == 0) {
|
||||
if(val > 0)
|
||||
group->sendCommand(somfy_commands::Flag);
|
||||
else
|
||||
group->sendCommand(somfy_commands::SunFlag);
|
||||
}
|
||||
else if(strncmp(command, "sunny", sizeof(command)) == 0) {
|
||||
if(val >= 0) group->sendSensorCommand(-1, val, group->repeats);
|
||||
}
|
||||
else if(strncmp(command, "windy", sizeof(command)) == 0) {
|
||||
if(val >= 0) group->sendSensorCommand(val, -1, group->repeats);
|
||||
}
|
||||
}
|
||||
}
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
}
|
||||
bool MQTTClass::connect() {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
if(mqttClient.connected()) {
|
||||
if(!settings.MQTT.enabled || this->suspended)
|
||||
return this->disconnect();
|
||||
else
|
||||
return true;
|
||||
}
|
||||
if(settings.MQTT.enabled && !this->suspended) {
|
||||
if(this->lastConnect + 10000 > millis()) return false;
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
snprintf(this->clientId, sizeof(this->clientId), "client-%08x%08x", (uint32_t)((mac >> 32) & 0xFFFFFFFF), (uint32_t)(mac & 0xFFFFFFFF));
|
||||
if(strlen(settings.MQTT.protocol) > 0 && strlen(settings.MQTT.hostname) > 0) {
|
||||
mqttClient.setServer(settings.MQTT.hostname, settings.MQTT.port);
|
||||
char lwtTopic[128] = "status";
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(lwtTopic, sizeof(lwtTopic), "%s/status", settings.MQTT.rootTopic);
|
||||
esp_task_wdt_reset();
|
||||
if(mqttClient.connect(this->clientId, settings.MQTT.username, settings.MQTT.password, lwtTopic, 0, true, "offline")) {
|
||||
Serial.print("Successfully connected MQTT client ");
|
||||
Serial.println(this->clientId);
|
||||
this->publish("status", "online", true);
|
||||
this->publish("ipAddress", settings.IP.ip.toString().c_str(), true);
|
||||
this->publish("host", settings.hostname, true);
|
||||
this->publish("firmware", settings.fwVersion.name, true);
|
||||
this->publish("serverId", settings.serverId, true);
|
||||
this->publish("mac", net.mac.c_str());
|
||||
somfy.publish();
|
||||
this->subscribe("shades/+/target/set");
|
||||
this->subscribe("shades/+/tiltTarget/set");
|
||||
this->subscribe("shades/+/direction/set");
|
||||
this->subscribe("shades/+/mypos/set");
|
||||
this->subscribe("shades/+/myTiltPos/set");
|
||||
this->subscribe("shades/+/sunFlag/set");
|
||||
this->subscribe("shades/+/sunny/set");
|
||||
this->subscribe("shades/+/windy/set");
|
||||
this->subscribe("shades/+/position/set");
|
||||
this->subscribe("shades/+/tiltPosition/set");
|
||||
this->subscribe("groups/+/direction/set");
|
||||
this->subscribe("groups/+/sunFlag/set");
|
||||
this->subscribe("groups/+/sunny/set");
|
||||
this->subscribe("groups/+/windy/set");
|
||||
mqttClient.setCallback(MQTTClass::receive);
|
||||
Serial.println("MQTT Startup Completed");
|
||||
esp_task_wdt_reset();
|
||||
this->lastConnect = millis();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Serial.print("MQTT Connection failed for: ");
|
||||
Serial.println(mqttClient.state());
|
||||
this->lastConnect = millis();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::disconnect() {
|
||||
if(mqttClient.connected()) {
|
||||
this->unsubscribe("shades/+/target/set");
|
||||
this->unsubscribe("shades/+/direction/set");
|
||||
this->unsubscribe("shades/+/tiltTarget/set");
|
||||
this->unsubscribe("shades/+/mypos/set");
|
||||
this->unsubscribe("shades/+/myTiltPos/set");
|
||||
this->unsubscribe("shades/+/sunFlag/set");
|
||||
this->unsubscribe("groups/+/direction/set");
|
||||
this->unsubscribe("shades/+/sunny/set");
|
||||
this->unsubscribe("shades/+/windy/set");
|
||||
this->unsubscribe("shades/+/position/set");
|
||||
this->unsubscribe("shades/+/tiltPosition/set");
|
||||
this->unsubscribe("groups/+/direction/set");
|
||||
this->unsubscribe("groups/+/sunFlag/set");
|
||||
this->unsubscribe("groups/+/sunny/set");
|
||||
this->unsubscribe("groups/+/windy/set");
|
||||
mqttClient.disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::unsubscribe(const char *topic) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[128];
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
Serial.print("MQTT Unsubscribed from:");
|
||||
Serial.println(top);
|
||||
return mqttClient.unsubscribe(top);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::subscribe(const char *topic) {
|
||||
if(mqttClient.connected()) {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
char top[128];
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
Serial.print("MQTT Subscribed to:");
|
||||
Serial.println(top);
|
||||
return mqttClient.subscribe(top);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, const char *payload, bool retain) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[128];
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
mqttClient.publish(top, payload, retain);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, uint32_t val, bool retain) {
|
||||
snprintf(g_content, sizeof(g_content), "%u", val);
|
||||
return this->publish(topic, g_content, retain);
|
||||
}
|
||||
bool MQTTClass::unpublish(const char *topic) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[128];
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
mqttClient.publish(top, (const uint8_t *)"", 0, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
// mqttClient.beginPublish(topic, 0, true);
|
||||
// mqttClient.endPublish();
|
||||
}
|
||||
|
||||
bool MQTTClass::publishBuffer(const char *topic, uint8_t *data, uint16_t len, bool retain) {
|
||||
size_t res;
|
||||
uint16_t offset = 0;
|
||||
uint16_t to_write = len;
|
||||
uint16_t buff_len;
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
mqttClient.beginPublish(topic, len, retain);
|
||||
do {
|
||||
buff_len = to_write;
|
||||
if(buff_len > 128) buff_len = 128;
|
||||
res = mqttClient.write(data+offset, buff_len);
|
||||
offset += buff_len;
|
||||
to_write -= buff_len;
|
||||
} while(res == buff_len && to_write > 0);
|
||||
mqttClient.endPublish();
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::publishDisco(const char *topic, JsonObject &obj, bool retain) {
|
||||
serializeJson(obj, g_content, sizeof(g_content));
|
||||
this->publishBuffer(topic, (uint8_t *)g_content, strlen(g_content), retain);
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, int8_t val, bool retain) {
|
||||
snprintf(g_content, sizeof(g_content), "%d", val);
|
||||
return this->publish(topic, g_content, retain);
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, uint8_t val, bool retain) {
|
||||
snprintf(g_content, sizeof(g_content), "%u", val);
|
||||
return this->publish(topic, g_content, retain);
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, uint16_t val, bool retain) {
|
||||
snprintf(g_content, sizeof(g_content), "%u", val);
|
||||
return this->publish(topic, g_content, retain);
|
||||
}
|
||||
bool MQTTClass::publish(const char *topic, bool val, bool retain) {
|
||||
snprintf(g_content, sizeof(g_content), "%s", val ? "true" : "false");
|
||||
return this->publish(topic, g_content, retain);
|
||||
}
|
||||
bool MQTTClass::connected() {
|
||||
if(settings.MQTT.enabled) return mqttClient.connected();
|
||||
return false;
|
||||
}
|
||||
31
src/MQTT.h
Normal file
31
src/MQTT.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef MQTT_H
|
||||
#define MQTT_H
|
||||
#include <Arduino.h>
|
||||
#include <PubSubClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
class MQTTClass {
|
||||
public:
|
||||
uint64_t lastConnect = 0;
|
||||
bool suspended = false;
|
||||
char clientId[32] = {'\0'};
|
||||
bool begin();
|
||||
bool loop();
|
||||
bool end();
|
||||
bool connect();
|
||||
bool disconnect();
|
||||
bool connected();
|
||||
void reset();
|
||||
bool unpublish(const char *topic);
|
||||
bool publish(const char *topic, const char *payload, bool retain = false);
|
||||
bool publish(const char *topic, uint8_t val, bool retain = false);
|
||||
bool publish(const char *topic, int8_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint32_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint16_t val, bool retain = false);
|
||||
bool publish(const char *topic, bool val, bool retain = false);
|
||||
bool publishBuffer(const char *topic, uint8_t *data, uint16_t len, bool retain = false);
|
||||
bool publishDisco(const char *topic, JsonObject &obj, bool retain = false);
|
||||
bool subscribe(const char *topic);
|
||||
bool unsubscribe(const char *topic);
|
||||
static void receive(const char *topic, byte *payload, uint32_t length);
|
||||
};
|
||||
#endif
|
||||
711
src/Network.cpp
Normal file
711
src/Network.cpp
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
#include <ETH.h>
|
||||
#include <WiFi.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Network.h"
|
||||
#include "Web.h"
|
||||
#include "Sockets.h"
|
||||
#include "Utils.h"
|
||||
#include "SSDP.h"
|
||||
#include "MQTT.h"
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern Web webServer;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern MQTTClass mqtt;
|
||||
extern rebootDelay_t rebootDelay;
|
||||
extern Network net;
|
||||
extern SomfyShadeController somfy;
|
||||
|
||||
static unsigned long _lastHeapEmit = 0;
|
||||
|
||||
static bool _apScanning = false;
|
||||
static uint32_t _lastMaxHeap = 0;
|
||||
static uint32_t _lastHeap = 0;
|
||||
int connectRetries = 0;
|
||||
void Network::end() {
|
||||
SSDP.end();
|
||||
mqtt.end();
|
||||
sockEmit.end();
|
||||
delay(100);
|
||||
}
|
||||
bool Network::setup() {
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
WiFi.persistent(false);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.onEvent(this->networkEvent);
|
||||
this->disconnectTime = millis();
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true, true);
|
||||
if(settings.connType == conn_types_t::wifi || settings.connType == conn_types_t::unset) {
|
||||
WiFi.persistent(false);
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
Serial.print("WiFi Mode: ");
|
||||
Serial.println(WiFi.getMode());
|
||||
WiFi.mode(WIFI_STA);
|
||||
}
|
||||
sockEmit.begin();
|
||||
return true;
|
||||
}
|
||||
conn_types_t Network::preferredConnType() {
|
||||
switch(settings.connType) {
|
||||
case conn_types_t::wifi:
|
||||
return settings.WIFI.ssid[0] != '\0' ? conn_types_t::wifi : conn_types_t::ap;
|
||||
case conn_types_t::unset:
|
||||
case conn_types_t::ap:
|
||||
return conn_types_t::ap;
|
||||
case conn_types_t::ethernetpref:
|
||||
return settings.WIFI.ssid[0] != '\0' && (!ETH.linkUp() && this->ethStarted) ? conn_types_t::wifi : conn_types_t::ethernet;
|
||||
case conn_types_t::ethernet:
|
||||
return ETH.linkUp() || !this->ethStarted ? conn_types_t::ethernet : conn_types_t::ap;
|
||||
default:
|
||||
return settings.connType;
|
||||
}
|
||||
}
|
||||
void Network::loop() {
|
||||
// ORDER OF OPERATIONS:
|
||||
// ----------------------------------------------
|
||||
// 1. If we are in the middle of a connection process we need to simply bail after the connect method. The
|
||||
// connect method will take care of our target connection for us.
|
||||
// 2. Check to see what type of target connection we need.
|
||||
// a. If this is an ethernet target then the connection needs to perform a fallback if applicable.
|
||||
// b. If this is a wifi target then we need to first check to see if the SSID is available.
|
||||
// c. If an SSID has not been set then we need to turn on the Soft AP.
|
||||
// 3. If the Soft AP is open and the target is either wifi, ethernet, or ethernetpref then
|
||||
// we need to shut it down if there are no connections and the preferred connection is available.
|
||||
// a. Ethernet: Check for an active ethernet connection. We cannot rely on linkup because the PHY will
|
||||
// report that the link is up when no IP address is being served.
|
||||
// b. WiFi: Perform synchronous scan for APs related to the SSID. If the SSID can be found then perform
|
||||
// the connection process for the WiFi connection.
|
||||
// c. SoftAP: This condition retains the Soft AP because no other connection method is available.
|
||||
conn_types_t ctype = this->preferredConnType();
|
||||
this->connect(ctype); // Connection timeout handled in connect function as well as the opening of the Soft AP if needed.
|
||||
if(this->connecting()) return; // If we are currently attempting to connect to something then we need to bail here.
|
||||
if(_apScanning) {
|
||||
if(settings.WIFI.hidden || // This user has elected to use a hidden AP.
|
||||
(this->connected() && !settings.WIFI.roaming) || // We are already connected and should not be roaming.
|
||||
(this->softAPOpened && WiFi.softAPgetStationNum() != 0) || // The Soft AP is open and a user is connected.
|
||||
(ctype != conn_types_t::wifi)) { // The Ethernet link is up so we should ignore this scan.
|
||||
Serial.println("Cancelling WiFi STA Scan...");
|
||||
_apScanning = false;
|
||||
WiFi.scanDelete();
|
||||
}
|
||||
else {
|
||||
int16_t n = WiFi.scanComplete();
|
||||
if( n >= 0) { // If the scan is complete but the WiFi isn't ready this can return 0.
|
||||
uint8_t bssid[6];
|
||||
int32_t channel = 0;
|
||||
if(this->getStrongestAP(settings.WIFI.ssid, bssid, &channel)) {
|
||||
if(!WiFi.BSSID() || memcmp(bssid, WiFi.BSSID(), sizeof(bssid)) != 0) {
|
||||
if(!this->connected()) {
|
||||
Serial.printf("Connecting to AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
this->connectWiFi(bssid, channel);
|
||||
}
|
||||
else {
|
||||
Serial.printf("Found stronger AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
|
||||
this->changeAP(bssid, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
_apScanning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!this->connecting() && !settings.WIFI.hidden) {
|
||||
if((this->softAPOpened && WiFi.softAPgetStationNum() == 0) ||
|
||||
(!this->connected() && ctype == conn_types_t::wifi)) {
|
||||
// If the Soft AP is opened and there are no clients connected then we need to scan for an AP. If
|
||||
// our target exists we will exit out of the Soft AP and start that connection. We are also
|
||||
// going to continuously scan when there is no connection and our preferred connection is wifi.
|
||||
if(ctype == conn_types_t::wifi) {
|
||||
// Scan for an AP but only if we are not already scanning.
|
||||
if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) {
|
||||
_apScanning = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(this->connected() && ctype == conn_types_t::wifi && settings.WIFI.roaming) {
|
||||
// Periodically look for a roaming AP.
|
||||
if(millis() > SSID_SCAN_INTERVAL + this->lastWifiScan) {
|
||||
//Serial.println("Started scan for access points");
|
||||
if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) {
|
||||
_apScanning = true;
|
||||
this->lastWifiScan = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(millis() - this->lastEmit > 1500) {
|
||||
// Post our connection status if needed.
|
||||
this->lastEmit = millis();
|
||||
if(this->connected()) {
|
||||
this->emitSockets();
|
||||
this->lastEmit = millis();
|
||||
}
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
}
|
||||
|
||||
sockEmit.loop();
|
||||
mqtt.loop();
|
||||
if(settings.ssdpBroadcast && this->connected()) {
|
||||
if(!SSDP.isStarted) SSDP.begin();
|
||||
if(SSDP.isStarted) SSDP.loop();
|
||||
}
|
||||
else if(!settings.ssdpBroadcast && SSDP.isStarted) SSDP.end();
|
||||
}
|
||||
bool Network::changeAP(const uint8_t *bssid, const int32_t channel) {
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
if(SSDP.isStarted) SSDP.end();
|
||||
mqtt.disconnect();
|
||||
//sockEmit.end();
|
||||
WiFi.disconnect(false, true);
|
||||
this->connType = conn_types_t::unset;
|
||||
this->_connecting = true;
|
||||
this->connectStart = millis();
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid);
|
||||
this->connectStart = millis();
|
||||
return false;
|
||||
}
|
||||
void Network::emitSockets() {
|
||||
this->emitHeap();
|
||||
if(this->needsBroadcast ||
|
||||
(this->connType == conn_types_t::wifi && (abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel))) {
|
||||
this->emitSockets(255);
|
||||
sockEmit.loop();
|
||||
this->needsBroadcast = false;
|
||||
}
|
||||
}
|
||||
void Network::emitSockets(uint8_t num) {
|
||||
if(this->connType == conn_types_t::ethernet) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
|
||||
json->beginObject();
|
||||
json->addElem("connected", this->connected());
|
||||
json->addElem("speed", ETH.linkSpeed());
|
||||
json->addElem("fullduplex", ETH.fullDuplex());
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
}
|
||||
else {
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
|
||||
json->beginObject();
|
||||
json->addElem("ssid", WiFi.SSID().c_str());
|
||||
json->addElem("strength", (int32_t)WiFi.RSSI());
|
||||
json->addElem("channel", (int32_t)this->channel);
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
this->lastRSSI = WiFi.RSSI();
|
||||
this->lastChannel = WiFi.channel();
|
||||
}
|
||||
else {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
|
||||
json->beginObject();
|
||||
json->addElem("ssid", "");
|
||||
json->addElem("strength", (int8_t)-100);
|
||||
json->addElem("channel", (int8_t)-1);
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
|
||||
json = sockEmit.beginEmit("ethernet");
|
||||
json->beginObject();
|
||||
json->addElem("connected", false);
|
||||
json->addElem("speed", (uint8_t)0);
|
||||
json->addElem("fullduplex", false);
|
||||
json->endObject();
|
||||
sockEmit.endEmit(num);
|
||||
this->lastRSSI = -100;
|
||||
this->lastChannel = -1;
|
||||
}
|
||||
}
|
||||
this->emitHeap(num);
|
||||
}
|
||||
void Network::setConnected(conn_types_t connType) {
|
||||
esp_task_wdt_reset();
|
||||
this->connType = connType;
|
||||
this->connectTime = millis();
|
||||
connectRetries = 0;
|
||||
Serial.println("Setting connected...");
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
if(this->softAPOpened && WiFi.softAPgetStationNum() == 0) {
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_STA);
|
||||
}
|
||||
this->_connecting = false;
|
||||
this->ssid = WiFi.SSID();
|
||||
this->mac = WiFi.BSSIDstr();
|
||||
this->strength = WiFi.RSSI();
|
||||
this->channel = WiFi.channel();
|
||||
this->connectAttempts++;
|
||||
}
|
||||
else if(this->connType == conn_types_t::ethernet) {
|
||||
if(this->softAPOpened) {
|
||||
Serial.println("Disonnecting from SoftAP");
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
this->connectAttempts++;
|
||||
this->_connecting = false;
|
||||
this->wifiFallback = false;
|
||||
}
|
||||
// NET: Begin this in the startup.
|
||||
//sockEmit.begin();
|
||||
esp_task_wdt_reset();
|
||||
|
||||
if(this->connectAttempts == 1) {
|
||||
Serial.println();
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
Serial.print("Successfully Connected to WiFi!!!!");
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.println("dbm)");
|
||||
if(settings.IP.dhcp) {
|
||||
settings.IP.ip = WiFi.localIP();
|
||||
settings.IP.subnet = WiFi.subnetMask();
|
||||
settings.IP.gateway = WiFi.gatewayIP();
|
||||
settings.IP.dns1 = WiFi.dnsIP(0);
|
||||
settings.IP.dns2 = WiFi.dnsIP(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.print("Successfully Connected to Ethernet!!! ");
|
||||
Serial.print(ETH.localIP());
|
||||
if(ETH.fullDuplex()) {
|
||||
Serial.print(" FULL DUPLEX");
|
||||
}
|
||||
Serial.print(" ");
|
||||
Serial.print(ETH.linkSpeed());
|
||||
Serial.println("Mbps");
|
||||
if(settings.IP.dhcp) {
|
||||
settings.IP.ip = ETH.localIP();
|
||||
settings.IP.subnet = ETH.subnetMask();
|
||||
settings.IP.gateway = ETH.gatewayIP();
|
||||
settings.IP.dns1 = ETH.dnsIP(0);
|
||||
settings.IP.dns2 = ETH.dnsIP(1);
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
|
||||
json->beginObject();
|
||||
json->addElem("connected", this->connected());
|
||||
json->addElem("speed", ETH.linkSpeed());
|
||||
json->addElem("fullduplex", ETH.fullDuplex());
|
||||
json->endObject();
|
||||
sockEmit.endEmit();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Serial.println();
|
||||
Serial.print("Reconnected after ");
|
||||
Serial.print(1.0 * (millis() - this->connectStart)/1000);
|
||||
Serial.print("sec IP: ");
|
||||
if(this->connType == conn_types_t::wifi) {
|
||||
Serial.print(WiFi.localIP());
|
||||
Serial.print(" ");
|
||||
Serial.print(this->mac);
|
||||
Serial.print(" CH:");
|
||||
Serial.print(this->channel);
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.print(" dBm)");
|
||||
}
|
||||
else {
|
||||
Serial.print(ETH.localIP());
|
||||
if(ETH.fullDuplex()) {
|
||||
Serial.print(" FULL DUPLEX");
|
||||
}
|
||||
Serial.print(" ");
|
||||
Serial.print(ETH.linkSpeed());
|
||||
Serial.print("Mbps");
|
||||
}
|
||||
Serial.print(" Disconnected ");
|
||||
Serial.print(this->connectAttempts - 1);
|
||||
Serial.println(" times");
|
||||
}
|
||||
SSDP.setHTTPPort(80);
|
||||
SSDP.setSchemaURL(0, "upnp.xml");
|
||||
SSDP.setChipId(0, this->getChipId());
|
||||
SSDP.setDeviceType(0, "urn:schemas-rstrouse-org:device:ESPSomfyRTS:1");
|
||||
SSDP.setName(0, settings.hostname);
|
||||
|
||||
//SSDP.setSerialNumber(0, "C2496952-5610-47E6-A968-2FC19737A0DB");
|
||||
//SSDP.setUUID(0, settings.uuid);
|
||||
SSDP.setModelName(0, "ESPSomfy RTS");
|
||||
if(strlen(settings.chipModel) == 0) SSDP.setModelNumber(0, "ESP32");
|
||||
else {
|
||||
char sModel[20] = "";
|
||||
snprintf(sModel, sizeof(sModel), "ESP32-%s", settings.chipModel);
|
||||
SSDP.setModelNumber(0, sModel);
|
||||
}
|
||||
SSDP.setModelURL(0, "https://github.com/rstrouse/ESPSomfy-RTS");
|
||||
SSDP.setManufacturer(0, "rstrouse");
|
||||
SSDP.setManufacturerURL(0, "https://github.com/rstrouse");
|
||||
SSDP.setURL(0, "/");
|
||||
SSDP.setActive(0, true);
|
||||
esp_task_wdt_reset();
|
||||
if(MDNS.begin(settings.hostname)) {
|
||||
Serial.printf("MDNS Responder Started: serverId=%s\n", settings.serverId);
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
//MDNS.addServiceTxt("http", "tcp", "board", "ESP32");
|
||||
//MDNS.addServiceTxt("http", "tcp", "model", "ESPSomfyRTS");
|
||||
|
||||
MDNS.addService("espsomfy_rts", "tcp", 8080);
|
||||
MDNS.addServiceTxt("espsomfy_rts", "tcp", "serverId", String(settings.serverId));
|
||||
MDNS.addServiceTxt("espsomfy_rts", "tcp", "model", "ESPSomfyRTS");
|
||||
MDNS.addServiceTxt("espsomfy_rts", "tcp", "version", String(settings.fwVersion.name));
|
||||
}
|
||||
if(settings.ssdpBroadcast) {
|
||||
esp_task_wdt_reset();
|
||||
SSDP.begin();
|
||||
}
|
||||
else if(SSDP.isStarted) SSDP.end();
|
||||
esp_task_wdt_reset();
|
||||
this->emitSockets();
|
||||
settings.printAvailHeap();
|
||||
this->needsBroadcast = true;
|
||||
}
|
||||
bool Network::connectWired() {
|
||||
if(ETH.linkUp()) {
|
||||
// If the ethernet link is re-established then we need to shut down wifi.
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
//sockEmit.end();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
if(this->connType != conn_types_t::ethernet) this->setConnected(conn_types_t::ethernet);
|
||||
return true;
|
||||
}
|
||||
else if(this->ethStarted) {
|
||||
// There is no wired connection so we need to fallback if appropriate.
|
||||
if(settings.connType == conn_types_t::ethernetpref && settings.WIFI.ssid[0] != '\0')
|
||||
return this->connectWiFi();
|
||||
}
|
||||
if(this->connectAttempts > 0) {
|
||||
Serial.printf("Ethernet Connection Lost... %d Reconnecting ", this->connectAttempts);
|
||||
Serial.println(this->mac);
|
||||
}
|
||||
else
|
||||
Serial.println("Connecting to Wired Ethernet");
|
||||
this->_connecting = true;
|
||||
this->connTarget = conn_types_t::ethernet;
|
||||
this->connType = conn_types_t::unset;
|
||||
if(!this->ethStarted) {
|
||||
// Currently the ethernet module will leak memory if you call begin more than once.
|
||||
this->ethStarted = true;
|
||||
WiFi.mode(WIFI_OFF);
|
||||
if(settings.hostname[0] != '\0')
|
||||
ETH.setHostname(settings.hostname);
|
||||
else
|
||||
ETH.setHostname("ESPSomfy-RTS");
|
||||
Serial.print("Set hostname to:");
|
||||
Serial.println(ETH.getHostname());
|
||||
if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) {
|
||||
Serial.println("Ethernet Begin failed");
|
||||
this->ethStarted = false;
|
||||
if(settings.connType == conn_types_t::ethernetpref) {
|
||||
this->wifiFallback = true;
|
||||
return connectWiFi();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if(!settings.IP.dhcp) {
|
||||
if(!ETH.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) {
|
||||
Serial.println("Unable to configure static IP address....");
|
||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
}
|
||||
}
|
||||
else
|
||||
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
}
|
||||
}
|
||||
this->connectStart = millis();
|
||||
return true;
|
||||
}
|
||||
void Network::updateHostname() {
|
||||
if(settings.hostname[0] != '\0' && this->connected()) {
|
||||
if(this->connType == conn_types_t::ethernet &&
|
||||
strcmp(settings.hostname, ETH.getHostname()) != 0) {
|
||||
Serial.printf("Updating host name to %s...\n", settings.hostname);
|
||||
ETH.setHostname(settings.hostname);
|
||||
MDNS.setInstanceName(settings.hostname);
|
||||
SSDP.setName(0, settings.hostname);
|
||||
}
|
||||
else if(strcmp(settings.hostname, WiFi.getHostname()) != 0) {
|
||||
Serial.printf("Updating host name to %s...\n", settings.hostname);
|
||||
WiFi.setHostname(settings.hostname);
|
||||
MDNS.setInstanceName(settings.hostname);
|
||||
SSDP.setName(0, settings.hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
|
||||
if(this->softAPOpened && WiFi.softAPgetStationNum() > 0) {
|
||||
// There is a client connected to the soft AP. We do not want to close out the connection. While both the
|
||||
// Soft AP and a wifi connection can coexist on ESP32 the performance is abysmal.
|
||||
WiFi.disconnect(false);
|
||||
this->_connecting = false;
|
||||
this->connType = conn_types_t::unset;
|
||||
return true;
|
||||
}
|
||||
WiFi.setSleep(false);
|
||||
if(!settings.IP.dhcp) {
|
||||
if(!WiFi.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2))
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
}
|
||||
else
|
||||
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
delay(100);
|
||||
|
||||
if(bssid && channel > 0) {
|
||||
if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0
|
||||
&& WiFi.channel() == channel) {
|
||||
this->disconnected = 0;
|
||||
return true;
|
||||
}
|
||||
this->connTarget = conn_types_t::wifi;
|
||||
this->connType = conn_types_t::unset;
|
||||
Serial.println("WiFi begin...");
|
||||
this->_connecting = true;
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid);
|
||||
this->connectStart = millis();
|
||||
}
|
||||
else if(settings.WIFI.ssid[0] != '\0') {
|
||||
if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0) {
|
||||
// If we are connected to the target SSID then just return.
|
||||
this->disconnected = 0;
|
||||
this->_connecting = true;
|
||||
return true;
|
||||
}
|
||||
if(this->_connecting) return true;
|
||||
this->_connecting = true;
|
||||
this->connTarget = conn_types_t::wifi;
|
||||
this->connType = conn_types_t::unset;
|
||||
if(this->connectAttempts > 0) {
|
||||
Serial.print("Connection Lost...");
|
||||
Serial.print(this->mac);
|
||||
Serial.print(" CH:");
|
||||
Serial.print(this->channel);
|
||||
Serial.print(" (");
|
||||
Serial.print(this->strength);
|
||||
Serial.println("dbm) ");
|
||||
}
|
||||
else Serial.println("Connecting to AP");
|
||||
delay(100);
|
||||
// There is also another method simply called hostname() but this is legacy for esp8266.
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
Serial.print("Set hostname to:");
|
||||
Serial.println(WiFi.getHostname());
|
||||
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
|
||||
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
uint8_t _bssid[6];
|
||||
int32_t _channel = 0;
|
||||
if(!settings.WIFI.hidden && this->getStrongestAP(settings.WIFI.ssid, _bssid, &_channel)) {
|
||||
Serial.printf("Found strongest AP %02X:%02X:%02X:%02X:%02X:%02X CH:%d\n", _bssid[0], _bssid[1], _bssid[2], _bssid[3], _bssid[4], _bssid[5], _channel);
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, _channel, _bssid);
|
||||
}
|
||||
else
|
||||
// If the user has the hidden flag set just connect to whatever the AP gives us.
|
||||
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase);
|
||||
}
|
||||
this->connectStart = millis();
|
||||
return true;
|
||||
}
|
||||
bool Network::connect(conn_types_t ctype) {
|
||||
esp_task_wdt_reset();
|
||||
if(this->connecting()) return true;
|
||||
if(this->disconnectTime == 0) this->disconnectTime = millis();
|
||||
if(ctype == conn_types_t::ethernet && this->connType != conn_types_t::ethernet) {
|
||||
// Here we need to call the connect to ethernet.
|
||||
this->connectWired();
|
||||
}
|
||||
else if(ctype == conn_types_t::ap || (!this->connected() && millis() > this->disconnectTime + CONNECT_TIMEOUT)) {
|
||||
if(!this->softAPOpened && !this->openingSoftAP) {
|
||||
this->disconnectTime = millis();
|
||||
this->openSoftAP();
|
||||
}
|
||||
else if(this->softAPOpened && !this->openingSoftAP &&
|
||||
(ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) {
|
||||
// When thge softAP is open then we need to try to connect to wifi repeatedly if the user connects to a hidden SSID.
|
||||
this->connectWiFi();
|
||||
}
|
||||
}
|
||||
else if((ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) {
|
||||
this->connectWiFi();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
uint32_t Network::getChipId() {
|
||||
uint32_t chipId = 0;
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
for(int i=0; i<17; i=i+8) {
|
||||
chipId |= ((mac >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
return chipId;
|
||||
}
|
||||
bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) {
|
||||
// The new AP must be at least 10dbm greater.
|
||||
int32_t strength = this->connected() ? WiFi.RSSI() + 10 : -127;
|
||||
int32_t chan = -1;
|
||||
memset(bssid, 0x00, 6);
|
||||
esp_task_wdt_delete(NULL);
|
||||
int16_t n = WiFi.scanComplete();
|
||||
//int16_t n = this->connected() ? WiFi.scanComplete() : WiFi.scanNetworks(false, false, false, 300, 0, ssid);
|
||||
esp_task_wdt_add(NULL);
|
||||
for(int16_t i = 0; i < n; i++) {
|
||||
if(WiFi.SSID(i).compareTo(ssid) == 0) {
|
||||
if(WiFi.RSSI(i) > strength) {
|
||||
strength = WiFi.RSSI(i);
|
||||
memcpy(bssid, WiFi.BSSID(i), 6);
|
||||
*channel = chan = WiFi.channel(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
return chan > 0;
|
||||
}
|
||||
bool Network::openSoftAP() {
|
||||
if(this->softAPOpened || this->openingSoftAP) return true;
|
||||
if(this->connected()) WiFi.disconnect(false);
|
||||
this->openingSoftAP = true;
|
||||
Serial.println();
|
||||
Serial.println("Turning the HotSpot On");
|
||||
esp_task_wdt_reset(); // Make sure we do not reboot here.
|
||||
WiFi.softAP(strlen(settings.hostname) > 0 ? settings.hostname : "ESPSomfy RTS", "");
|
||||
delay(200);
|
||||
return true;
|
||||
}
|
||||
bool Network::connected() {
|
||||
if(this->connecting()) return false;
|
||||
else if(this->connType == conn_types_t::unset) return false;
|
||||
else if(this->connType == conn_types_t::wifi) return WiFi.status() == WL_CONNECTED;
|
||||
else if(this->connType == conn_types_t::ethernet) return ETH.linkUp();
|
||||
else return this->connType != conn_types_t::unset;
|
||||
return false;
|
||||
}
|
||||
bool Network::connecting() {
|
||||
if(this->_connecting && millis() > this->connectStart + CONNECT_TIMEOUT) this->_connecting = false;
|
||||
return this->_connecting;
|
||||
}
|
||||
void Network::clearConnecting() { this->_connecting = false; }
|
||||
void Network::networkEvent(WiFiEvent_t event) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_READY: Serial.println("(evt) WiFi interface ready"); break;
|
||||
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
||||
Serial.printf("(evt) Completed scan for access points (%d)\n", WiFi.scanComplete());
|
||||
//Serial.println("(evt) Completed scan for access points");
|
||||
net.lastWifiScan = millis();
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_START:
|
||||
Serial.println("WiFi station mode started");
|
||||
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP: Serial.println("(evt) WiFi clients stopped"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("(evt) Connected to WiFi STA access point"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
Serial.printf("(evt) Disconnected from WiFi STA access point. Connecting: %d\n", net.connecting());
|
||||
net.connType = conn_types_t::unset;
|
||||
net.disconnectTime = millis();
|
||||
net.clearConnecting();
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("(evt) Authentication mode of STA access point has changed"); break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
Serial.print("(evt) Got WiFi STA IP: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
net.connType = conn_types_t::wifi;
|
||||
net.connectTime = millis();
|
||||
net.setConnected(conn_types_t::wifi);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println("Lost IP address and IP address is reset to 0"); break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
// If the Wifi is connected then drop that connection
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||
Serial.print("Got Ethernet IP ");
|
||||
Serial.println(ETH.localIP());
|
||||
net.connectTime = millis();
|
||||
net.connType = conn_types_t::ethernet;
|
||||
if(settings.IP.dhcp) {
|
||||
settings.IP.ip = ETH.localIP();
|
||||
settings.IP.subnet = ETH.subnetMask();
|
||||
settings.IP.gateway = ETH.gatewayIP();
|
||||
settings.IP.dns1 = ETH.dnsIP(0);
|
||||
settings.IP.dns2 = ETH.dnsIP(1);
|
||||
}
|
||||
net.setConnected(conn_types_t::ethernet);
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
Serial.print("(evt) Ethernet Connected ");
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
Serial.println("(evt) Ethernet Disconnected");
|
||||
net.connType = conn_types_t::unset;
|
||||
net.disconnectTime = millis();
|
||||
net.clearConnecting();
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
Serial.println("(evt) Ethernet Started");
|
||||
net.ethStarted = true;
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
Serial.println("(evt) Ethernet Stopped");
|
||||
net.connType = conn_types_t::unset;
|
||||
net.ethStarted = false;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
Serial.print("(evt) WiFi SoftAP Started IP:");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
net.openingSoftAP = false;
|
||||
net.softAPOpened = true;
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
if(!net.openingSoftAP) Serial.println("(evt) WiFi SoftAP Stopped");
|
||||
net.softAPOpened = false;
|
||||
break;
|
||||
default:
|
||||
if(event > ARDUINO_EVENT_ETH_START)
|
||||
Serial.printf("(evt) Unknown Ethernet Event %d\n", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Network::emitHeap(uint8_t num) {
|
||||
bool bEmit = false;
|
||||
bool bTimeEmit = millis() - _lastHeapEmit > 15000;
|
||||
bool bRoomEmit = false;
|
||||
bool bValEmit = false;
|
||||
if(num != 255 || this->needsBroadcast) bEmit = true;
|
||||
if(millis() - _lastHeapEmit > 15000) bTimeEmit = true;
|
||||
uint32_t freeHeap = ESP.getFreeHeap();
|
||||
uint32_t maxHeap = ESP.getMaxAllocHeap();
|
||||
uint32_t minHeap = ESP.getMinFreeHeap();
|
||||
if(abs((int)(freeHeap - _lastHeap)) > 1500) bValEmit = true;
|
||||
if(abs((int)(maxHeap - _lastMaxHeap)) > 1500) bValEmit = true;
|
||||
bRoomEmit = sockEmit.activeClients(0) > 0;
|
||||
if(bValEmit) bTimeEmit = millis() - _lastHeapEmit > 7000;
|
||||
if(bEmit || bTimeEmit || bRoomEmit || bValEmit) {
|
||||
JsonSockEvent *json = sockEmit.beginEmit("memStatus");
|
||||
json->beginObject();
|
||||
json->addElem("max", maxHeap);
|
||||
json->addElem("free", freeHeap);
|
||||
json->addElem("min", minHeap);
|
||||
json->addElem("total", ESP.getHeapSize());
|
||||
json->endObject();
|
||||
if(num == 255 && bTimeEmit && bValEmit) {
|
||||
sockEmit.endEmit(num);
|
||||
_lastHeapEmit = millis();
|
||||
_lastHeap = freeHeap;
|
||||
_lastMaxHeap = maxHeap;
|
||||
//Serial.printf("BROAD HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit);
|
||||
}
|
||||
else if(num != 255) {
|
||||
sockEmit.endEmit(num);
|
||||
//Serial.printf("TARGET HEAP %d: Emit:%d TimeEmit:%d ValEmit:%d\n", num, bEmit, bTimeEmit, bValEmit);
|
||||
}
|
||||
else if(bRoomEmit) {
|
||||
sockEmit.endEmitRoom(0);
|
||||
//Serial.printf("ROOM HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/Network.h
Normal file
59
src/Network.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#include <Arduino.h>
|
||||
|
||||
#ifndef Network_h
|
||||
#define Network_h
|
||||
|
||||
//enum class conn_types_t : byte;
|
||||
|
||||
#define CONNECT_TIMEOUT 20000
|
||||
#define SSID_SCAN_INTERVAL 60000
|
||||
class Network {
|
||||
protected:
|
||||
unsigned long lastEmit = 0;
|
||||
unsigned long lastMDNS = 0;
|
||||
int lastRSSI = 0;
|
||||
int lastChannel = 0;
|
||||
int linkSpeed = 0;
|
||||
bool _connecting = false;
|
||||
public:
|
||||
unsigned long lastWifiScan = 0;
|
||||
bool ethStarted = false;
|
||||
bool wifiFallback = false;
|
||||
bool softAPOpened = false;
|
||||
bool openingSoftAP = false;
|
||||
bool needsBroadcast = true;
|
||||
conn_types_t connType = conn_types_t::unset;
|
||||
conn_types_t connTarget = conn_types_t::unset;
|
||||
bool connected();
|
||||
bool connecting();
|
||||
void clearConnecting();
|
||||
conn_types_t preferredConnType();
|
||||
String ssid;
|
||||
String mac;
|
||||
int channel;
|
||||
int strength;
|
||||
int disconnected = 0;
|
||||
int connectAttempts = 0;
|
||||
uint32_t disconnectTime = 0;
|
||||
uint32_t connectStart = 0;
|
||||
uint32_t connectTime = 0;
|
||||
bool openSoftAP();
|
||||
bool connect(conn_types_t ctype);
|
||||
bool connectWiFi(const uint8_t *bssid = nullptr, const int32_t channel = -1);
|
||||
bool connectWired();
|
||||
void setConnected(conn_types_t connType);
|
||||
bool getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel);
|
||||
bool changeAP(const uint8_t *bssid, const int32_t channel);
|
||||
//int getStrengthByMac(const char *mac);
|
||||
//int getStrengthBySSID(const char *ssid);
|
||||
void updateHostname();
|
||||
bool setup();
|
||||
void loop();
|
||||
void end();
|
||||
void emitSockets();
|
||||
void emitSockets(uint8_t num);
|
||||
void emitHeap(uint8_t num = 255);
|
||||
uint32_t getChipId();
|
||||
static void networkEvent(WiFiEvent_t event);
|
||||
};
|
||||
#endif
|
||||
835
src/SSDP.cpp
Normal file
835
src/SSDP.cpp
Normal file
|
|
@ -0,0 +1,835 @@
|
|||
#include <functional>
|
||||
#include <AsyncUDP.h>
|
||||
#include "Utils.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "SSDP.h"
|
||||
|
||||
|
||||
#define SSDP_PORT 1900
|
||||
#define SSDP_METHOD_SIZE 10
|
||||
#define SSDP_URI_SIZE 2
|
||||
#define SSDP_BUFFER_SIZE 64
|
||||
#define SSDP_MULTICAST_ADDR 239, 255, 255, 250
|
||||
//#define DEBUG_SSDP Serial
|
||||
//#define DEBUG_SSDP_PACKET Serial
|
||||
extern ConfigSettings settings;
|
||||
|
||||
static const char _ssdp_uuid_template[] PROGMEM = "C2496952-5610-47E6-A968-2FC1%02X%02X%02X%02X";
|
||||
static const char _ssdp_serial_number_template[] PROGMEM = "ESP32-%02x%02x%02x";
|
||||
static const char _ssdp_usn_root_template[] PROGMEM = "uuid:%s::upnp:rootdevice";
|
||||
static const char _ssdp_usn_uuid_template[] PROGMEM = "uuid:%s";
|
||||
static const char _ssdp_usn_urn_template[] PROGMEM = "uuid:%s::%s";
|
||||
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_bye_template[] PROGMEM =
|
||||
"NOTIFY * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"NTS: ssdp:byebye\r\n"
|
||||
"NT: %s\r\n"
|
||||
"USN: %s\r\n"
|
||||
"BOOTID.UPNP.ORG: %lu\r\n"
|
||||
"CONFIGID.UPNP.ORG: %d\r\n"
|
||||
"\r\n";
|
||||
static const char _ssdp_packet_template[] PROGMEM =
|
||||
"%s" // _ssdp_response_template / _ssdp_notify_template
|
||||
"CACHE-CONTROL: max-age=%u\r\n" // _interval
|
||||
"SERVER: Arduino/1.0 UPnP/1.1 ESPSomfyRTS/2.0\r\n"
|
||||
"USN: %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
|
||||
"BOOTID.UPNP.ORG: %lu\r\n"
|
||||
"CONFIGID.UPNP.ORG: %d\r\n"
|
||||
"\r\n";
|
||||
static const char _ssdp_device_schema_template[] PROGMEM =
|
||||
"<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>"
|
||||
"<modelDescription>ESPSomfy RTS Controller</modelDescription>"
|
||||
"<firmwareVersion>%s</firmwareVersion>"
|
||||
"<UDN>%s</UDN>"
|
||||
"<serviceList></serviceList>"
|
||||
"<deviceList></deviceList>"
|
||||
"</device>";
|
||||
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"
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
||||
"<specVersion>"
|
||||
"<major>1</major>"
|
||||
"<minor>0</minor>"
|
||||
"</specVersion>"
|
||||
"<URLBase>http://%s:%u/</URLBase>"; // WiFi.localIP(), _port
|
||||
|
||||
|
||||
UPNPDeviceType::UPNPDeviceType() {
|
||||
lastNotified = 0;
|
||||
schemaURL[0] = '\0';
|
||||
uuid[0] = '\0';
|
||||
deviceType[0] = '\0';
|
||||
friendlyName[0] = '\0';
|
||||
serialNumber[0] = '\0';
|
||||
presentationURL[0] = '\0';
|
||||
manufacturer[0] = '\0';
|
||||
manufacturerURL[0] = '\0';
|
||||
modelName[0] = '\0';
|
||||
modelURL[0] = '\0';
|
||||
modelNumber[0] = '\0';
|
||||
m_usn[0] = '\0';
|
||||
isActive = true;
|
||||
}
|
||||
UPNPDeviceType::UPNPDeviceType(const char *deviceType) { this->setDeviceType(deviceType); }
|
||||
UPNPDeviceType::UPNPDeviceType(const char *deviceType, const char *uuid){ this->setDeviceType(deviceType); this->setUUID(uuid); }
|
||||
UPNPDeviceType::UPNPDeviceType(const char *deviceType, const char *uuid, const char*friendlyName) { this->setDeviceType(deviceType); this->setUUID(uuid); this->setName(friendlyName);}
|
||||
void UPNPDeviceType::setSchemaURL(const char *url) { strlcpy(schemaURL, url, sizeof(schemaURL)); }
|
||||
void UPNPDeviceType::setDeviceType(const char *dtype) { strlcpy(this->deviceType, dtype, sizeof(deviceType)); }
|
||||
void UPNPDeviceType::setUUID(const char *id) { snprintf_P(uuid, sizeof(uuid), PSTR("uuid:%s"), id); }
|
||||
void UPNPDeviceType::setName(const char *name) { strlcpy(friendlyName, name, sizeof(friendlyName)); }
|
||||
void UPNPDeviceType::setURL(const char *url) { strlcpy(presentationURL, url, sizeof(presentationURL)); }
|
||||
void UPNPDeviceType::setSerialNumber(const char *sn) { strlcpy(serialNumber, sn, sizeof(serialNumber)); }
|
||||
void UPNPDeviceType::setSerialNumber(const uint32_t sn) { snprintf(serialNumber, sizeof(uint32_t) * 2 + 1, "%08X", sn); }
|
||||
void UPNPDeviceType::setModelName(const char *name) { strlcpy(modelName, name, sizeof(modelName)); }
|
||||
void UPNPDeviceType::setModelNumber(const char *num) { strlcpy(modelNumber, num, sizeof(modelNumber));}
|
||||
void UPNPDeviceType::setModelURL(const char *url) { strlcpy(modelURL, url, sizeof(modelURL)); }
|
||||
void UPNPDeviceType::setManufacturer(const char *name) { strlcpy(manufacturer, name, sizeof(manufacturer)); }
|
||||
void UPNPDeviceType::setManufacturerURL(const char *url) { strlcpy(manufacturerURL, url, sizeof(manufacturerURL)); }
|
||||
//char * UPNPDeviceType::getUSN() { return this->getUSN(this->deviceType); }
|
||||
char * UPNPDeviceType::getUSN(response_types_t responseType) {
|
||||
switch(responseType) {
|
||||
case response_types_t::root:
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_root_template, this->uuid);
|
||||
break;
|
||||
case response_types_t::deviceType:
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) -1, _ssdp_usn_urn_template, this->uuid, this->deviceType);
|
||||
break;
|
||||
default:
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_uuid_template, this->uuid);
|
||||
break;
|
||||
}
|
||||
return this->m_usn;
|
||||
}
|
||||
char * UPNPDeviceType::getUSN(const char *st) {
|
||||
//#ifdef DEBUG_SSDP
|
||||
//DEBUG_SSDP.print("GETUSN ST: ");
|
||||
//DEBUG_SSDP.println(st);
|
||||
//DEBUG_SSDP.print("GETUSN UUID: ");
|
||||
//DEBUG_SSDP.println(this->uuid);
|
||||
//DEBUG_SSDP.print("sizeof(this->m_usn)");
|
||||
//DEBUG_SSDP.println(sizeof(this->m_usn));
|
||||
//#endif
|
||||
if(strncmp("upnp:rootdevice", st, strlen(st)) == 0) {
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_root_template, this->uuid);
|
||||
}
|
||||
else if(strncmp("uuid:", st, 5) == 0)
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_uuid_template, this->uuid);
|
||||
else if(strncmp("urn:", st, 4) == 0)
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) -1, _ssdp_usn_urn_template, this->uuid, this->deviceType);
|
||||
else {
|
||||
snprintf_P(this->m_usn, sizeof(this->m_usn) - 1, _ssdp_usn_uuid_template, this->uuid);
|
||||
}
|
||||
//#ifdef DEBUG_SSDP
|
||||
//DEBUG_SSDP.print("RESUSN UUID: ");
|
||||
//DEBUG_SSDP.println(this->m_usn);
|
||||
//#endif
|
||||
return this->m_usn;
|
||||
}
|
||||
void UPNPDeviceType::setChipId(uint32_t chipId) {
|
||||
snprintf_P(this->uuid, sizeof(this->uuid), _ssdp_uuid_template,
|
||||
0x40,
|
||||
(uint16_t)((chipId >> 16) & 0xff),
|
||||
(uint16_t)((chipId >> 8) & 0xff),
|
||||
(uint16_t)chipId & 0xff);
|
||||
snprintf_P(this->serialNumber, sizeof(this->serialNumber),
|
||||
_ssdp_serial_number_template,
|
||||
(uint16_t)((chipId >> 16) & 0xff),
|
||||
(uint16_t)((chipId >> 8) & 0xff),
|
||||
(uint16_t)chipId & 0xff);
|
||||
}
|
||||
SSDPClass::SSDPClass():sendQueue{false, INADDR_NONE, 0, nullptr, false, 0, "", response_types_t::root} {}
|
||||
SSDPClass::~SSDPClass() { end(); this->isStarted = false; }
|
||||
bool SSDPClass::begin() {
|
||||
for(int i = 0; i < SSDP_QUEUE_SIZE; i++) {
|
||||
this->sendQueue[i].waiting = false;
|
||||
}
|
||||
if(this->_server.connected()) this->end();
|
||||
//assert(NULL == _server);
|
||||
if(_server.connected()) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println(PSTR("Already connected to SSDP."));
|
||||
this->isStarted = true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
this->bootId = Timestamp::epoch();
|
||||
if(this->bootId < 1000) {
|
||||
this->isStarted = false;
|
||||
return false;
|
||||
}
|
||||
this->configId = (settings.fwVersion.major * 100) + (settings.fwVersion.minor * 10) + settings.fwVersion.build;
|
||||
_server.onPacket([](void * arg, AsyncUDPPacket& packet) { ((SSDPClass*)(arg))->_processRequest(packet); }, this);
|
||||
if(!_server.listenMulticast(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT)) {
|
||||
#ifdef DEBUG_SSDP
|
||||
IPAddress mcast(SSDP_MULTICAST_ADDR);
|
||||
DEBUG_SSDP.println(PSTR("Error starting SSDP listener on: "));
|
||||
DEBUG_SSDP.print(mcast);
|
||||
DEBUG_SSDP.print(":");
|
||||
DEBUG_SSDP.println(SSDP_PORT);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
Serial.printf("SSDP: %s - %s\n", this->deviceTypes[i].deviceType, this->deviceTypes[i].isActive ? "true" : "false");
|
||||
}
|
||||
this->isStarted = true;
|
||||
this->_sendByeBye();
|
||||
this->_sendNotify();
|
||||
Serial.println("Connected to SSDP...");
|
||||
return true;
|
||||
}
|
||||
void SSDPClass::end() {
|
||||
if(!this->_server || !this->_server.connected()) return; // server isn't connected nothing to do
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.printf(PSTR("SSDP end ...\n "));
|
||||
#endif
|
||||
if(this->_server.connected()) {
|
||||
this->_sendByeBye();
|
||||
this->_server.close();
|
||||
Serial.println("Disconnected from SSDP...");
|
||||
}
|
||||
this->isStarted = false;
|
||||
// Clear out the last notified so if the user starts us up again it will notify
|
||||
// that we exist again.
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
this->deviceTypes[i].lastNotified = 0;
|
||||
}
|
||||
}
|
||||
UPNPDeviceType* SSDPClass::getDeviceType(uint8_t ndx) { if(ndx < this->m_cdeviceTypes) return &this->deviceTypes[ndx]; return nullptr; }
|
||||
UPNPDeviceType* SSDPClass::findDeviceByType(char *devType) {
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
if (strcasecmp(devType, this->deviceTypes[i].deviceType) == 0) return &this->deviceTypes[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
UPNPDeviceType* SSDPClass::findDeviceByUUID(char *uuid) {
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
if (strcasecmp(uuid, this->deviceTypes[i].uuid) == 0) return &this->deviceTypes[i];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool SSDPClass::_startsWith(const char *sw, const char *str) { return strncmp(sw, str, strlen(sw)) == 0; }
|
||||
void SSDPClass::_parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p) {
|
||||
//MSEARCH = "M-SEARCH * HTTP/1.1\nHost: 239.255.255.250:1900\nMan: \"ssdp:discover\"\nST: roku:ecp\n";
|
||||
memset(pkt, 0x00, sizeof(ssdp_packet_t));
|
||||
pkt->method = NONE;
|
||||
pkt->type = UNKNOWN;
|
||||
pkt->recvd = millis();
|
||||
pkt->valid = true;
|
||||
char buffer[SSDP_BUFFER_SIZE] = {0};
|
||||
uint8_t pos = 0;
|
||||
while(p.available() > 0) {
|
||||
char c = p.read();
|
||||
if(pkt->method == NONE) {
|
||||
if(c == ' ') {
|
||||
_trim(buffer);
|
||||
pos = 0;
|
||||
if(strcmp(buffer, "M-SEARCH") == 0) pkt->method = SEARCH;
|
||||
else if(strcmp(buffer, "NOTIFY") == 0) pkt->method = NOTIFY;
|
||||
}
|
||||
else {
|
||||
if(pos < SSDP_BUFFER_SIZE - 1) {
|
||||
buffer[pos++] = c;
|
||||
buffer[pos] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(c == '\r' || c == '\n') {
|
||||
_trim(buffer);
|
||||
if(strcasecmp(buffer, "* HTTP/1.1") != 0) pkt->valid = false;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
if(pos < SSDP_BUFFER_SIZE) {
|
||||
buffer[pos++] = c;
|
||||
buffer[pos] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
if(c == ' ' && pkt->method == NONE) {
|
||||
pkt->valid = false;
|
||||
break; // We didn't get a method we want.
|
||||
}
|
||||
}
|
||||
enum {KEY, HOST, MAN, ST, MX, AGENT, HEAD, SERVER, NTS, LOCATION, USN, NT} keys;
|
||||
keys = KEY;
|
||||
if(pkt->method == SEARCH) {
|
||||
// Get all the key:value pairs
|
||||
pos = 0;
|
||||
buffer[0] = '\0';
|
||||
while(p.available() > 0) {
|
||||
char c = p.read();
|
||||
if(keys == KEY) {
|
||||
if(c == ':') {
|
||||
_trim(buffer);
|
||||
if(strcasecmp(buffer, "MAN") == 0) keys = MAN;
|
||||
else if(strcasecmp(buffer, "ST") == 0) keys = ST;
|
||||
else if(strcasecmp(buffer, "MX") == 0) keys = MX;
|
||||
else if(strcasecmp(buffer, "HOST") == 0) keys = HOST;
|
||||
else if(strcasecmp(buffer, "USER-AGENT") == 0) keys = AGENT;
|
||||
else if(strcasecmp(buffer, "SERVER") == 0) keys = SERVER;
|
||||
else if(strcasecmp(buffer, "NTS") == 0) keys = NTS;
|
||||
else if(strcasecmp(buffer, "NT") == 0) keys = NT;
|
||||
else if(strcasecmp(buffer, "LOCATION") == 0) keys = LOCATION;
|
||||
else if(strcasecmp(buffer, "USN") == 0) keys = USN;
|
||||
else keys = HEAD;
|
||||
pos = 0;
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
else if(c == '\r' || c == '\n') {
|
||||
// If we find a key that ends before a : then we need to bugger out.
|
||||
keys = KEY;
|
||||
pos = 0;
|
||||
buffer[0] = '\0';
|
||||
//break;
|
||||
}
|
||||
else {
|
||||
if(pos < SSDP_BUFFER_SIZE - 1) {
|
||||
buffer[pos++] = c;
|
||||
buffer[pos] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We are reading a value
|
||||
if(c == '\r' || c == '\n') {
|
||||
_trim(buffer);
|
||||
switch(keys) {
|
||||
case HOST:
|
||||
if(strcasecmp(buffer, "239.255.255.250:1900") == 0) pkt->type = MULTICAST;
|
||||
else pkt->type = UNICAST;
|
||||
strcpy(pkt->host, buffer);
|
||||
break;
|
||||
case MAN:
|
||||
strcpy(pkt->man, buffer);
|
||||
break;
|
||||
case ST:
|
||||
strcpy(pkt->st, buffer);
|
||||
break;
|
||||
case MX:
|
||||
pkt->mx = atoi(buffer);
|
||||
break;
|
||||
case AGENT:
|
||||
strcpy(pkt->agent, buffer);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
keys = KEY;
|
||||
pos = 0;
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
else {
|
||||
if(pos < SSDP_BUFFER_SIZE - 1) {
|
||||
buffer[pos++] = c;
|
||||
buffer[pos] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(pkt->valid) {
|
||||
if(pkt->method == NONE) pkt->valid = false;
|
||||
if(pkt->method == SEARCH) {
|
||||
if(strcmp(pkt->man, "ssdp:discover") != 0) pkt->valid = false;
|
||||
if(strlen(pkt->st) == 0) pkt->valid = false;
|
||||
else if(strcmp(pkt->st, "ssdp:all") != 0 &&
|
||||
strcmp(pkt->st, "upnp:rootdevice") != 0 &&
|
||||
!this->_startsWith("uuid:", pkt->st) &&
|
||||
!this->_startsWith("urn:", pkt->st))
|
||||
pkt->valid = false;
|
||||
if(pkt->type == UNICAST) {
|
||||
if(strlen(pkt->host) == 0) pkt->valid = false;
|
||||
}
|
||||
else if(pkt->type == MULTICAST) {
|
||||
if(pkt->mx == 0 || isnan(pkt->mx)) pkt->valid = false;
|
||||
}
|
||||
}
|
||||
else if(pkt->method == NOTIFY) {
|
||||
// This is somebody else's notify so we should blow it off.
|
||||
}
|
||||
else {
|
||||
pkt->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
IPAddress SSDPClass::localIP()
|
||||
{
|
||||
// Make sure we don't get a null IPAddress.
|
||||
tcpip_adapter_ip_info_t ip;
|
||||
if (WiFi.getMode() == WIFI_STA) {
|
||||
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
|
||||
return IPAddress();
|
||||
}
|
||||
} else if (WiFi.getMode() == WIFI_OFF) {
|
||||
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
|
||||
return IPAddress();
|
||||
}
|
||||
}
|
||||
return IPAddress(ip.ip.addr);
|
||||
}
|
||||
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType) {
|
||||
char buffer[1460];
|
||||
IPAddress ip = this->localIP();
|
||||
char *pbuff = (char *)malloc(strlen_P(_ssdp_response_template)+1);
|
||||
if(!pbuff) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("Out of memory for SSDP response");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
strcpy_P(pbuff, _ssdp_response_template);
|
||||
|
||||
// Don't use ip.toString as this fragments the heap like no tomorrow.
|
||||
snprintf_P(buffer, sizeof(buffer)-1,
|
||||
_ssdp_packet_template,
|
||||
pbuff,
|
||||
this->_interval,
|
||||
d->getUSN(responseType),
|
||||
"ST", st,
|
||||
ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL,
|
||||
this->bootId, this->configId);
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
this->_sendResponse(addr, port, buffer);
|
||||
free(pbuff);
|
||||
}
|
||||
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, const char *buff) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print("Sending Response to ");
|
||||
DEBUG_SSDP.print(IPAddress(addr));
|
||||
DEBUG_SSDP.print(":");
|
||||
DEBUG_SSDP.println(port);
|
||||
DEBUG_SSDP.println(buff);
|
||||
#endif
|
||||
|
||||
_server.writeTo((const uint8_t *)buff, strlen(buff), addr, port);
|
||||
}
|
||||
void SSDPClass::_sendNotify() {
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[i];
|
||||
if(i == 0 && (strlen(dev->deviceType) == 0 || !dev->isActive)) Serial.printf("The device type is empty: %s\n", dev->isActive ? "true" : "false");
|
||||
if(strlen(dev->deviceType) > 0 && dev->isActive) {
|
||||
unsigned long elapsed = (millis() - dev->lastNotified);
|
||||
if(!dev->lastNotified || (elapsed * 5) > (this->_interval * 1000)) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print(dev->deviceType);
|
||||
DEBUG_SSDP.print(" Time since last notified: ");
|
||||
DEBUG_SSDP.print(elapsed/1000);
|
||||
DEBUG_SSDP.print("sec ");
|
||||
DEBUG_SSDP.print(this->_interval);
|
||||
DEBUG_SSDP.println("sec");
|
||||
#endif
|
||||
this->_sendNotify(dev, i == 0);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print(dev->deviceType);
|
||||
DEBUG_SSDP.print(" Time since last notified: ");
|
||||
DEBUG_SSDP.print(elapsed/1000);
|
||||
DEBUG_SSDP.print("sec ");
|
||||
DEBUG_SSDP.print(this->_interval);
|
||||
DEBUG_SSDP.println("sec -- SKIPPING");
|
||||
#endif
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSDPClass::_sendNotify(const char *msg) {
|
||||
//_server->append(msg, strlen(msg));
|
||||
//_server->send(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT);
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("--------------- SEND NOTIFY PACKET ----------------");
|
||||
DEBUG_SSDP.println(msg);
|
||||
#endif
|
||||
_server.writeTo((uint8_t *)msg, strlen(msg), IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT);
|
||||
}
|
||||
void SSDPClass::_sendNotify(UPNPDeviceType *d) { this->_sendNotify(d, strcmp(d->deviceType, this->deviceTypes[0].deviceType) == 0); }
|
||||
void SSDPClass::_sendNotify(UPNPDeviceType *d, bool root) {
|
||||
char buffer[1460];
|
||||
IPAddress ip = this->localIP();
|
||||
/*
|
||||
"NOTIFY * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"NTS: ssdp:alive\r\n";
|
||||
"CACHE-CONTROL: max-age=%u\r\n" // _interval
|
||||
"SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber
|
||||
"USN: %s\r\n" // _uuid
|
||||
"%s: %s\r\n" // "NT" or "ST", _deviceType
|
||||
"LOCATION: http://%s:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
|
||||
*/
|
||||
char *pbuff = (char *)malloc(strlen_P(_ssdp_notify_template)+1);
|
||||
if(!pbuff) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println(PSTR("Out of memory for SSDP response"));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
strcpy_P(pbuff, _ssdp_notify_template);
|
||||
if(root) {
|
||||
// Send 1 for root
|
||||
snprintf_P(buffer, sizeof(buffer),
|
||||
_ssdp_packet_template,
|
||||
pbuff,
|
||||
this->_interval,
|
||||
d->getUSN(response_types_t::root), // USN
|
||||
"NT", "upnp:rootdevice",
|
||||
ip[0], ip[1], ip[2], ip[3], this->_port, d->schemaURL, this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
}
|
||||
// Send 1 for uuid
|
||||
snprintf_P(buffer, sizeof(buffer),
|
||||
_ssdp_packet_template,
|
||||
pbuff,
|
||||
this->_interval,
|
||||
d->getUSN(response_types_t::uuid),
|
||||
"NT", d->getUSN(response_types_t::uuid),
|
||||
ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
// Send 1 for deviceType
|
||||
snprintf_P(buffer, sizeof(buffer),
|
||||
_ssdp_packet_template,
|
||||
pbuff,
|
||||
this->_interval,
|
||||
d->getUSN(response_types_t::deviceType),
|
||||
"NT", d->deviceType,
|
||||
ip[0], ip[1], ip[2], ip[3], _port, d->schemaURL, this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
d->lastNotified = millis();
|
||||
free(pbuff);
|
||||
}
|
||||
void SSDPClass::setActive(uint8_t ndx, bool isActive) {
|
||||
UPNPDeviceType *d = &this->deviceTypes[ndx];
|
||||
if(!isActive) {
|
||||
if(this->isStarted) this->_sendByeBye(d, ndx == 0);
|
||||
d->isActive = false;
|
||||
}
|
||||
else {
|
||||
d->isActive = true;
|
||||
d->lastNotified = 0;
|
||||
if(this->isStarted) this->_sendNotify(d, ndx == 0);
|
||||
}
|
||||
}
|
||||
void SSDPClass::_sendByeBye() {
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[i];
|
||||
if(strlen(dev->deviceType) > 0) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print(dev->deviceType);
|
||||
DEBUG_SSDP.print(" ");
|
||||
DEBUG_SSDP.print(this->_interval);
|
||||
DEBUG_SSDP.println("sec");
|
||||
#endif
|
||||
this->_sendByeBye(dev, i == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSDPClass::_sendByeBye(const char *msg) {
|
||||
//_server->append(msg, strlen(msg));
|
||||
//_server->send(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT);
|
||||
_server.writeTo((uint8_t *)msg, strlen(msg), IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT);
|
||||
}
|
||||
void SSDPClass::_sendByeBye(UPNPDeviceType *d) { this->_sendByeBye(d, strcmp(d->deviceType, this->deviceTypes[0].deviceType) == 0); }
|
||||
void SSDPClass::_sendByeBye(UPNPDeviceType *d, bool root) {
|
||||
char buffer[1460];
|
||||
IPAddress ip = this->localIP();
|
||||
/*
|
||||
"NOTIFY * HTTP/1.1\r\n"
|
||||
"HOST: 239.255.255.250:1900\r\n"
|
||||
"NTS: ssdp:byebye\r\n";
|
||||
"CACHE-CONTROL: max-age=%u\r\n" // _interval
|
||||
"SERVER: Arduino/1.0 UPNP/1.1 %s/%s\r\n" // _modelName, _modelNumber
|
||||
"USN: %s\r\n" // _uuid
|
||||
"%s: %s\r\n" // "NT" or "ST", _deviceType
|
||||
"LOCATION: http://%s:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
|
||||
*/
|
||||
if(root) {
|
||||
// Send 1 for root
|
||||
snprintf_P(buffer, sizeof(buffer)-1,
|
||||
_ssdp_bye_template,
|
||||
"upnp:rootdevice", d->getUSN(response_types_t::root), this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
}
|
||||
// Send 1 for uuid
|
||||
snprintf_P(buffer, sizeof(buffer)-1,
|
||||
_ssdp_bye_template,
|
||||
d->getUSN(response_types_t::uuid),
|
||||
d->getUSN(response_types_t::uuid), this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
// Send 1 for deviceType
|
||||
snprintf_P(buffer, sizeof(buffer)-1,
|
||||
_ssdp_bye_template,
|
||||
d->deviceType,
|
||||
d->getUSN(response_types_t::deviceType), this->bootId, this->configId);
|
||||
this->_sendNotify(buffer);
|
||||
}
|
||||
void SSDPClass::_addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType, uint8_t sec) {
|
||||
/*
|
||||
typedef struct ssdp_response_t {
|
||||
IPAddress address;
|
||||
uint16_t port;
|
||||
UPNPDeviceType *dev;
|
||||
bool sendUUID;
|
||||
unsigned long sendTime;
|
||||
char st[SSDP_DEVICE_TYPE_SIZE];
|
||||
};
|
||||
*/
|
||||
for(uint8_t i = 0; i < SSDP_QUEUE_SIZE; i++) {
|
||||
if(this->sendQueue[i].waiting) {
|
||||
// Check to see if this is a reply to the same place.
|
||||
ssdp_response_t *q = &this->sendQueue[i];
|
||||
if(q->address == addr && q->port == port && q->responseType == responseType) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.printf("There is already a response to this query in slot %u\n", i);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find an empty queue slot and fill it.
|
||||
for(uint8_t i = 0; i < SSDP_QUEUE_SIZE; i++) {
|
||||
if(!this->sendQueue[i].waiting) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.printf("Queueing SSDP response in slot %u\n", i);
|
||||
DEBUG_SSDP.println("*********************************");
|
||||
#endif
|
||||
ssdp_response_t *q = &this->sendQueue[i];
|
||||
q->dev = d;
|
||||
q->sendTime = millis() + (random(0, sec - 1) * 1000L);
|
||||
q->address = addr;
|
||||
q->port = port;
|
||||
q->responseType = responseType;
|
||||
strlcpy(q->st, st, sizeof(ssdp_response_t::st)-1);
|
||||
q->waiting = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we made it here then there were was not space available on the queue.
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("The SSDP response queue was full. Dropping request");
|
||||
#endif
|
||||
}
|
||||
void SSDPClass::_sendQueuedResponses() {
|
||||
for(uint8_t i = 0; i < SSDP_QUEUE_SIZE; i++) {
|
||||
if(this->sendQueue[i].waiting) {
|
||||
ssdp_response_t *q = &this->sendQueue[i];
|
||||
if(q->sendTime < millis()) {
|
||||
// Send the response and delete the pointer.
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.print("Sending SSDP queued response ");
|
||||
DEBUG_SSDP.println(i);
|
||||
#endif
|
||||
this->_sendResponse(q->address, q->port, q->dev, q->st, q->responseType);
|
||||
q->waiting = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSDPClass::_printPacket(ssdp_packet_t *pkt) {
|
||||
Serial.printf("Rec: %lu\n", pkt->recvd);
|
||||
switch(pkt->method) {
|
||||
case NONE:
|
||||
Serial.println("Method: NONE");
|
||||
break;
|
||||
case SEARCH:
|
||||
Serial.println("Method: SEARCH");
|
||||
break;
|
||||
case NOTIFY:
|
||||
Serial.println("Method: NOTIFY");
|
||||
break;
|
||||
default:
|
||||
Serial.println("Method: UNKOWN");
|
||||
break;
|
||||
}
|
||||
Serial.printf("ST: %s\n", pkt->st);
|
||||
Serial.printf("MAN: %s\n", pkt->man);
|
||||
Serial.printf("AGENT: %s\n", pkt->agent);
|
||||
Serial.printf("HOST: %s\n", pkt->host);
|
||||
Serial.printf("MX: %d\n", pkt->mx);
|
||||
Serial.printf("type: %d\n", pkt->type);
|
||||
Serial.printf("valid: %d\n", pkt->valid);
|
||||
}
|
||||
void SSDPClass::_processRequest(AsyncUDPPacket &p) {
|
||||
// This pending BS should probably be for unicast request only but we will play along for now.
|
||||
if(!this->_server.connected()) return;
|
||||
if(p.length() == 0) {
|
||||
//this->_sendQueuedResponses();
|
||||
return;
|
||||
}
|
||||
ssdp_packet_t pkt;
|
||||
this->_parsePacket(&pkt, p);
|
||||
if(pkt.valid && pkt.method == SEARCH) {
|
||||
// Check to see if we have anything to respond to from this packet.
|
||||
if(strcmp("ssdp:all", pkt.st) == 0) {
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("--------------- ALL ---------------------");
|
||||
this->_printPacket(&pkt);
|
||||
#endif
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[i];
|
||||
if(strlen(this->deviceTypes[i].deviceType) > 0) {
|
||||
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::root, pkt.mx);
|
||||
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::uuid, pkt.mx);
|
||||
this->_addToSendQueue(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::deviceType, pkt.mx);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(strcmp("upnp:rootdevice", pkt.st) == 0) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[0];
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("--------------- ROOT ---------------------");
|
||||
this->_printPacket(&pkt);
|
||||
#endif
|
||||
if(pkt.type == MULTICAST)
|
||||
this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, response_types_t::root, pkt.mx);
|
||||
else
|
||||
this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, response_types_t::root);
|
||||
}
|
||||
else {
|
||||
UPNPDeviceType *dev = nullptr;
|
||||
bool useUUID = false;
|
||||
if(this->_startsWith("uuid:", pkt.st)) {
|
||||
dev = this->findDeviceByUUID(pkt.st);
|
||||
useUUID = true;
|
||||
}
|
||||
else if(this->_startsWith("urn:", pkt.st)) { dev = this->findDeviceByType(pkt.st); }
|
||||
if(dev) {
|
||||
#ifdef DEBUG_SSDP
|
||||
this->_printPacket(&pkt);
|
||||
DEBUG_SSDP.println("-------------- ACCEPT --------------------");
|
||||
#endif
|
||||
if(pkt.type == MULTICAST)
|
||||
this->_addToSendQueue(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, dev, pkt.st, useUUID ? response_types_t::uuid : response_types_t::root, pkt.mx);
|
||||
else {
|
||||
this->_sendResponse(p.remoteIP(), p.remotePort(), dev, pkt.st, useUUID ? response_types_t::uuid : response_types_t::root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void SSDPClass::loop() {
|
||||
this->_sendQueuedResponses();
|
||||
// We need to send the notify if required.
|
||||
this->_sendNotify();
|
||||
}
|
||||
void SSDPClass::schema(Print &client) {
|
||||
IPAddress ip = this->localIP();
|
||||
uint8_t devCount = 0;
|
||||
for(uint8_t i = 0; i < this->m_cdeviceTypes; i++) {
|
||||
if(this->deviceTypes[i].deviceType && strlen(this->deviceTypes[i].deviceType) > 0) devCount++;
|
||||
}
|
||||
char schema_template[strlen_P(_ssdp_schema_template)+1];
|
||||
char device_template[strlen_P(_ssdp_device_schema_template)+1];
|
||||
strcpy_P(schema_template, _ssdp_schema_template);
|
||||
strcpy_P(device_template, _ssdp_device_schema_template);
|
||||
char buff[sizeof(device_template) + sizeof(UPNPDeviceType)];
|
||||
buff[0] = '\0';
|
||||
client.printf(schema_template,
|
||||
ip.toString().c_str(), _port);
|
||||
UPNPDeviceType *r = &this->deviceTypes[0];
|
||||
sprintf(buff, device_template,
|
||||
r->deviceType,
|
||||
r->friendlyName,
|
||||
r->presentationURL,
|
||||
r->serialNumber,
|
||||
r->modelName,
|
||||
r->modelNumber,
|
||||
r->modelURL,
|
||||
r->manufacturer,
|
||||
r->manufacturerURL,
|
||||
settings.fwVersion.name,
|
||||
r->uuid );
|
||||
char *devList = strstr(buff, "</device>") - strlen("</deviceList>");
|
||||
devList[0] = '\0';
|
||||
client.print(buff);
|
||||
for(uint8_t i = 1; i < this->m_cdeviceTypes; i++) {
|
||||
UPNPDeviceType *dev = &this->deviceTypes[i];
|
||||
if(strlen(dev->deviceType) > 0) {
|
||||
//Serial.print(devList);
|
||||
client.printf(device_template,
|
||||
dev->deviceType,
|
||||
dev->friendlyName,
|
||||
dev->presentationURL,
|
||||
dev->serialNumber,
|
||||
dev->modelName,
|
||||
dev->modelNumber,
|
||||
dev->modelURL,
|
||||
dev->manufacturer,
|
||||
dev->manufacturerURL,
|
||||
settings.fwVersion.name,
|
||||
dev->uuid);
|
||||
}
|
||||
}
|
||||
client.print(F("</deviceList></device></root>\r\n"));
|
||||
#ifdef DEBUG_SSDP
|
||||
DEBUG_SSDP.println("Sending upnp.xml");
|
||||
#endif
|
||||
}
|
||||
/*
|
||||
void SSDPClass::setDeviceType(uint8_t ndx, UPNPDeviceType *dt) {
|
||||
this->setDeviceType(ndx, dt->deviceType);
|
||||
this->setName(ndx, dt->friendlyName);
|
||||
this->setURL(ndx, dt->presentationURL);
|
||||
this->setSerialNumber(ndx, dt->serialNumber);
|
||||
this->setUUID(ndx, dt->uuid);
|
||||
this->setModelName(ndx, dt->modelName);
|
||||
this->setModelNumber(ndx, dt->modelNumber);
|
||||
this->setManufacturer(ndx, dt->manufacturer);
|
||||
this->setManufacturerURL(ndx, dt->manufacturerURL);
|
||||
}
|
||||
*/
|
||||
void SSDPClass::setTTL(const uint8_t ttl) { _ttl = ttl; }
|
||||
void SSDPClass::setHTTPPort(uint16_t port) { _port = port; }
|
||||
void SSDPClass::setInterval(uint32_t interval) { _interval = interval; }
|
||||
void SSDPClass::setDeviceType(uint8_t ndx, const char *type) { this->deviceTypes[ndx].setDeviceType(type); }
|
||||
void SSDPClass::setSchemaURL(uint8_t ndx, const char *url) { this->deviceTypes[ndx].setSchemaURL(url); }
|
||||
void SSDPClass::setUUID(uint8_t ndx, const char *uuid) { this->deviceTypes[ndx].setUUID(uuid); }
|
||||
void SSDPClass::setUUID(uint8_t ndx, const char *prefix, uint32_t id) {
|
||||
char svcuuid[50];
|
||||
sprintf(svcuuid, "%s%02X%02X%02X",
|
||||
prefix,
|
||||
(uint16_t)((id >> 16) & 0xff),
|
||||
(uint16_t)((id >> 8) & 0xff),
|
||||
(uint16_t)id & 0xff);
|
||||
this->setUUID(ndx, svcuuid);
|
||||
}
|
||||
void SSDPClass::setName(uint8_t ndx, const char *name) { this->deviceTypes[ndx].setName(name); }
|
||||
void SSDPClass::setURL(uint8_t ndx, const char *url) { this->deviceTypes[ndx].setURL(url); }
|
||||
void SSDPClass::setSerialNumber(uint8_t ndx, const char *serialNumber) { this->deviceTypes[ndx].setSerialNumber(serialNumber); }
|
||||
//void SSDPClass::setSerialNumber(uint8_t ndx, const uint32_t serialNumber) { this->deviceTypes[ndx].setSerialNumber(serialNumber); }
|
||||
void SSDPClass::setModelName(uint8_t ndx, const char *name) { this->deviceTypes[ndx].setModelName(name); }
|
||||
void SSDPClass::setModelNumber(uint8_t ndx, const char *num) { this->deviceTypes[ndx].setModelNumber(num); }
|
||||
void SSDPClass::setModelURL(uint8_t ndx, const char *url) { this->deviceTypes[ndx].setModelURL(url); }
|
||||
void SSDPClass::setManufacturer(uint8_t ndx, const char *name) { this->deviceTypes[ndx].setManufacturer(name); }
|
||||
void SSDPClass::setManufacturerURL(uint8_t ndx, const char *url) { this->deviceTypes[ndx].setManufacturerURL(url); }
|
||||
void SSDPClass::setChipId(uint8_t ndx, uint32_t chipId) { this->deviceTypes[ndx].setChipId(chipId); }
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP)
|
||||
SSDPClass SSDP;
|
||||
#endif
|
||||
183
src/SSDP.h
Normal file
183
src/SSDP.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#ifndef SSDP_h
|
||||
#define SSDP_h
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <AsyncUDP.h>
|
||||
|
||||
#define SSDP_UUID_SIZE 42
|
||||
#define SSDP_SCHEMA_URL_SIZE 64
|
||||
#define SSDP_DEVICE_TYPE_SIZE 64
|
||||
#define SSDP_SERIAL_NUMBER_SIZE 37
|
||||
#define SSDP_PRESENTATION_URL_SIZE 64
|
||||
#define SSDP_MODEL_NAME_SIZE 64
|
||||
#define SSDP_MODEL_URL_SIZE 64
|
||||
#define SSDP_MODEL_VERSION_SIZE 32
|
||||
#define SSDP_MANUFACTURER_SIZE 64
|
||||
#define SSDP_USN_SIZE 128
|
||||
#define SSDP_MANUFACTURER_URL_SIZE 64
|
||||
#define SSDP_FRIENDLY_NAME_SIZE 64
|
||||
#define SSDP_INTERVAL_SECONDS 1800
|
||||
#define SSDP_MULTICAST_TTL 2
|
||||
#define SSDP_HTTP_PORT 80
|
||||
#define SSDP_CHILD_DEVICES 0
|
||||
#define SSDP_QUEUE_SIZE 5
|
||||
|
||||
//#define DEBUG_SSDP Serial
|
||||
//#define DEBUG_SSDP_PACKET Serial
|
||||
|
||||
typedef enum {
|
||||
NONE,
|
||||
SEARCH,
|
||||
NOTIFY
|
||||
} ssdp_method_t;
|
||||
typedef enum {
|
||||
root,
|
||||
uuid,
|
||||
deviceType
|
||||
} response_types_t;
|
||||
|
||||
#define SSDP_FIELD_LEN 128
|
||||
#define SSDP_LOCATION_LEN 256
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN,
|
||||
MULTICAST,
|
||||
UNICAST
|
||||
} ssdp_req_types_t;
|
||||
struct ssdp_packet_t {
|
||||
bool pending;
|
||||
unsigned long recvd;
|
||||
ssdp_method_t method;
|
||||
char req [SSDP_FIELD_LEN];
|
||||
char st [SSDP_FIELD_LEN];
|
||||
char man [SSDP_FIELD_LEN];
|
||||
char agent [SSDP_FIELD_LEN];
|
||||
char host [SSDP_LOCATION_LEN];
|
||||
uint8_t mx;
|
||||
ssdp_req_types_t type;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
class UPNPDeviceType {
|
||||
char m_usn[SSDP_USN_SIZE];
|
||||
public:
|
||||
UPNPDeviceType();
|
||||
UPNPDeviceType(const char *deviceType);
|
||||
UPNPDeviceType(const String& deviceType) { UPNPDeviceType(deviceType.c_str()); }
|
||||
UPNPDeviceType(const String& deviceType, const String& uuid);
|
||||
UPNPDeviceType(const char *deviceType, const char *uuid, const char *friendlyName);
|
||||
UPNPDeviceType(const char *deviceType, const char *uuid);
|
||||
UPNPDeviceType(const String& deviceType, const String& uuid, const String& friendlyName) { UPNPDeviceType(deviceType.c_str(), uuid.c_str(), friendlyName.c_str()); }
|
||||
unsigned long lastNotified;
|
||||
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];
|
||||
bool isActive;
|
||||
void setSchemaURL(const char *url);
|
||||
void setDeviceType(const char *dtype);
|
||||
void setUUID(const char *id);
|
||||
void setName(const char *name);
|
||||
void setURL(const char *url);
|
||||
void setSerialNumber(const char *sn);
|
||||
void setSerialNumber(const uint32_t sn);
|
||||
void setModelName(const char *name);
|
||||
void setModelNumber(const char *num);
|
||||
void setModelURL(const char *url);
|
||||
void setManufacturer(const char *name);
|
||||
void setManufacturerURL(const char *url);
|
||||
//char *getUSN();
|
||||
char *getUSN(const char *st);
|
||||
char *getUSN(response_types_t responseType);
|
||||
void setChipId(uint32_t chipId);
|
||||
};
|
||||
struct ssdp_response_t {
|
||||
bool waiting;
|
||||
IPAddress address;
|
||||
uint16_t port;
|
||||
UPNPDeviceType *dev;
|
||||
bool sendUUID;
|
||||
unsigned long sendTime;
|
||||
char st[SSDP_DEVICE_TYPE_SIZE];
|
||||
response_types_t responseType;
|
||||
};
|
||||
class SSDPClass {
|
||||
uint8_t m_cdeviceTypes = SSDP_CHILD_DEVICES + 1;
|
||||
protected:
|
||||
ssdp_response_t sendQueue[SSDP_QUEUE_SIZE];
|
||||
//ssdp_response_t sendQueue[SSDP_QUEUE_SIZE];
|
||||
//void _send(ssdp_method_t method, UPNPDeviceType *dev, bool useUUID);
|
||||
//void _sendAll(ssdp_method_t method, bool useUUID);
|
||||
void _startTimer();
|
||||
void _stopTimer();
|
||||
void _sendNotify();
|
||||
void _sendNotify(UPNPDeviceType *d);
|
||||
void _sendNotify(UPNPDeviceType *d, bool root);
|
||||
void _sendNotify(const char *);
|
||||
void _sendByeBye();
|
||||
void _sendByeBye(UPNPDeviceType *d);
|
||||
void _sendByeBye(UPNPDeviceType *d, bool root);
|
||||
void _sendByeBye(const char *);
|
||||
void _sendQueuedResponses();
|
||||
void _sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType);
|
||||
void _sendResponse(IPAddress addr, uint16_t port, const char *msg);
|
||||
AsyncUDP _server;
|
||||
hw_timer_t* _timer = nullptr;
|
||||
uint16_t _port = SSDP_HTTP_PORT;
|
||||
uint8_t _ttl = SSDP_MULTICAST_TTL;
|
||||
uint32_t _interval = SSDP_INTERVAL_SECONDS;
|
||||
void _processRequest(AsyncUDPPacket &p);
|
||||
void _parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p);
|
||||
void _printPacket(ssdp_packet_t *pkt);
|
||||
bool _startsWith(const char* pre, const char* str);
|
||||
void _addToSendQueue(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType, uint8_t sec);
|
||||
|
||||
public:
|
||||
SSDPClass();
|
||||
~SSDPClass();
|
||||
UPNPDeviceType deviceTypes[SSDP_CHILD_DEVICES + 1];
|
||||
bool begin();
|
||||
void loop();
|
||||
void end();
|
||||
bool isStarted = false;
|
||||
unsigned long bootId = 0;
|
||||
int configId = 0;
|
||||
IPAddress localIP();
|
||||
UPNPDeviceType* getDeviceTypes(uint8_t ndx);
|
||||
UPNPDeviceType* getDeviceType(uint8_t ndx);
|
||||
UPNPDeviceType* findDeviceByType(char *devType);
|
||||
UPNPDeviceType* findDeviceByUUID(char *uuid);
|
||||
void setHTTPPort(uint16_t port);
|
||||
void setTTL(uint8_t ttl);
|
||||
void setInterval(uint32_t interval);
|
||||
void schema(WiFiClient client) { schema((Print&)std::ref(client)); }
|
||||
void schema(Print &print);
|
||||
|
||||
void setDeviceType(uint8_t ndx, UPNPDeviceType *dt);
|
||||
void setDeviceType(uint8_t ndx, const String& type) { setDeviceType(ndx, type.c_str()); }
|
||||
void setDeviceType(uint8_t ndx, const char *type);
|
||||
void setUUID(uint8_t ndx, const char *uuid);
|
||||
void setUUID(uint8_t ndx, const char *prefix, uint32_t id);
|
||||
void setName(uint8_t ndx, const char *name);
|
||||
void setURL(uint8_t ndx, const char *url);
|
||||
void setSchemaURL(uint8_t ndx, const char *url);
|
||||
void setSerialNumber(uint8_t ndx, const char *serialNumber);
|
||||
void setModelName(uint8_t ndx, const char *name);
|
||||
void setModelNumber(uint8_t ndx, const char *num);
|
||||
void setModelURL(uint8_t ndx, const char *url);
|
||||
void setManufacturer(uint8_t ndx, const char *name);
|
||||
void setManufacturerURL(uint8_t ndx, const char *url);
|
||||
void setActive(uint8_t ndx, bool bActive);
|
||||
void setChipId(uint8_t ndx, uint32_t chipId);
|
||||
};
|
||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSSDP)
|
||||
extern SSDPClass SSDP;
|
||||
#endif
|
||||
#endif
|
||||
203
src/Sockets.cpp
Normal file
203
src/Sockets.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <WebSocketsServer.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "Sockets.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Somfy.h"
|
||||
#include "Network.h"
|
||||
#include "GitOTA.h"
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern Network net;
|
||||
extern SomfyShadeController somfy;
|
||||
extern SocketEmitter sockEmit;
|
||||
extern GitUpdater git;
|
||||
|
||||
|
||||
WebSocketsServer sockServer = WebSocketsServer(8080);
|
||||
|
||||
#define MAX_SOCK_RESPONSE 2048
|
||||
static char g_response[MAX_SOCK_RESPONSE];
|
||||
|
||||
bool room_t::isJoined(uint8_t num) {
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::join(uint8_t num) {
|
||||
if(this->isJoined(num)) return true;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == 255) {
|
||||
this->clients[i] = num;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool room_t::leave(uint8_t num) {
|
||||
if(!this->isJoined(num)) return false;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] == num) this->clients[i] = 255;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void room_t::clear() {
|
||||
memset(this->clients, 255, sizeof(this->clients));
|
||||
}
|
||||
uint8_t room_t::activeClients() {
|
||||
uint8_t n = 0;
|
||||
for(uint8_t i = 0; i < sizeof(this->clients); i++) {
|
||||
if(this->clients[i] != 255) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
/*********************************************************************
|
||||
* ClientSocketEvent class members
|
||||
********************************************************************/
|
||||
/*
|
||||
void ClientSocketEvent::prepareMessage(const char *evt, const char *payload) {
|
||||
if(strlen(payload) + 5 >= sizeof(this->msg)) Serial.printf("Socket buffer overflow %d > 2048\n", strlen(payload) + 5 + strlen(evt));
|
||||
snprintf(this->msg, sizeof(this->msg), "42[%s,%s]", evt, payload);
|
||||
}
|
||||
void ClientSocketEvent::prepareMessage(const char *evt, JsonDocument &doc) {
|
||||
memset(this->msg, 0x00, sizeof(this->msg));
|
||||
snprintf(this->msg, sizeof(this->msg), "42[%s,", evt);
|
||||
serializeJson(doc, &this->msg[strlen(this->msg)], sizeof(this->msg) - strlen(this->msg) - 2);
|
||||
strcat(this->msg, "]");
|
||||
}
|
||||
*/
|
||||
|
||||
/*********************************************************************
|
||||
* SocketEmitter class members
|
||||
********************************************************************/
|
||||
void SocketEmitter::startup() {
|
||||
|
||||
}
|
||||
void SocketEmitter::begin() {
|
||||
sockServer.begin();
|
||||
sockServer.enableHeartbeat(20000, 10000, 3);
|
||||
sockServer.onEvent(this->wsEvent);
|
||||
Serial.println("Socket Server Started...");
|
||||
//settings.printAvailHeap();
|
||||
}
|
||||
void SocketEmitter::loop() {
|
||||
this->initClients();
|
||||
sockServer.loop();
|
||||
}
|
||||
JsonSockEvent *SocketEmitter::beginEmit(const char *evt) {
|
||||
this->json.beginEvent(&sockServer, evt, g_response, sizeof(g_response));
|
||||
return &this->json;
|
||||
}
|
||||
void SocketEmitter::endEmit(uint8_t num) { this->json.endEvent(num); sockServer.loop(); }
|
||||
void SocketEmitter::endEmitRoom(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) {
|
||||
room_t *r = &this->rooms[room];
|
||||
for(uint8_t i = 0; i < sizeof(r->clients); i++) {
|
||||
if(r->clients[i] != 255) this->json.endEvent(r->clients[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t SocketEmitter::activeClients(uint8_t room) {
|
||||
if(room < SOCK_MAX_ROOMS) return this->rooms[room].activeClients();
|
||||
return 0;
|
||||
}
|
||||
void SocketEmitter::initClients() {
|
||||
for(uint8_t i = 0; i < sizeof(this->newClients); i++) {
|
||||
uint8_t num = this->newClients[i];
|
||||
if(num != 255) {
|
||||
if(sockServer.clientIsConnected(num)) {
|
||||
Serial.printf("Initializing Socket Client %u\n", num);
|
||||
esp_task_wdt_reset();
|
||||
settings.emitSockets(num);
|
||||
somfy.emitState(num);
|
||||
git.emitUpdateCheck(num);
|
||||
net.emitSockets(num);
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
this->newClients[i] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::delayInit(uint8_t num) {
|
||||
for(uint8_t i=0; i < sizeof(this->newClients); i++) {
|
||||
if(this->newClients[i] == num) break;
|
||||
else if(this->newClients[i] == 255) {
|
||||
this->newClients[i] = num;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void SocketEmitter::end() {
|
||||
sockServer.close();
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++)
|
||||
this->rooms[i].clear();
|
||||
}
|
||||
void SocketEmitter::disconnect() { sockServer.disconnect(); }
|
||||
void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
|
||||
switch(type) {
|
||||
case WStype_ERROR:
|
||||
if(length > 0)
|
||||
Serial.printf("Socket Error: %s\n", payload);
|
||||
else
|
||||
Serial.println("Socket Error: \n");
|
||||
break;
|
||||
case WStype_DISCONNECTED:
|
||||
if(length > 0)
|
||||
Serial.printf("Socket [%u] Disconnected!\n [%s]", num, payload);
|
||||
else
|
||||
Serial.printf("Socket [%u] Disconnected!\n", num);
|
||||
for(uint8_t i = 0; i < SOCK_MAX_ROOMS; i++) {
|
||||
sockEmit.rooms[i].leave(num);
|
||||
}
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
{
|
||||
IPAddress ip = sockServer.remoteIP(num);
|
||||
Serial.printf("Socket [%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
|
||||
// Send all the current shade settings to the client.
|
||||
sockServer.sendTXT(num, "Connected");
|
||||
//sockServer.loop();
|
||||
sockEmit.delayInit(num);
|
||||
}
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
if(strncmp((char *)payload, "join:", 5) == 0) {
|
||||
// In this instance the client wants to join a room. Let's do some
|
||||
// work to get the ordinal of the room that the client wants to join.
|
||||
uint8_t roomNum = atoi((char *)&payload[5]);
|
||||
Serial.printf("Client %u joining room %u\n", num, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].join(num);
|
||||
}
|
||||
else if(strncmp((char *)payload, "leave:", 6) == 0) {
|
||||
uint8_t roomNum = atoi((char *)&payload[6]);
|
||||
Serial.printf("Client %u leaving room %u\n", num, roomNum);
|
||||
if(roomNum < SOCK_MAX_ROOMS) sockEmit.rooms[roomNum].leave(num);
|
||||
}
|
||||
else {
|
||||
Serial.printf("Socket [%u] text: %s\n", num, payload);
|
||||
}
|
||||
// send message to client
|
||||
// webSocket.sendTXT(num, "message here");
|
||||
|
||||
// send data to all connected clients
|
||||
// sockServer.broadcastTXT("message here");
|
||||
break;
|
||||
case WStype_BIN:
|
||||
Serial.printf("[%u] get binary length: %u\n", num, length);
|
||||
//hexdump(payload, length);
|
||||
|
||||
// send message to client
|
||||
// sockServer.sendBIN(num, payload, length);
|
||||
break;
|
||||
case WStype_PONG:
|
||||
//Serial.printf("Pong from %u\n", num);
|
||||
break;
|
||||
case WStype_PING:
|
||||
//Serial.printf("Ping from %u\n", num);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
38
src/Sockets.h
Normal file
38
src/Sockets.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <WebSocketsServer.h>
|
||||
#include "WResp.h"
|
||||
#ifndef sockets_h
|
||||
#define sockets_h
|
||||
|
||||
#define SOCK_MAX_ROOMS 1
|
||||
#define ROOM_EMIT_FRAME 0
|
||||
|
||||
struct room_t {
|
||||
uint8_t clients[5] = {255, 255, 255, 255, 255};
|
||||
uint8_t activeClients();
|
||||
bool isJoined(uint8_t num);
|
||||
bool join(uint8_t num);
|
||||
bool leave(uint8_t num);
|
||||
void clear();
|
||||
};
|
||||
class SocketEmitter {
|
||||
protected:
|
||||
uint8_t newclients = 0;
|
||||
uint8_t newClients[5] = {255,255,255,255,255};
|
||||
void delayInit(uint8_t num);
|
||||
public:
|
||||
JsonSockEvent json;
|
||||
//ClientSocketEvent evt;
|
||||
room_t rooms[SOCK_MAX_ROOMS];
|
||||
uint8_t activeClients(uint8_t room);
|
||||
void initClients();
|
||||
void startup();
|
||||
void begin();
|
||||
void loop();
|
||||
void end();
|
||||
void disconnect();
|
||||
JsonSockEvent * beginEmit(const char *evt);
|
||||
void endEmit(uint8_t num = 255);
|
||||
void endEmitRoom(uint8_t num);
|
||||
static void wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length);
|
||||
};
|
||||
#endif
|
||||
5132
src/Somfy.cpp
Normal file
5132
src/Somfy.cpp
Normal file
File diff suppressed because it is too large
Load diff
587
src/Somfy.h
Normal file
587
src/Somfy.h
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
#ifndef SOMFY_H
|
||||
#define SOMFY_H
|
||||
#include "ConfigSettings.h"
|
||||
#include "WResp.h"
|
||||
|
||||
#define SOMFY_MAX_SHADES 32
|
||||
#define SOMFY_MAX_GROUPS 16
|
||||
#define SOMFY_MAX_LINKED_REMOTES 7
|
||||
#define SOMFY_MAX_GROUPED_SHADES 32
|
||||
#define SOMFY_MAX_ROOMS 16
|
||||
#define SOMFY_MAX_REPEATERS 7
|
||||
|
||||
#define SECS_TO_MILLIS(x) ((x) * 1000)
|
||||
#define MINS_TO_MILLIS(x) SECS_TO_MILLIS((x) * 60)
|
||||
|
||||
#define SOMFY_SUN_TIMEOUT MINS_TO_MILLIS(2)
|
||||
#define SOMFY_NO_SUN_TIMEOUT MINS_TO_MILLIS(20)
|
||||
|
||||
#define SOMFY_WIND_TIMEOUT SECS_TO_MILLIS(2)
|
||||
#define SOMFY_NO_WIND_TIMEOUT MINS_TO_MILLIS(12)
|
||||
#define SOMFY_NO_WIND_REMOTE_TIMEOUT SECS_TO_MILLIS(30)
|
||||
|
||||
|
||||
enum class radio_proto : byte { // Ordinal byte 0-255
|
||||
RTS = 0x00,
|
||||
RTW = 0x01,
|
||||
RTV = 0x02,
|
||||
GP_Relay = 0x08,
|
||||
GP_Remote = 0x09
|
||||
};
|
||||
enum class somfy_commands : byte {
|
||||
Unknown0 = 0x0,
|
||||
My = 0x1,
|
||||
Up = 0x2,
|
||||
MyUp = 0x3,
|
||||
Down = 0x4,
|
||||
MyDown = 0x5,
|
||||
UpDown = 0x6,
|
||||
MyUpDown = 0x7,
|
||||
Prog = 0x8,
|
||||
SunFlag = 0x9,
|
||||
Flag = 0xA,
|
||||
StepDown = 0xB,
|
||||
Toggle = 0xC,
|
||||
UnknownD = 0xD,
|
||||
Sensor = 0xE,
|
||||
RTWProto = 0xF, // RTW Protocol
|
||||
// Command extensions for 80 bit frames
|
||||
StepUp = 0x8B,
|
||||
Favorite = 0xC1,
|
||||
Stop = 0xF1
|
||||
};
|
||||
enum class group_types : byte {
|
||||
channel = 0x00
|
||||
};
|
||||
enum class shade_types : byte {
|
||||
roller = 0x00,
|
||||
blind = 0x01,
|
||||
ldrapery = 0x02,
|
||||
awning = 0x03,
|
||||
shutter = 0x04,
|
||||
garage1 = 0x05,
|
||||
garage3 = 0x06,
|
||||
rdrapery = 0x07,
|
||||
cdrapery = 0x08,
|
||||
drycontact = 0x09,
|
||||
drycontact2 = 0x0A,
|
||||
lgate = 0x0B,
|
||||
cgate = 0x0C,
|
||||
rgate = 0x0D,
|
||||
lgate1 = 0x0E,
|
||||
cgate1 = 0x0F,
|
||||
rgate1 = 0x10
|
||||
};
|
||||
enum class tilt_types : byte {
|
||||
none = 0x00,
|
||||
tiltmotor = 0x01,
|
||||
integrated = 0x02,
|
||||
tiltonly = 0x03,
|
||||
euromode = 0x04
|
||||
};
|
||||
String translateSomfyCommand(const somfy_commands cmd);
|
||||
somfy_commands translateSomfyCommand(const String& string);
|
||||
|
||||
#define MAX_TIMINGS 300
|
||||
#define MAX_RX_BUFFER 3
|
||||
#define MAX_TX_BUFFER 5
|
||||
|
||||
typedef enum {
|
||||
waiting_synchro = 0,
|
||||
receiving_data = 1,
|
||||
complete = 2
|
||||
} t_status;
|
||||
|
||||
struct somfy_rx_t {
|
||||
void clear() {
|
||||
this->status = t_status::waiting_synchro;
|
||||
this->bit_length = 56;
|
||||
this->cpt_synchro_hw = 0;
|
||||
this->cpt_bits = 0;
|
||||
this->previous_bit = 0;
|
||||
this->waiting_half_symbol = false;
|
||||
memset(this->payload, 0, sizeof(this->payload));
|
||||
memset(this->pulses, 0, sizeof(this->pulses));
|
||||
this->pulseCount = 0;
|
||||
}
|
||||
t_status status;
|
||||
uint8_t bit_length = 56;
|
||||
uint8_t cpt_synchro_hw = 0;
|
||||
uint8_t cpt_bits = 0;
|
||||
uint8_t previous_bit = 0;
|
||||
bool waiting_half_symbol;
|
||||
uint8_t payload[10];
|
||||
unsigned int pulses[MAX_TIMINGS];
|
||||
uint16_t pulseCount = 0;
|
||||
};
|
||||
// A simple FIFO queue to hold rx buffers. We are using
|
||||
// a byte index to make it so we don't have to reorganize
|
||||
// the storage each time we push or pop.
|
||||
struct somfy_rx_queue_t {
|
||||
void init();
|
||||
uint8_t length = 0;
|
||||
uint8_t index[MAX_RX_BUFFER];
|
||||
somfy_rx_t items[MAX_RX_BUFFER];
|
||||
void push(somfy_rx_t *rx);
|
||||
bool pop(somfy_rx_t *rx);
|
||||
};
|
||||
struct somfy_tx_t {
|
||||
void clear() {
|
||||
this->hwsync = 0;
|
||||
this->bit_length = 0;
|
||||
memset(this->payload, 0x00, sizeof(this->payload));
|
||||
}
|
||||
uint8_t hwsync = 0;
|
||||
uint8_t bit_length = 0;
|
||||
uint8_t payload[10] = {};
|
||||
};
|
||||
struct somfy_tx_queue_t {
|
||||
somfy_tx_queue_t() { this->clear(); }
|
||||
void clear() {
|
||||
for (uint8_t i = 0; i < MAX_TX_BUFFER; i++) {
|
||||
this->index[i] = 255;
|
||||
this->items[i].clear();
|
||||
}
|
||||
this->length = 0;
|
||||
}
|
||||
unsigned long delay_time = 0;
|
||||
uint8_t length = 0;
|
||||
uint8_t index[MAX_TX_BUFFER] = {255};
|
||||
somfy_tx_t items[MAX_TX_BUFFER];
|
||||
bool pop(somfy_tx_t *tx);
|
||||
void push(somfy_rx_t *rx); // Used for repeats
|
||||
void push(uint8_t hwsync, byte *payload, uint8_t bit_length);
|
||||
};
|
||||
|
||||
enum class somfy_flags_t : byte {
|
||||
SunFlag = 0x01,
|
||||
SunSensor = 0x02,
|
||||
DemoMode = 0x04,
|
||||
Light = 0x08,
|
||||
Windy = 0x10,
|
||||
Sunny = 0x20,
|
||||
Lighted = 0x40,
|
||||
SimMy = 0x80
|
||||
};
|
||||
enum class gpio_flags_t : byte {
|
||||
LowLevelTrigger = 0x01
|
||||
};
|
||||
struct somfy_relay_t {
|
||||
uint32_t remoteAddress = 0;
|
||||
uint8_t sync = 0;
|
||||
byte frame[10] = {0};
|
||||
};
|
||||
struct somfy_frame_t {
|
||||
bool valid = false;
|
||||
bool processed = false;
|
||||
bool synonym = false;
|
||||
radio_proto proto = radio_proto::RTS;
|
||||
int rssi = 0;
|
||||
byte lqi = 0x0;
|
||||
somfy_commands cmd;
|
||||
uint32_t remoteAddress = 0;
|
||||
uint16_t rollingCode = 0;
|
||||
uint8_t encKey = 0xA7;
|
||||
uint8_t checksum = 0;
|
||||
uint8_t hwsync = 0;
|
||||
uint8_t repeats = 0;
|
||||
uint32_t await = 0;
|
||||
uint8_t bitLength = 56;
|
||||
uint16_t pulseCount = 0;
|
||||
uint8_t stepSize = 0;
|
||||
void print();
|
||||
void encode80BitFrame(byte *frame, uint8_t repeat);
|
||||
byte calc80Checksum(byte b0, byte b1, byte b2);
|
||||
byte encode80Byte7(byte start, uint8_t repeat);
|
||||
void encodeFrame(byte *frame);
|
||||
void decodeFrame(byte* frame);
|
||||
void decodeFrame(somfy_rx_t *rx);
|
||||
bool isRepeat(somfy_frame_t &f);
|
||||
bool isSynonym(somfy_frame_t &f);
|
||||
void copy(somfy_frame_t &f);
|
||||
};
|
||||
|
||||
class SomfyRoom {
|
||||
public:
|
||||
uint8_t roomId = 0;
|
||||
char name[21] = "";
|
||||
int8_t sortOrder = 0;
|
||||
void clear();
|
||||
bool save();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
void emitState(const char *evt = "roomState");
|
||||
void emitState(uint8_t num, const char *evt = "roomState");
|
||||
void publish();
|
||||
void unpublish();
|
||||
};
|
||||
|
||||
class SomfyRemote {
|
||||
// These sizes for the data have been
|
||||
// confirmed. The address is actually 24bits
|
||||
// and the rolling code is 16 bits.
|
||||
protected:
|
||||
char m_remotePrefId[11] = "";
|
||||
uint32_t m_remoteAddress = 0;
|
||||
public:
|
||||
radio_proto proto = radio_proto::RTS;
|
||||
uint8_t gpioFlags = 0;
|
||||
int8_t gpioDir = 0;
|
||||
uint8_t gpioUp = 0;
|
||||
uint8_t gpioDown = 0;
|
||||
uint8_t gpioMy = 0;
|
||||
uint32_t gpioRelease = 0;
|
||||
somfy_frame_t lastFrame;
|
||||
bool flipCommands = false;
|
||||
uint16_t lastRollingCode = 0;
|
||||
uint8_t flags = 0;
|
||||
uint8_t bitLength = 0;
|
||||
uint8_t repeats = 1;
|
||||
virtual bool isLastCommand(somfy_commands cmd);
|
||||
char *getRemotePrefId() {return m_remotePrefId;}
|
||||
virtual void toJSON(JsonResponse &json);
|
||||
virtual void setRemoteAddress(uint32_t address);
|
||||
virtual uint32_t getRemoteAddress();
|
||||
virtual uint16_t getNextRollingCode();
|
||||
virtual uint16_t setRollingCode(uint16_t code);
|
||||
bool hasSunSensor();
|
||||
bool hasLight();
|
||||
bool simMy();
|
||||
void setSunSensor(bool bHasSensor);
|
||||
void setLight(bool bHasLight);
|
||||
void setSimMy(bool bSimMy);
|
||||
virtual void sendCommand(somfy_commands cmd);
|
||||
virtual void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
void sendSensorCommand(int8_t isWindy, int8_t isSunny, uint8_t repeat);
|
||||
void repeatFrame(uint8_t repeat);
|
||||
virtual uint16_t p_lastRollingCode(uint16_t code);
|
||||
somfy_commands transformCommand(somfy_commands cmd);
|
||||
virtual void triggerGPIOs(somfy_frame_t &frame);
|
||||
|
||||
};
|
||||
class SomfyLinkedRemote : public SomfyRemote {
|
||||
public:
|
||||
SomfyLinkedRemote();
|
||||
};
|
||||
class SomfyShade : public SomfyRemote {
|
||||
protected:
|
||||
uint8_t shadeId = 255;
|
||||
uint64_t moveStart = 0;
|
||||
uint64_t tiltStart = 0;
|
||||
uint64_t noSunStart = 0;
|
||||
uint64_t sunStart = 0;
|
||||
uint64_t windStart = 0;
|
||||
uint64_t windLast = 0;
|
||||
uint64_t noWindStart = 0;
|
||||
bool noSunDone = true;
|
||||
bool sunDone = true;
|
||||
bool windDone = true;
|
||||
bool noWindDone = true;
|
||||
float startPos = 0.0f;
|
||||
float startTiltPos = 0.0f;
|
||||
bool settingMyPos = false;
|
||||
bool settingPos = false;
|
||||
bool settingTiltPos = false;
|
||||
uint32_t awaitMy = 0;
|
||||
public:
|
||||
uint8_t roomId = 0;
|
||||
int8_t sortOrder = 0;
|
||||
bool flipPosition = false;
|
||||
shade_types shadeType = shade_types::roller;
|
||||
tilt_types tiltType = tilt_types::none;
|
||||
#ifdef USE_NVS
|
||||
void load();
|
||||
#endif
|
||||
float currentPos = 0.0f;
|
||||
float currentTiltPos = 0.0f;
|
||||
int8_t lastMovement = 0;
|
||||
int8_t direction = 0; // 0 = stopped, 1=down, -1=up.
|
||||
int8_t tiltDirection = 0; // 0=stopped, 1=clockwise, -1=counter clockwise
|
||||
float target = 0.0f;
|
||||
float tiltTarget = 0.0f;
|
||||
float myPos = -1.0f;
|
||||
float myTiltPos = -1.0f;
|
||||
SomfyLinkedRemote linkedRemotes[SOMFY_MAX_LINKED_REMOTES];
|
||||
bool paired = false;
|
||||
int8_t validateJSON(JsonObject &obj);
|
||||
void toJSONRef(JsonResponse &json);
|
||||
int8_t fromJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json) override;
|
||||
|
||||
char name[21] = "";
|
||||
void setShadeId(uint8_t id) { shadeId = id; }
|
||||
uint8_t getShadeId() { return shadeId; }
|
||||
uint32_t upTime = 10000;
|
||||
uint32_t downTime = 10000;
|
||||
uint32_t tiltTime = 7000;
|
||||
uint16_t stepSize = 100;
|
||||
bool save();
|
||||
bool isIdle();
|
||||
bool isInGroup();
|
||||
void checkMovement();
|
||||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void processInternalCommand(somfy_commands cmd, uint8_t repeat = 1);
|
||||
void setTiltMovement(int8_t dir);
|
||||
void setMovement(int8_t dir);
|
||||
void setTarget(float target);
|
||||
bool isAtTarget();
|
||||
bool isToggle();
|
||||
void moveToTarget(float pos, float tilt = -1.0f);
|
||||
void moveToTiltTarget(float target);
|
||||
void sendTiltCommand(somfy_commands cmd);
|
||||
void sendCommand(somfy_commands cmd);
|
||||
void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
|
||||
bool unlinkRemote(uint32_t remoteAddress);
|
||||
void emitState(const char *evt = "shadeState");
|
||||
void emitState(uint8_t num, const char *evt = "shadeState");
|
||||
void emitCommand(somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
|
||||
void emitCommand(uint8_t num, somfy_commands cmd, const char *source, uint32_t sourceAddress, const char *evt = "shadeCommand");
|
||||
void setMyPosition(int8_t pos, int8_t tilt = -1);
|
||||
void moveToMyPosition();
|
||||
void processWaitingFrame();
|
||||
void publish();
|
||||
void unpublish();
|
||||
static void unpublish(uint8_t id);
|
||||
static void unpublish(uint8_t id, const char *topic);
|
||||
void publishState();
|
||||
void commit();
|
||||
void commitShadePosition();
|
||||
void commitTiltPosition();
|
||||
void commitMyPosition();
|
||||
void clear();
|
||||
int8_t transformPosition(float fpos);
|
||||
void setGPIOs();
|
||||
void triggerGPIOs(somfy_frame_t &frame);
|
||||
bool usesPin(uint8_t pin);
|
||||
// State Setters
|
||||
int8_t p_direction(int8_t dir);
|
||||
int8_t p_tiltDirection(int8_t dir);
|
||||
float p_target(float target);
|
||||
float p_tiltTarget(float target);
|
||||
float p_myPos(float pos);
|
||||
float p_myTiltPos(float pos);
|
||||
bool p_flag(somfy_flags_t flag, bool val);
|
||||
bool p_sunFlag(bool val);
|
||||
bool p_sunny(bool val);
|
||||
bool p_windy(bool val);
|
||||
float p_currentPos(float pos);
|
||||
float p_currentTiltPos(float pos);
|
||||
uint16_t p_lastRollingCode(uint16_t code);
|
||||
bool publish(const char *topic, const char *val, bool retain = false);
|
||||
bool publish(const char *topic, uint8_t val, bool retain = false);
|
||||
bool publish(const char *topic, int8_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint32_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint16_t val, bool retain = false);
|
||||
bool publish(const char *topic, bool val, bool retain = false);
|
||||
void publishDisco();
|
||||
void unpublishDisco();
|
||||
};
|
||||
class SomfyGroup : public SomfyRemote {
|
||||
protected:
|
||||
uint8_t groupId = 255;
|
||||
public:
|
||||
uint8_t roomId = 0;
|
||||
int8_t sortOrder = 0;
|
||||
group_types groupType = group_types::channel;
|
||||
int8_t direction = 0; // 0 = stopped, 1=down, -1=up.
|
||||
char name[21] = "";
|
||||
uint8_t linkedShades[SOMFY_MAX_GROUPED_SHADES];
|
||||
void setGroupId(uint8_t id) { groupId = id; }
|
||||
uint8_t getGroupId() { return groupId; }
|
||||
bool save();
|
||||
void clear();
|
||||
bool fromJSON(JsonObject &obj);
|
||||
//bool toJSON(JsonObject &obj);
|
||||
void toJSON(JsonResponse &json);
|
||||
void toJSONRef(JsonResponse &json);
|
||||
|
||||
bool linkShade(uint8_t shadeId);
|
||||
bool unlinkShade(uint8_t shadeId);
|
||||
bool hasShadeId(uint8_t shadeId);
|
||||
void compressLinkedShadeIds();
|
||||
void publish();
|
||||
void unpublish();
|
||||
static void unpublish(uint8_t id);
|
||||
static void unpublish(uint8_t id, const char *topic);
|
||||
void publishState();
|
||||
void updateFlags();
|
||||
void emitState(const char *evt = "groupState");
|
||||
void emitState(uint8_t num, const char *evt = "groupState");
|
||||
void sendCommand(somfy_commands cmd);
|
||||
void sendCommand(somfy_commands cmd, uint8_t repeat, uint8_t stepSize = 0);
|
||||
int8_t p_direction(int8_t dir);
|
||||
bool publish(const char *topic, uint8_t val, bool retain = false);
|
||||
bool publish(const char *topic, int8_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint32_t val, bool retain = false);
|
||||
bool publish(const char *topic, uint16_t val, bool retain = false);
|
||||
bool publish(const char *topic, bool val, bool retain = false);
|
||||
};
|
||||
struct transceiver_config_t {
|
||||
bool printBuffer = false;
|
||||
bool enabled = false;
|
||||
uint8_t type = 56; // 56 or 80 bit protocol.
|
||||
radio_proto proto = radio_proto::RTS;
|
||||
uint8_t SCKPin = 18;
|
||||
uint8_t TXPin = 13;
|
||||
uint8_t RXPin = 12;
|
||||
uint8_t MOSIPin = 23;
|
||||
uint8_t MISOPin = 19;
|
||||
uint8_t CSNPin = 5;
|
||||
bool radioInit = false;
|
||||
float frequency = 433.42; // Basic frequency
|
||||
float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
||||
float rxBandwidth = 99.97; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
||||
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||
/*
|
||||
bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
|
||||
byte modulationMode = 2; // Modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||
uint8_t channel = 0; // The channel number from 0 to 255
|
||||
float channelSpacing = 199.95; // Channel spacing in multiplied by the channel number and added to the base frequency in kHz. 25.39 to 405.45. Default 199.95
|
||||
float dataRate = 99.97; // The data rate in kBaud. 0.02 to 1621.83 Default is 99.97.
|
||||
uint8_t syncMode = 0; // 0=No preamble/sync,
|
||||
// 1=16 sync word bits detected,
|
||||
// 2=16/16 sync words bits detected.
|
||||
// 3=30/32 sync word bits detected,
|
||||
// 4=No preamble/sync carrier above threshold
|
||||
// 5=15/16 + carrier above threshold.
|
||||
// 6=16/16 + carrier-sense above threshold
|
||||
// 7=0/32 + carrier-sense above threshold
|
||||
uint16_t syncWordHigh = 211; // The sync word used to the sync mode.
|
||||
uint16_t syncWordLow = 145; // The sync word used to the sync mode.
|
||||
uint8_t addrCheckMode = 0; // 0=No address filtration
|
||||
// 1=Check address without broadcast.
|
||||
// 2=Address check with 0 as broadcast.
|
||||
// 3=Address check with 0 or 255 as broadcast.
|
||||
uint8_t checkAddr = 0; // Packet filter address depending on addrCheck settings.
|
||||
bool dataWhitening = false; // Indicates whether data whitening should be applied.
|
||||
uint8_t pktFormat = 0; // 0=Use FIFO buffers form RX and TX
|
||||
// 1=Synchronous serial mode. RX on GDO0 and TX on either GDOx pins.
|
||||
// 2=Random TX mode. Send data using PN9 generator.
|
||||
// 3=Asynchronous serial mode. RX on GDO0 and TX on either GDOx pins.
|
||||
uint8_t pktLengthMode = 0; // 0=Fixed packet length
|
||||
// 1=Variable packet length
|
||||
// 2=Infinite packet length
|
||||
// 3=Reserved
|
||||
uint8_t pktLength = 0; // Packet length
|
||||
bool useCRC = false; // Indicates whether CRC is to be used.
|
||||
bool autoFlushCRC = false; // Automatically flush RX FIFO when CRC fails. If more than one packet is in the buffer it too will be flushed.
|
||||
bool disableDCFilter = false; // Digital blocking filter for demodulator. Only for data rates <= 250k.
|
||||
bool enableManchester = true; // Enable/disable Manchester encoding.
|
||||
bool enableFEC = false; // Enable/disable forward error correction.
|
||||
uint8_t minPreambleBytes = 0; // The minimum number of preamble bytes to be transmitten.
|
||||
// 0=2bytes
|
||||
// 1=3bytes
|
||||
// 2=4bytes
|
||||
// 3=6bytes
|
||||
// 4=8bytes
|
||||
// 5=12bytes
|
||||
// 6=16bytes
|
||||
// 7=24bytes
|
||||
uint8_t pqtThreshold = 0; // Preamble quality estimator threshold. The preable quality estimator increase an internal counter by one each time a bit is received that is different than the prevoius bit and
|
||||
// decreases the bounter by 8 each time a bit is received that is the same as the lats bit. A threshold of 4 PQT for this counter is used to gate sync word detection.
|
||||
// When PQT = 0 a sync word is always accepted.
|
||||
bool appendStatus = false; // Appends the RSSI and LQI values to the TX packed as well as the CRC.
|
||||
*/
|
||||
void fromJSON(JsonObject& obj);
|
||||
//void toJSON(JsonObject& obj);
|
||||
void toJSON(JsonResponse& json);
|
||||
void save();
|
||||
void load();
|
||||
void apply();
|
||||
void removeNVSKey(const char *key);
|
||||
};
|
||||
class Transceiver {
|
||||
private:
|
||||
static void handleReceive();
|
||||
bool _received = false;
|
||||
somfy_frame_t frame;
|
||||
public:
|
||||
transceiver_config_t config;
|
||||
bool printBuffer = false;
|
||||
//bool toJSON(JsonObject& obj);
|
||||
void toJSON(JsonResponse& json);
|
||||
bool fromJSON(JsonObject& obj);
|
||||
bool save();
|
||||
bool begin();
|
||||
void loop();
|
||||
bool end();
|
||||
bool receive(somfy_rx_t *rx);
|
||||
void clearReceived();
|
||||
void enableReceive();
|
||||
void disableReceive();
|
||||
somfy_frame_t& lastFrame();
|
||||
void sendFrame(byte *frame, uint8_t sync, uint8_t bitLength = 56);
|
||||
void beginTransmit();
|
||||
void endTransmit();
|
||||
void emitFrame(somfy_frame_t *frame, somfy_rx_t *rx = nullptr);
|
||||
void beginFrequencyScan();
|
||||
void endFrequencyScan();
|
||||
void processFrequencyScan(bool received = false);
|
||||
void emitFrequencyScan(uint8_t num = 255);
|
||||
bool usesPin(uint8_t pin);
|
||||
};
|
||||
class SomfyShadeController {
|
||||
protected:
|
||||
uint8_t m_shadeIds[SOMFY_MAX_SHADES];
|
||||
uint32_t lastCommit = 0;
|
||||
public:
|
||||
bool useNVS();
|
||||
bool isDirty = false;
|
||||
uint32_t startingAddress;
|
||||
uint8_t getNextRoomId();
|
||||
uint8_t getNextShadeId();
|
||||
uint8_t getNextGroupId();
|
||||
int8_t getMaxRoomOrder();
|
||||
int8_t getMaxShadeOrder();
|
||||
int8_t getMaxGroupOrder();
|
||||
uint32_t getNextRemoteAddress(uint8_t shadeId);
|
||||
SomfyShadeController();
|
||||
Transceiver transceiver;
|
||||
SomfyRoom *addRoom();
|
||||
SomfyRoom *addRoom(JsonObject &obj);
|
||||
SomfyShade *addShade();
|
||||
SomfyShade *addShade(JsonObject &obj);
|
||||
SomfyGroup *addGroup();
|
||||
SomfyGroup *addGroup(JsonObject &obj);
|
||||
bool deleteRoom(uint8_t roomId);
|
||||
bool deleteShade(uint8_t shadeId);
|
||||
bool deleteGroup(uint8_t groupId);
|
||||
bool begin();
|
||||
void loop();
|
||||
void end();
|
||||
void compressRepeaters();
|
||||
uint32_t repeaters[SOMFY_MAX_REPEATERS] = {0};
|
||||
SomfyRoom rooms[SOMFY_MAX_ROOMS];
|
||||
SomfyShade shades[SOMFY_MAX_SHADES];
|
||||
SomfyGroup groups[SOMFY_MAX_GROUPS];
|
||||
bool linkRepeater(uint32_t address);
|
||||
bool unlinkRepeater(uint32_t address);
|
||||
void toJSONShades(JsonResponse &json);
|
||||
void toJSONRooms(JsonResponse &json);
|
||||
void toJSONGroups(JsonResponse &json);
|
||||
void toJSONRepeaters(JsonResponse &json);
|
||||
uint8_t repeaterCount();
|
||||
uint8_t roomCount();
|
||||
uint8_t shadeCount();
|
||||
uint8_t groupCount();
|
||||
void updateGroupFlags();
|
||||
SomfyShade * getShadeById(uint8_t shadeId);
|
||||
SomfyRoom * getRoomById(uint8_t roomId);
|
||||
SomfyGroup * getGroupById(uint8_t groupId);
|
||||
SomfyShade * findShadeByRemoteAddress(uint32_t address);
|
||||
SomfyGroup * findGroupByRemoteAddress(uint32_t address);
|
||||
void sendFrame(somfy_frame_t &frame, uint8_t repeats = 0);
|
||||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void emitState(uint8_t num = 255);
|
||||
void publish();
|
||||
void processWaitingFrame();
|
||||
void commit();
|
||||
void writeBackup();
|
||||
bool loadShadesFile(const char *filename);
|
||||
#ifdef USE_NVS
|
||||
bool loadLegacy();
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
86
src/SomfyController.ino
Normal file
86
src/SomfyController.ino
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include "ConfigSettings.h"
|
||||
#include "Network.h"
|
||||
#include "Web.h"
|
||||
#include "Sockets.h"
|
||||
#include "Utils.h"
|
||||
#include "Somfy.h"
|
||||
#include "MQTT.h"
|
||||
#include "GitOTA.h"
|
||||
|
||||
ConfigSettings settings;
|
||||
Web webServer;
|
||||
SocketEmitter sockEmit;
|
||||
Network net;
|
||||
rebootDelay_t rebootDelay;
|
||||
SomfyShadeController somfy;
|
||||
MQTTClass mqtt;
|
||||
GitUpdater git;
|
||||
|
||||
uint32_t oldheap = 0;
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println();
|
||||
Serial.println("Startup/Boot....");
|
||||
Serial.println("Mounting File System...");
|
||||
if(LittleFS.begin()) Serial.println("File system mounted successfully");
|
||||
else Serial.println("Error mounting file system");
|
||||
settings.begin();
|
||||
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
|
||||
delay(10);
|
||||
Serial.println();
|
||||
webServer.startup();
|
||||
webServer.begin();
|
||||
delay(1000);
|
||||
net.setup();
|
||||
somfy.begin();
|
||||
//git.checkForUpdate();
|
||||
esp_task_wdt_init(7, true); //enable panic so ESP32 restarts
|
||||
esp_task_wdt_add(NULL); //add current thread to WDT watch
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
//uint32_t heap = ESP.getFreeHeap();
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
Serial.print("Rebooting after ");
|
||||
Serial.print(rebootDelay.rebootTime);
|
||||
Serial.println("ms");
|
||||
net.end();
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
uint32_t timing = millis();
|
||||
|
||||
net.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Net: %ldms\n", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
somfy.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Somfy: %ldms\n", millis() - timing);
|
||||
timing = millis();
|
||||
esp_task_wdt_reset();
|
||||
if(net.connected() || net.softAPOpened) {
|
||||
if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) {
|
||||
git.loop();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
webServer.loop();
|
||||
esp_task_wdt_reset();
|
||||
if(millis() - timing > 100) Serial.printf("Timing WebServer: %ldms\n", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
sockEmit.loop();
|
||||
if(millis() - timing > 100) Serial.printf("Timing Socket: %ldms\n", millis() - timing);
|
||||
esp_task_wdt_reset();
|
||||
timing = millis();
|
||||
}
|
||||
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
|
||||
net.end();
|
||||
ESP.restart();
|
||||
}
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
BIN
src/SomfyController.ino.esp32.bin
Normal file
BIN
src/SomfyController.ino.esp32.bin
Normal file
Binary file not shown.
BIN
src/SomfyController.ino.esp32s3.bin
Normal file
BIN
src/SomfyController.ino.esp32s3.bin
Normal file
Binary file not shown.
BIN
src/SomfyController.littlefs.bin
Normal file
BIN
src/SomfyController.littlefs.bin
Normal file
Binary file not shown.
131
src/Utils.cpp
Normal file
131
src/Utils.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#include <Arduino.h>
|
||||
#include <time.h>
|
||||
#include "Utils.h"
|
||||
|
||||
|
||||
/*********************************************************************
|
||||
* Timestamp class members
|
||||
********************************************************************/
|
||||
unsigned long Timestamp::epoch() {
|
||||
struct tm tmNow;
|
||||
time_t now;
|
||||
if(!getLocalTime(&tmNow,50)) return 0;
|
||||
time(&now);
|
||||
return now;
|
||||
}
|
||||
time_t Timestamp::now() {
|
||||
struct tm tmNow;
|
||||
getLocalTime(&tmNow,50);
|
||||
return mktime(&tmNow);
|
||||
}
|
||||
time_t Timestamp::getUTC() {
|
||||
time_t t;
|
||||
time(&t);
|
||||
return t;
|
||||
}
|
||||
time_t Timestamp::mkUTCTime(struct tm *dt) {
|
||||
time_t tsBadLocal = mktime(dt);
|
||||
|
||||
struct tm tmUTC;
|
||||
struct tm tmLocal;
|
||||
gmtime_r(&tsBadLocal, &tmUTC);
|
||||
localtime_r(&tsBadLocal, &tmLocal);
|
||||
time_t tsBadUTC = mktime(&tmUTC);
|
||||
time_t tsLocal = mktime(&tmLocal);
|
||||
time_t tsLocalOffset = tsLocal - tsBadUTC;
|
||||
return tsBadLocal + tsLocalOffset;
|
||||
}
|
||||
time_t Timestamp::parseUTCTime(const char *buff) {
|
||||
struct tm dt;
|
||||
dt.tm_hour = 0;
|
||||
dt.tm_mday = 0;
|
||||
dt.tm_mon = 0;
|
||||
dt.tm_year = 0;
|
||||
dt.tm_wday = 0;
|
||||
dt.tm_yday = 0;
|
||||
dt.tm_isdst = false;
|
||||
char num[5];
|
||||
uint8_t i = 0;
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_year = atoi(num)-1900;
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_mon = atoi(num)-1;
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-' || ch == 'T' || ch == 't') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_mday = atoi(num);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-' || ch == ':') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_hour = atoi(num);
|
||||
memset(num, 0x00, sizeof(num));
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-' || ch == ':') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_min = atoi(num);
|
||||
for(uint8_t j = 0; j < 5 && i < strlen(buff);) {
|
||||
char ch = buff[i++];
|
||||
if(ch == '-' || ch == ':' || ch == 'Z') break;
|
||||
if(!isdigit(ch)) continue;
|
||||
else num[j++] = ch;
|
||||
}
|
||||
dt.tm_sec = atoi(num);
|
||||
//Serial.printf("Y:%d M:%d D:%d H:%d M:%d S:%d\n", dt.tm_year, dt.tm_mon, dt.tm_mday, dt.tm_hour, dt.tm_min, dt.tm_sec);
|
||||
return Timestamp::mkUTCTime(&dt);
|
||||
}
|
||||
time_t Timestamp::getUTC(time_t t) {
|
||||
tm tmUTC;
|
||||
gmtime_r(&t, &tmUTC);
|
||||
return mktime(&tmUTC);
|
||||
}
|
||||
char * Timestamp::getISOTime() { return this->getISOTime(this->getUTC()); }
|
||||
char * Timestamp::getISOTime(time_t epoch) {
|
||||
struct tm *dt = localtime((time_t *)&epoch);
|
||||
return this->formatISO(dt, this->tzOffset());
|
||||
}
|
||||
char * Timestamp::formatISO(struct tm *dt, int tz) {
|
||||
int tzHrs = floor(tz/100);
|
||||
int tzMin = tz - (tzHrs * 100);
|
||||
int ms = millis() % 1000;
|
||||
snprintf(this->_timeBuffer, sizeof(this->_timeBuffer), "%04d-%02d-%02dT%02d:%02d:%02d.%03d%s%02d%02d",
|
||||
dt->tm_year + 1900, dt->tm_mon + 1, dt->tm_mday, dt->tm_hour, dt->tm_min, dt->tm_sec, ms, tzHrs < 0 ? "-" : "+", abs(tzHrs), abs(tzMin));
|
||||
return this->_timeBuffer;
|
||||
}
|
||||
int Timestamp::calcTZOffset(time_t *dt) {
|
||||
tm tmLocal, tmUTC;
|
||||
gmtime_r(dt, &tmUTC);
|
||||
localtime_r(dt, &tmLocal);
|
||||
long diff = mktime(&tmLocal) - mktime(&tmUTC);
|
||||
if(tmLocal.tm_isdst) diff += 3600;
|
||||
int hrs = (int)((diff/3600) * 100);
|
||||
int mins = diff - (hrs * 36);
|
||||
return hrs + mins;
|
||||
}
|
||||
int Timestamp::tzOffset() {
|
||||
time_t now;
|
||||
time(&now);
|
||||
return Timestamp::calcTZOffset(&now);
|
||||
}
|
||||
99
src/Utils.h
Normal file
99
src/Utils.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#ifndef utils_h
|
||||
#define utils_h
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
#define DEBUG_SOMFY Serial
|
||||
|
||||
|
||||
|
||||
|
||||
[[maybe_unused]] static void SETCHARPROP(char *prop, const char *value, size_t size) {strncpy(prop, value, size); prop[size - 1] = '\0';}
|
||||
/*
|
||||
namespace util {
|
||||
// Createa a custom to_string function. C++ can be annoying
|
||||
// with all the trailing 0s on number formats.
|
||||
template <typename T> std::string to_string(const T& t) {
|
||||
std::string str{std::to_string (t)};
|
||||
int offset{1};
|
||||
if (str.find_last_not_of('0') == str.find('.')) {
|
||||
offset = 0;
|
||||
}
|
||||
str.erase ( str.find_last_not_of('0') + 1, std::string::npos );
|
||||
str.erase ( str.find_last_not_of('.') + 1, std::string::npos );
|
||||
return str;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static void _ltrim(char *str) {
|
||||
int s = 0, j, k = 0;
|
||||
int e = strlen(str);
|
||||
while(s < e && (str[s] == ' ' || str[s] == '\n' || str[s] == '\r' || str[s] == '\t' || str[s] == '"')) s++;
|
||||
if(s > 0) {
|
||||
for(j = s; j < e; j++) {
|
||||
str[k] = str[j];
|
||||
k++;
|
||||
}
|
||||
str[k] = '\0';
|
||||
}
|
||||
//if(s > 0) strcpy(str, &str[s]);
|
||||
}
|
||||
static void _rtrim(char *str) {
|
||||
int e = strlen(str) - 1;
|
||||
while(e >= 0 && (str[e] == ' ' || str[e] == '\n' || str[e] == '\r' || str[e] == '\t' || str[e] == '"')) {str[e] = '\0'; e--;}
|
||||
}
|
||||
[[maybe_unused]] static void _trim(char *str) { _ltrim(str); _rtrim(str); }
|
||||
struct rebootDelay_t {
|
||||
bool reboot = false;
|
||||
int rebootTime = 0;
|
||||
bool closed = false;
|
||||
};
|
||||
[[maybe_unused]] static bool toBoolean(const char *str, bool def) {
|
||||
if(!str) return def;
|
||||
if(strlen(str) == 0) return def;
|
||||
else if(str[0] == 't' || str[0] == 'T' || str[0] == '1') return true;
|
||||
else if(str[0] == 'f' || str[0] == 'F' || str[0] == '0') return false;
|
||||
return def;
|
||||
}
|
||||
|
||||
class Timestamp {
|
||||
char _timeBuffer[128];
|
||||
public:
|
||||
time_t getUTC();
|
||||
time_t getUTC(time_t epoch);
|
||||
char * getISOTime();
|
||||
char * getISOTime(time_t epoch);
|
||||
char * formatISO(struct tm *dt, int tz);
|
||||
int tzOffset();
|
||||
static time_t parseUTCTime(const char *buff);
|
||||
static time_t mkUTCTime(struct tm *dt);
|
||||
static int calcTZOffset(time_t *dt);
|
||||
static time_t now();
|
||||
static unsigned long epoch();
|
||||
};
|
||||
// Sort an array
|
||||
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray);
|
||||
// Sort in reverse
|
||||
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray);
|
||||
// Sort an array with custom comparison function
|
||||
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType));
|
||||
// Sort in reverse with custom comparison function
|
||||
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType));
|
||||
namespace ArduinoSort {
|
||||
template<typename AnyType> bool builtinLargerThan(AnyType first, AnyType second) { return first > second; }
|
||||
//template<> bool builtinLargerThan(char* first, char* second) { return strcmp(first, second) > 0; }
|
||||
template<typename AnyType> void insertionSort(AnyType array[], size_t sizeOfArray, bool reverse, bool (*largerThan)(AnyType, AnyType)) { for (size_t i = 1; i < sizeOfArray; i++) {
|
||||
for (size_t j = i; j > 0 && (largerThan(array[j-1], array[j]) != reverse); j--) {
|
||||
AnyType tmp = array[j-1];
|
||||
array[j-1] = array[j];
|
||||
array[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray) { ArduinoSort::insertionSort(array, sizeOfArray, false, ArduinoSort::builtinLargerThan); }
|
||||
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray) { ArduinoSort::insertionSort(array, sizeOfArray, true, ArduinoSort::builtinLargerThan); }
|
||||
template<typename AnyType> void sortArray(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType)) { ArduinoSort::insertionSort(array, sizeOfArray, false, largerThan); }
|
||||
template<typename AnyType> void sortArrayReverse(AnyType array[], size_t sizeOfArray, bool (*largerThan)(AnyType, AnyType)) { ArduinoSort::insertionSort(array, sizeOfArray, true, largerThan); }
|
||||
#endif
|
||||
204
src/WResp.cpp
Normal file
204
src/WResp.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#include "WResp.h"
|
||||
void JsonSockEvent::beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize) {
|
||||
this->server = server;
|
||||
this->buff = buff;
|
||||
this->buffSize = buffSize;
|
||||
this->_nocomma = true;
|
||||
this->_closed = false;
|
||||
snprintf(this->buff, buffSize, "42[%s,", evt);
|
||||
}
|
||||
void JsonSockEvent::closeEvent() {
|
||||
if(!this->_closed) {
|
||||
if(strlen(this->buff) < buffSize) strcat(this->buff, "]");
|
||||
else this->buff[buffSize - 1] = ']';
|
||||
}
|
||||
this->_nocomma = true;
|
||||
this->_closed = true;
|
||||
}
|
||||
void JsonSockEvent::endEvent(uint8_t num) {
|
||||
this->closeEvent();
|
||||
if(num == 255) this->server->broadcastTXT(this->buff);
|
||||
else this->server->sendTXT(num, this->buff);
|
||||
}
|
||||
void JsonSockEvent::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
Serial.printf("Socket exceeded buffer size %d - %d\n", this->buffSize, len);
|
||||
Serial.println(this->buff);
|
||||
return;
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
}
|
||||
void JsonResponse::beginResponse(WebServer *server, char *buff, size_t buffSize) {
|
||||
this->server = server;
|
||||
this->buff = buff;
|
||||
this->buffSize = buffSize;
|
||||
this->buff[0] = 0x00;
|
||||
this->_nocomma = true;
|
||||
server->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
}
|
||||
void JsonResponse::endResponse() {
|
||||
if(strlen(buff)) this->send();
|
||||
server->sendContent("", 0);
|
||||
}
|
||||
void JsonResponse::send() {
|
||||
if(!this->_headersSent) server->send_P(200, "application/json", this->buff);
|
||||
else server->sendContent(this->buff);
|
||||
//Serial.printf("Sent %d bytes %d\n", strlen(this->buff), this->buffSize);
|
||||
this->buff[0] = 0x00;
|
||||
this->_headersSent = true;
|
||||
}
|
||||
void JsonResponse::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
this->send();
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
}
|
||||
|
||||
void JsonFormatter::beginObject(const char *name) {
|
||||
if(name && strlen(name) > 0) this->appendElem(name);
|
||||
else if(!this->_nocomma) this->_safecat(",");
|
||||
this->_safecat("{");
|
||||
this->_objects++;
|
||||
this->_nocomma = true;
|
||||
}
|
||||
void JsonFormatter::endObject() {
|
||||
//if(strlen(this->buff) + 1 > this->buffSize - 1) this->send();
|
||||
this->_safecat("}");
|
||||
this->_objects--;
|
||||
this->_nocomma = false;
|
||||
}
|
||||
void JsonFormatter::beginArray(const char *name) {
|
||||
if(name && strlen(name) > 0) this->appendElem(name);
|
||||
else if(!this->_nocomma) this->_safecat(",");
|
||||
this->_safecat("[");
|
||||
this->_arrays++;
|
||||
this->_nocomma = true;
|
||||
}
|
||||
void JsonFormatter::endArray() {
|
||||
//if(strlen(this->buff) + 1 > this->buffSize - 1) this->send();
|
||||
this->_safecat("]");
|
||||
this->_arrays--;
|
||||
this->_nocomma = false;
|
||||
}
|
||||
|
||||
void JsonFormatter::appendElem(const char *name) {
|
||||
if(!this->_nocomma) this->_safecat(",");
|
||||
if(name && strlen(name) > 0) {
|
||||
this->_safecat(name, true);
|
||||
this->_safecat(":");
|
||||
}
|
||||
this->_nocomma = false;
|
||||
}
|
||||
|
||||
void JsonFormatter::addElem(const char *name, const char *val) {
|
||||
if(!val) return;
|
||||
this->appendElem(name);
|
||||
this->_safecat(val, true);
|
||||
}
|
||||
void JsonFormatter::addElem(const char *val) { this->addElem(nullptr, val); }
|
||||
void JsonFormatter::addElem(float fval) { sprintf(this->_numbuff, "%.4f", fval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(int8_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(uint8_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(int32_t nval) { sprintf(this->_numbuff, "%ld", (long)nval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(uint32_t nval) { sprintf(this->_numbuff, "%lu", (unsigned long)nval); this->_appendNumber(nullptr); }
|
||||
|
||||
/*
|
||||
void JsonFormatter::addElem(int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(nullptr); }
|
||||
void JsonFormatter::addElem(uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(nullptr); }
|
||||
*/
|
||||
void JsonFormatter::addElem(bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(nullptr); }
|
||||
|
||||
void JsonFormatter::addElem(const char *name, float fval) { sprintf(this->_numbuff, "%.4f", fval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, int8_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint8_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, int32_t nval) { sprintf(this->_numbuff, "%ld", (long)nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint32_t nval) { sprintf(this->_numbuff, "%lu", (unsigned long)nval); this->_appendNumber(name); }
|
||||
|
||||
/*
|
||||
void JsonFormatter::addElem(const char *name, int16_t nval) { sprintf(this->_numbuff, "%d", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint16_t nval) { sprintf(this->_numbuff, "%u", nval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, int64_t lval) { sprintf(this->_numbuff, "%lld", (long long)lval); this->_appendNumber(name); }
|
||||
void JsonFormatter::addElem(const char *name, uint64_t lval) { sprintf(this->_numbuff, "%llu", (unsigned long long)lval); this->_appendNumber(name); }
|
||||
*/
|
||||
void JsonFormatter::addElem(const char *name, bool bval) { strcpy(this->_numbuff, bval ? "true" : "false"); this->_appendNumber(name); }
|
||||
|
||||
void JsonFormatter::_safecat(const char *val, bool escape) {
|
||||
size_t len = (escape ? this->calcEscapedLength(val) : strlen(val)) + strlen(this->buff);
|
||||
if(escape) len += 2;
|
||||
if(len >= this->buffSize) {
|
||||
return;
|
||||
}
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
if(escape) this->escapeString(val, &this->buff[strlen(this->buff)]);
|
||||
else strcat(this->buff, val);
|
||||
if(escape) strcat(this->buff, "\"");
|
||||
}
|
||||
void JsonFormatter::_appendNumber(const char *name) { this->appendElem(name); this->_safecat(this->_numbuff); }
|
||||
uint32_t JsonFormatter::calcEscapedLength(const char *raw) {
|
||||
uint32_t len = 0;
|
||||
for(size_t i = strlen(raw); i > 0; i--) {
|
||||
switch(raw[i]) {
|
||||
case '"':
|
||||
case '/':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\\':
|
||||
len += 2;
|
||||
break;
|
||||
default:
|
||||
len++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
void JsonFormatter::escapeString(const char *raw, char *escaped) {
|
||||
for(uint32_t i = 0; i < strlen(raw); i++) {
|
||||
switch(raw[i]) {
|
||||
case '"':
|
||||
strcat(escaped, "\\\"");
|
||||
break;
|
||||
case '/':
|
||||
strcat(escaped, "\\/");
|
||||
break;
|
||||
case '\b':
|
||||
strcat(escaped, "\\b");
|
||||
break;
|
||||
case '\f':
|
||||
strcat(escaped, "\\f");
|
||||
break;
|
||||
case '\n':
|
||||
strcat(escaped, "\\n");
|
||||
break;
|
||||
case '\r':
|
||||
strcat(escaped, "\\r");
|
||||
break;
|
||||
case '\t':
|
||||
strcat(escaped, "\\t");
|
||||
break;
|
||||
case '\\':
|
||||
strcat(escaped, "\\\\");
|
||||
break;
|
||||
default:
|
||||
size_t len = strlen(escaped);
|
||||
escaped[len] = raw[i];
|
||||
escaped[len+1] = 0x00;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/WResp.h
Normal file
74
src/WResp.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include <WebServer.h>
|
||||
#include <WebSocketsServer.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef wresp_h
|
||||
#define wresp_h
|
||||
|
||||
class JsonFormatter {
|
||||
protected:
|
||||
char *buff;
|
||||
size_t buffSize;
|
||||
bool _headersSent = false;
|
||||
uint8_t _objects = 0;
|
||||
uint8_t _arrays = 0;
|
||||
bool _nocomma = true;
|
||||
char _numbuff[25] = {0};
|
||||
virtual void _safecat(const char *val, bool escape = false);
|
||||
void _appendNumber(const char *name);
|
||||
public:
|
||||
void escapeString(const char *raw, char *escaped);
|
||||
uint32_t calcEscapedLength(const char *raw);
|
||||
void beginObject(const char *name = nullptr);
|
||||
void endObject();
|
||||
void beginArray(const char *name = nullptr);
|
||||
void endArray();
|
||||
void appendElem(const char *name = nullptr);
|
||||
|
||||
void addElem(const char* val);
|
||||
void addElem(float fval);
|
||||
void addElem(int8_t nval);
|
||||
void addElem(uint8_t nval);
|
||||
/*
|
||||
void addElem(int32_t nval);
|
||||
void addElem(int16_t nval);
|
||||
void addElem(uint16_t nval);
|
||||
void addElem(unsigned int nval);
|
||||
*/
|
||||
void addElem(int32_t lval);
|
||||
void addElem(uint32_t lval);
|
||||
void addElem(bool bval);
|
||||
|
||||
void addElem(const char* name, float fval);
|
||||
void addElem(const char* name, int8_t nval);
|
||||
void addElem(const char* name, uint8_t nval);
|
||||
/*
|
||||
void addElem(const char* name, int nval);
|
||||
void addElem(const char* name, int16_t nval);
|
||||
void addElem(const char* name, uint16_t nval);
|
||||
void addElem(const char* name, unsigned int nval);
|
||||
*/
|
||||
void addElem(const char* name, int32_t lval);
|
||||
void addElem(const char* name, uint32_t lval);
|
||||
void addElem(const char* name, bool bval);
|
||||
void addElem(const char *name, const char *val);
|
||||
};
|
||||
class JsonResponse : public JsonFormatter {
|
||||
protected:
|
||||
void _safecat(const char *val, bool escape = false) override;
|
||||
public:
|
||||
WebServer *server;
|
||||
void beginResponse(WebServer *server, char *buff, size_t buffSize);
|
||||
void endResponse();
|
||||
void send();
|
||||
};
|
||||
class JsonSockEvent : public JsonFormatter {
|
||||
protected:
|
||||
bool _closed = false;
|
||||
void _safecat(const char *val, bool escape = false) override;
|
||||
public:
|
||||
WebSocketsServer *server = nullptr;
|
||||
void beginEvent(WebSocketsServer *server, const char *evt, char *buff, size_t buffSize);
|
||||
void endEvent(uint8_t clientNum = 255);
|
||||
void closeEvent();
|
||||
};
|
||||
#endif
|
||||
2733
src/Web.cpp
Normal file
2733
src/Web.cpp
Normal file
File diff suppressed because it is too large
Load diff
50
src/Web.h
Normal file
50
src/Web.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include <WebServer.h>
|
||||
#include "Somfy.h"
|
||||
#ifndef webserver_h
|
||||
#define webserver_h
|
||||
class Web {
|
||||
public:
|
||||
bool uploadSuccess = false;
|
||||
void sendCORSHeaders(WebServer &server);
|
||||
void sendCacheHeaders(uint32_t seconds=604800);
|
||||
void startup();
|
||||
void handleLogin(WebServer &server);
|
||||
void handleLogout(WebServer &server);
|
||||
void handleStreamFile(WebServer &server, const char *filename, const char *encoding);
|
||||
void handleController(WebServer &server);
|
||||
void handleLoginContext(WebServer &server);
|
||||
void handleGetRepeaters(WebServer &server);
|
||||
void handleGetRooms(WebServer &server);
|
||||
void handleGetShades(WebServer &server);
|
||||
void handleGetGroups(WebServer &server);
|
||||
void handleShadeCommand(WebServer &server);
|
||||
void handleRepeatCommand(WebServer &server);
|
||||
void handleGroupCommand(WebServer &server);
|
||||
void handleTiltCommand(WebServer &server);
|
||||
void handleDiscovery(WebServer &server);
|
||||
void handleNotFound(WebServer &server);
|
||||
void handleRoom(WebServer &server);
|
||||
void handleShade(WebServer &server);
|
||||
void handleGroup(WebServer &server);
|
||||
void handleSetPositions(WebServer &server);
|
||||
void handleSetSensor(WebServer &server);
|
||||
void handleDownloadFirmware(WebServer &server);
|
||||
void handleBackup(WebServer &server, bool attach = false);
|
||||
void handleReboot(WebServer &server);
|
||||
void handleDeserializationError(WebServer &server, DeserializationError &err);
|
||||
void begin();
|
||||
void loop();
|
||||
void end();
|
||||
// Web Handlers
|
||||
bool createAPIToken(const IPAddress ipAddress, char *token);
|
||||
bool createAPIToken(const char *payload, char *token);
|
||||
bool createAPIPinToken(const IPAddress ipAddress, const char *pin, char *token);
|
||||
bool createAPIPasswordToken(const IPAddress ipAddress, const char *username, const char *password, char *token);
|
||||
bool isAuthenticated(WebServer &server, bool cfg = false);
|
||||
|
||||
//void chunkRoomsResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkShadesResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkGroupsResponse(WebServer &server, const char *elem = nullptr);
|
||||
//void chunkGroupResponse(WebServer &server, SomfyGroup *, const char *prefix = nullptr);
|
||||
};
|
||||
#endif
|
||||
46087
src/esp32.svd
Normal file
46087
src/esp32.svd
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue