mirror of
https://github.com/rstrouse/ESPSomfy-RTS.git
synced 2025-12-12 18:42:10 +01:00
Version 1.003 Changes
This commit is contained in:
parent
a63e881a63
commit
72d1cdcccb
15 changed files with 48597 additions and 1691 deletions
|
|
@ -166,6 +166,18 @@ bool NTPSettings::apply() {
|
|||
setenv("TZ", this->posixZone, 1);
|
||||
return true;
|
||||
}
|
||||
WifiSettings::WifiSettings() {
|
||||
uint32_t chipId = 0;
|
||||
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);
|
||||
}
|
||||
|
||||
bool WifiSettings::begin() {
|
||||
this->load();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#ifndef configsettings_h
|
||||
#define configsettings_h
|
||||
|
||||
#define FW_VERSION "v0.90.1"
|
||||
#define FW_VERSION "v1.03.1"
|
||||
enum DeviceStatus {
|
||||
DS_OK = 0,
|
||||
DS_ERROR = 1,
|
||||
|
|
@ -35,6 +35,8 @@ class NTPSettings: BaseSettings {
|
|||
};
|
||||
class WifiSettings: BaseSettings {
|
||||
public:
|
||||
WifiSettings();
|
||||
char serverId[10] = "";
|
||||
char hostname[32] = "";
|
||||
char ssid[32] = "";
|
||||
char passphrase[32] = "";
|
||||
|
|
|
|||
91
MQTT.cpp
91
MQTT.cpp
|
|
@ -26,6 +26,68 @@ bool MQTTClass::loop() {
|
|||
mqttClient.loop();
|
||||
return true;
|
||||
}
|
||||
void MQTTClass::receive(const char *topic, byte*payload, uint32_t length) {
|
||||
//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
|
||||
uint16_t ndx = strlen(topic) - 1;
|
||||
// ------------------+
|
||||
// shades/1/target/set
|
||||
while(ndx >= 0 && topic[ndx] != '/') ndx--; // Back off the set command
|
||||
uint16_t end_command = --ndx;
|
||||
// --------------+----
|
||||
// shades/1/target/set
|
||||
while(ndx >= 0 && topic[ndx] != '/') ndx--; // Get the start of the leaf.
|
||||
// --------+----------
|
||||
// shades/1/target/set
|
||||
uint16_t start_command = ndx + 1;
|
||||
uint16_t id_end = --ndx;
|
||||
while(ndx >= 0 && topic[ndx] != '/') ndx--;
|
||||
// ------+------------
|
||||
// shades/1/target/set
|
||||
uint16_t id_start = ndx + 1;
|
||||
char shadeId[4];
|
||||
char command[32];
|
||||
memset(command, 0x00, sizeof(command));
|
||||
memset(shadeId, 0x00, sizeof(shadeId));
|
||||
for(uint16_t i = 0;id_start <= id_end; i++)
|
||||
shadeId[i] = topic[id_start++];
|
||||
for(uint16_t i = 0;start_command <= end_command; i++)
|
||||
command[i] = topic[start_command++];
|
||||
|
||||
char value[10];
|
||||
memset(value, 0x00, sizeof(value));
|
||||
for(uint8_t i = 0; i < length; i++)
|
||||
value[i] = payload[i];
|
||||
|
||||
Serial.print("MQTT Command:[");
|
||||
Serial.print(command);
|
||||
Serial.print("] shadeId:");
|
||||
Serial.print(shadeId);
|
||||
Serial.print(" value:");
|
||||
Serial.println(value);
|
||||
SomfyShade* shade = somfy.getShadeById(atoi(shadeId));
|
||||
if (shade) {
|
||||
int val = atoi(value);
|
||||
if(strncmp(command, "target", sizeof(command)) == 0) {
|
||||
if(val >= 0 && val <= 100)
|
||||
shade->moveToTarget(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
bool MQTTClass::connect() {
|
||||
if(mqttClient.connected()) {
|
||||
if(!settings.MQTT.enabled)
|
||||
|
|
@ -42,6 +104,9 @@ bool MQTTClass::connect() {
|
|||
Serial.print("Successfully connected MQTT client ");
|
||||
Serial.println(this->clientId);
|
||||
somfy.publish();
|
||||
this->subscribe("shades/+/target/set");
|
||||
this->subscribe("shades/+/direction/set");
|
||||
mqttClient.setCallback(MQTTClass::receive);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
|
|
@ -56,10 +121,36 @@ bool MQTTClass::connect() {
|
|||
}
|
||||
bool MQTTClass::disconnect() {
|
||||
if(mqttClient.connected()) {
|
||||
this->unsubscribe("shades/+/target/set");
|
||||
this->unsubscribe("shades/+/direction/set");
|
||||
mqttClient.disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::unsubscribe(const char *topic) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[64];
|
||||
if(strlen(settings.MQTT.rootTopic) > 0)
|
||||
snprintf(top, sizeof(top), "%s/%s", settings.MQTT.rootTopic, topic);
|
||||
else
|
||||
strlcpy(top, topic, sizeof(top));
|
||||
return mqttClient.unsubscribe(top);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool MQTTClass::subscribe(const char *topic) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[64];
|
||||
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) {
|
||||
if(mqttClient.connected()) {
|
||||
char top[64];
|
||||
|
|
|
|||
4
MQTT.h
4
MQTT.h
|
|
@ -20,6 +20,10 @@ class MQTTClass {
|
|||
bool publish(const char *topic, int8_t val);
|
||||
bool publish(const char *topic, uint32_t val);
|
||||
bool publish(const char *topic, uint16_t val);
|
||||
bool subscribe(const char *topic);
|
||||
bool unsubscribe(const char *topic);
|
||||
static void receive(const char *topic, byte *payload, uint32_t length);
|
||||
|
||||
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
31
Network.cpp
31
Network.cpp
|
|
@ -45,9 +45,14 @@ void Network::loop() {
|
|||
}
|
||||
void Network::emitSockets() {
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
|
||||
if(abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 2 || WiFi.channel() != this->lastChannel) {
|
||||
char buf[50];
|
||||
sprintf(buf, "{\"ssid\":\"%s\", \"strength\":%d, \"channel\":%d}", WiFi.SSID(), WiFi.RSSI(), WiFi.channel());
|
||||
sockEmit.sendToClients("wifiStrength", buf);
|
||||
this->lastRSSI = WiFi.RSSI();
|
||||
this->lastChannel = WiFi.channel();
|
||||
}
|
||||
}
|
||||
else
|
||||
sockEmit.sendToClients("wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}");
|
||||
|
|
@ -88,29 +93,32 @@ void Network::setConnected() {
|
|||
SSDP.setHTTPPort(80);
|
||||
SSDP.setSchemaURL(0, "upnp.xml");
|
||||
SSDP.setChipId(0, this->getChipId());
|
||||
SSDP.setDeviceType(0, "urn:schemas-rstrouse-org:device:SomfyServer:1");
|
||||
SSDP.setDeviceType(0, "urn:schemas-rstrouse-org:device:ESPSomfyRTS:1");
|
||||
SSDP.setName(0, settings.WIFI.hostname);
|
||||
|
||||
//SSDP.setSerialNumber(0, "C2496952-5610-47E6-A968-2FC19737A0DB");
|
||||
//SSDP.setUUID(0, settings.uuid);
|
||||
SSDP.setModelName(0, "Somfy Server");
|
||||
SSDP.setModelName(0, "ESPSomfy RTS");
|
||||
SSDP.setModelNumber(0, "SS v1");
|
||||
SSDP.setModelURL(0, "https://github.com/rstrouse/ESP32-somfyServer");
|
||||
SSDP.setModelURL(0, "https://github.com/rstrouse/ESPSomfy-RTS");
|
||||
SSDP.setManufacturer(0, "rstrouse");
|
||||
SSDP.setManufacturerURL(0, "https://github.com/rstrouse");
|
||||
SSDP.setURL(0, "/");
|
||||
if(MDNS.begin(settings.WIFI.hostname)) {
|
||||
Serial.println(F("MDNS Responder Started"));
|
||||
MDNS.addService("_http", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_http", "_tcp", "board", "ESP32");
|
||||
//MDNS.addServiceTxt("_osc", "_udp", "board", settings.WIFI.hostname);
|
||||
Serial.printf("MDNS Responder Started: serverId=%s\n", settings.WIFI.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.WIFI.serverId));
|
||||
MDNS.addServiceTxt("espsomfy_rts", "tcp", "model", "ESPSomfyRTS");
|
||||
MDNS.addServiceTxt("espsomfy_rts", "tcp", "version", String(settings.fwVersion));
|
||||
}
|
||||
if(settings.WIFI.ssdpBroadcast) {
|
||||
if( SSDP.begin()) Serial.println("SSDP Client Started...");
|
||||
if(SSDP.begin()) Serial.println("SSDP Client Started...");
|
||||
}
|
||||
else if(SSDP.isStarted) SSDP.end();
|
||||
|
||||
//digitalWrite(LED_BUILTIN, HIGH);
|
||||
this->emitSockets();
|
||||
}
|
||||
bool Network::connect() {
|
||||
|
|
@ -243,7 +251,7 @@ bool Network::openSoftAP() {
|
|||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
delay(100);
|
||||
WiFi.softAP("Somfy Controller", "");
|
||||
WiFi.softAP("ESPSomfy RTS", "");
|
||||
Serial.println("Initializing AP for credentials modification");
|
||||
Serial.println();
|
||||
Serial.print("SoftAP IP: ");
|
||||
|
|
@ -251,6 +259,7 @@ bool Network::openSoftAP() {
|
|||
pinMode(D0, INPUT_PULLUP);
|
||||
long startTime = millis();
|
||||
int c = 0;
|
||||
|
||||
while ((WiFi.status() != WL_CONNECTED))
|
||||
{
|
||||
for(int i = 0; i < 3; i++) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
class Network {
|
||||
protected:
|
||||
unsigned long lastEmit = 0;
|
||||
int lastRSSI = 0;
|
||||
int lastChannel = 0;
|
||||
public:
|
||||
String ssid;
|
||||
String mac;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
#include <WebSocketsServer.h>
|
||||
#include "Sockets.h"
|
||||
#include "ConfigSettings.h"
|
||||
#include "Somfy.h"
|
||||
|
||||
|
||||
extern ConfigSettings settings;
|
||||
extern SomfyShadeController somfy;
|
||||
|
||||
WebSocketsServer sockServer = WebSocketsServer(8080);
|
||||
char g_buffer[1024];
|
||||
|
||||
|
|
@ -62,7 +65,7 @@ void SocketEmitter::wsEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t
|
|||
// Send all the current Sensor readings to the client.
|
||||
sockServer.sendTXT(num, "Connected");
|
||||
settings.emitSockets();
|
||||
//settings.Inputs.emitSocket(num);
|
||||
somfy.emitState(num);
|
||||
}
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
|
|
|
|||
461
Somfy.cpp
461
Somfy.cpp
|
|
@ -22,6 +22,14 @@ extern MQTTClass mqtt;
|
|||
#define RECEIVE_ATTR
|
||||
#endif
|
||||
|
||||
int sort_asc(const void *cmp1, const void *cmp2) {
|
||||
int a = *((uint8_t *)cmp1);
|
||||
int b = *((uint8_t *)cmp2);
|
||||
if(a == b) return 0;
|
||||
else if(a < b) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int interruptPin = 0;
|
||||
static uint8_t bit_length = 56;
|
||||
somfy_commands translateSomfyCommand(const String& string) {
|
||||
|
|
@ -141,7 +149,9 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
Serial.print(" DECCS:");
|
||||
Serial.println(this->checksum);
|
||||
*/
|
||||
Serial.print("ADDR:");
|
||||
Serial.print("KEY:");
|
||||
Serial.print(this->encKey);
|
||||
Serial.print(" ADDR:");
|
||||
Serial.print(this->remoteAddress);
|
||||
Serial.print(" CMD:");
|
||||
Serial.print(translateSomfyCommand(this->cmd));
|
||||
|
|
@ -150,7 +160,9 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
}
|
||||
else {
|
||||
Serial.print("INVALID FRAME ");
|
||||
Serial.print("ADDR:");
|
||||
Serial.print("KEY:");
|
||||
Serial.print(this->encKey);
|
||||
Serial.print(" ADDR:");
|
||||
Serial.print(this->remoteAddress);
|
||||
Serial.print(" CMD:");
|
||||
Serial.print(translateSomfyCommand(this->cmd));
|
||||
|
|
@ -180,15 +192,15 @@ void somfy_frame_t::decodeFrame(byte* frame) {
|
|||
Serial.println();
|
||||
}
|
||||
}
|
||||
void somfy_frame_t::encodeFrame(const uint32_t address, const somfy_commands cmd, const uint16_t rcode, byte* frame) {
|
||||
void somfy_frame_t::encodeFrame(byte *frame) {
|
||||
const byte btn = static_cast<byte>(cmd);
|
||||
frame[0] = 0xA7; // Encryption key. Doesn't matter much
|
||||
frame[0] = this->encKey; // Encryption key. Doesn't matter much
|
||||
frame[1] = btn << 4; // Which button did you press? The 4 LSB will be the checksum
|
||||
frame[2] = rcode >> 8; // Rolling code (big endian)
|
||||
frame[3] = rcode; // Rolling code
|
||||
frame[4] = address >> 16; // Remote address
|
||||
frame[5] = address >> 8; // Remote address
|
||||
frame[6] = address; // Remote address
|
||||
frame[2] = this->rollingCode >> 8; // Rolling code (big endian)
|
||||
frame[3] = this->rollingCode; // Rolling code
|
||||
frame[4] = this->remoteAddress >> 16; // Remote address
|
||||
frame[5] = this->remoteAddress >> 8; // Remote address
|
||||
frame[6] = this->remoteAddress; // Remote address
|
||||
byte checksum = 0;
|
||||
for (byte i = 0; i < 7; i++) {
|
||||
checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
|
||||
|
|
@ -223,7 +235,7 @@ void SomfyShadeController::end() { this->transceiver.disableReceive(); }
|
|||
SomfyShadeController::SomfyShadeController() {
|
||||
memset(this->m_shadeIds, 255, sizeof(this->m_shadeIds));
|
||||
uint64_t mac = ESP.getEfuseMac();
|
||||
this->startingAddress = mac & 0xFFFF00;
|
||||
this->startingAddress = mac & 0x0FFFFF;
|
||||
}
|
||||
SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -239,18 +251,32 @@ SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
|
|||
}
|
||||
bool SomfyShadeController::begin() {
|
||||
// Load up all the configuration data.
|
||||
Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade));
|
||||
pref.begin("Shades");
|
||||
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
this->transceiver.begin();
|
||||
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) Serial.print(",");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
}
|
||||
Serial.println();
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(i != 0) Serial.print(",");
|
||||
Serial.print(this->m_shadeIds[i]);
|
||||
}
|
||||
Serial.println();
|
||||
|
||||
uint8_t id = 0;
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(this->m_shadeIds[i] == id) this->m_shadeIds[i] = 255;
|
||||
id = this->m_shadeIds[i];
|
||||
SomfyShade *shade = &this->shades[i];
|
||||
shade->setShadeId(id);
|
||||
if(id == 255) continue;
|
||||
if(id == 255) {
|
||||
continue;
|
||||
}
|
||||
shade->load();
|
||||
}
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -355,6 +381,18 @@ void SomfyShade::checkMovement() {
|
|||
}
|
||||
}
|
||||
this->position = floor(this->currentPos * 100);
|
||||
if(this->seekingPos && this->position >= this->target) {
|
||||
Serial.print("Stopping Shade:");
|
||||
Serial.print(this->name);
|
||||
Serial.print(" at ");
|
||||
Serial.print(this->position);
|
||||
Serial.print("% target ");
|
||||
Serial.print(this->target);
|
||||
Serial.println("%");
|
||||
this->sendCommand(somfy_commands::My);
|
||||
this->seekingPos = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else if(this->direction < 0) {
|
||||
|
|
@ -383,6 +421,17 @@ void SomfyShade::checkMovement() {
|
|||
}
|
||||
}
|
||||
this->position = floor(this->currentPos * 100);
|
||||
if(this->seekingPos && this->position <= this->target) {
|
||||
Serial.print("Stopping Shade:");
|
||||
Serial.print(this->name);
|
||||
Serial.print(" at ");
|
||||
Serial.print(this->position);
|
||||
Serial.print("% target ");
|
||||
Serial.print(this->target);
|
||||
Serial.println("%");
|
||||
this->sendCommand(somfy_commands::My);
|
||||
this->seekingPos = false;
|
||||
}
|
||||
}
|
||||
if(currDir != this->direction || currPos != this->position) {
|
||||
// We need to emit on the socket that our state has changed.
|
||||
|
|
@ -402,6 +451,7 @@ void SomfyShade::checkMovement() {
|
|||
void SomfyShade::load() {
|
||||
char shadeKey[15];
|
||||
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES];
|
||||
memset(linkedAddresses, 0x00, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
|
||||
// Now load up each of the shades into memory.
|
||||
Serial.print("key:");
|
||||
|
|
@ -414,6 +464,7 @@ void SomfyShade::load() {
|
|||
this->setRemoteAddress(pref.getULong("remoteAddress", 0));
|
||||
this->currentPos = pref.getFloat("currentPos", 0);
|
||||
this->position = (uint8_t)floor(this->currentPos * 100);
|
||||
this->target = this->position;
|
||||
pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses));
|
||||
pref.end();
|
||||
Serial.print("shadeId:");
|
||||
|
|
@ -424,7 +475,6 @@ void SomfyShade::load() {
|
|||
Serial.print(this->getRemoteAddress());
|
||||
Serial.print(" position:");
|
||||
Serial.println(this->position);
|
||||
|
||||
for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) {
|
||||
SomfyLinkedRemote &lremote = this->linkedRemotes[j];
|
||||
lremote.setRemoteAddress(linkedAddresses[j]);
|
||||
|
|
@ -434,12 +484,6 @@ void SomfyShade::load() {
|
|||
}
|
||||
|
||||
}
|
||||
void SomfyShade::emitConfig() {
|
||||
DynamicJsonDocument doc(256);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
this->toJSON(obj);
|
||||
sockEmit.sendToClients("somfyShade", obj);
|
||||
}
|
||||
void SomfyShade::publish() {
|
||||
if(mqtt.connected()) {
|
||||
char topic[32];
|
||||
|
|
@ -459,12 +503,14 @@ void SomfyShade::publish() {
|
|||
mqtt.publish(topic, this->lastRollingCode);
|
||||
}
|
||||
}
|
||||
void SomfyShade::emitState() {
|
||||
char buf[200];
|
||||
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
|
||||
void SomfyShade::emitState(uint8_t num, const char *evt) {
|
||||
char buf[220];
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "Shade_%u", this->shadeId);
|
||||
sprintf(buf, "{\"shadeId\":%d, \"remoteAddress\":%d, \"name\":\"%s\", \"direction\":%d, \"position\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position);
|
||||
sockEmit.sendToClients("somfyShadeState", buf);
|
||||
sprintf(buf, "{\"shadeId\":%d, \"remoteAddress\":%d, \"name\":\"%s\", \"direction\":%d, \"position\":%d, \"target\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position, this->target);
|
||||
if(num >= 255) sockEmit.sendToClients(evt, buf);
|
||||
else sockEmit.sendToClient(num, evt, buf);
|
||||
if(mqtt.connected()) {
|
||||
char topic[32];
|
||||
snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId);
|
||||
|
|
@ -477,7 +523,7 @@ void SomfyShade::emitState() {
|
|||
mqtt.publish(topic, this->lastRollingCode);
|
||||
}
|
||||
}
|
||||
void SomfyShade::processFrame(somfy_frame_t &frame) {
|
||||
void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
|
||||
bool hasRemote = this->getRemoteAddress() == frame.remoteAddress;
|
||||
if(!hasRemote) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
|
|
@ -490,14 +536,18 @@ void SomfyShade::processFrame(somfy_frame_t &frame) {
|
|||
}
|
||||
if(!hasRemote) return;
|
||||
int8_t dir = 0;
|
||||
// If the frame came from the radio it cannot be seeing a position. This means that the target will be set.
|
||||
if(!internal) this->seekingPos = false;
|
||||
// At this point we are not processing the combo buttons
|
||||
// will need to see what the shade does when you press both.
|
||||
switch(frame.cmd) {
|
||||
case somfy_commands::Up:
|
||||
dir = -1;
|
||||
if(!internal) this->target = 0;
|
||||
break;
|
||||
case somfy_commands::Down:
|
||||
dir = 1;
|
||||
if(!internal) this->target = 100;
|
||||
break;
|
||||
default:
|
||||
dir = 0;
|
||||
|
|
@ -512,6 +562,7 @@ void SomfyShade::setMovement(int8_t dir) {
|
|||
this->startPos = this->currentPos;
|
||||
this->moveStart = 0;
|
||||
this->direction = dir;
|
||||
this->emitState();
|
||||
}
|
||||
else if(this->direction != dir) {
|
||||
this->moveStart = millis();
|
||||
|
|
@ -523,6 +574,39 @@ void SomfyShade::setMovement(int8_t dir) {
|
|||
this->emitState();
|
||||
}
|
||||
}
|
||||
void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
||||
if(cmd == somfy_commands::Up) {
|
||||
this->target = 0;
|
||||
this->seekingPos = false;
|
||||
}
|
||||
else if(cmd == somfy_commands::Down) {
|
||||
this->target = 100;
|
||||
this->seekingPos = false;
|
||||
}
|
||||
else if(cmd == somfy_commands::My) {
|
||||
this->target = this->position;
|
||||
this->seekingPos = false;
|
||||
}
|
||||
SomfyRemote::sendCommand(cmd, repeat);
|
||||
}
|
||||
void SomfyShade::moveToTarget(uint8_t target) {
|
||||
int8_t newDir = 0;
|
||||
somfy_commands cmd = somfy_commands::My;
|
||||
if(target < this->position)
|
||||
cmd = somfy_commands::Up;
|
||||
else if(target > this->position)
|
||||
cmd = somfy_commands::Down;
|
||||
Serial.print("Moving to ");
|
||||
Serial.print(target);
|
||||
Serial.print("% from ");
|
||||
Serial.print(this->position);
|
||||
Serial.print("% using ");
|
||||
Serial.println(translateSomfyCommand(cmd));
|
||||
this->target = target;
|
||||
this->seekingPos = true;
|
||||
SomfyRemote::sendCommand(cmd);
|
||||
}
|
||||
|
||||
bool SomfyShade::save() {
|
||||
char shadeKey[15];
|
||||
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
|
||||
|
|
@ -564,10 +648,10 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
|
|||
return true;
|
||||
}
|
||||
bool SomfyShade::toJSON(JsonObject &obj) {
|
||||
Serial.print("Serializing Shade:");
|
||||
Serial.print(this->getShadeId());
|
||||
Serial.print(" ");
|
||||
Serial.println(this->name);
|
||||
//Serial.print("Serializing Shade:");
|
||||
//Serial.print(this->getShadeId());
|
||||
//Serial.print(" ");
|
||||
//Serial.println(this->name);
|
||||
obj["shadeId"] = this->getShadeId();
|
||||
obj["name"] = this->name;
|
||||
obj["remoteAddress"] = this->m_remoteAddress;
|
||||
|
|
@ -577,6 +661,7 @@ bool SomfyShade::toJSON(JsonObject &obj) {
|
|||
obj["remotePrefId"] = this->getRemotePrefId();
|
||||
obj["lastRollingCode"] = this->lastRollingCode;
|
||||
obj["position"] = this->position;
|
||||
obj["target"] = this->target;
|
||||
SomfyRemote::toJSON(obj);
|
||||
JsonArray arr = obj.createNestedArray("linkedRemotes");
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) {
|
||||
|
|
@ -596,9 +681,16 @@ bool SomfyRemote::toJSON(JsonObject &obj) {
|
|||
}
|
||||
void SomfyRemote::setRemoteAddress(uint32_t address) { this->m_remoteAddress = address; snprintf(this->m_remotePrefId, sizeof(this->m_remotePrefId), "_%lu", (unsigned long)this->m_remoteAddress); }
|
||||
uint32_t SomfyRemote::getRemoteAddress() { return this->m_remoteAddress; }
|
||||
void SomfyShadeController::processFrame(somfy_frame_t &frame) {
|
||||
void SomfyShadeController::processFrame(somfy_frame_t &frame, bool internal) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++)
|
||||
this->shades[i].processFrame(frame);
|
||||
this->shades[i].processFrame(frame, internal);
|
||||
}
|
||||
void SomfyShadeController::emitState(uint8_t num) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade *shade = &this->shades[i];
|
||||
if(shade->getShadeId() == 255) continue;
|
||||
shade->emitState(num);
|
||||
}
|
||||
}
|
||||
void SomfyShadeController::publish() {
|
||||
StaticJsonDocument<128> doc;
|
||||
|
|
@ -612,15 +704,26 @@ void SomfyShadeController::publish() {
|
|||
mqtt.publish("shades", arr);
|
||||
}
|
||||
uint8_t SomfyShadeController::getNextShadeId() {
|
||||
uint8_t maxId = 0;
|
||||
uint8_t lastId = 0;
|
||||
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
uint8_t id = this->m_shadeIds[i];
|
||||
if(id >= 255) continue;
|
||||
maxId = max(lastId, id);
|
||||
uint8_t nextId = 0;
|
||||
// There is no shortcut for this since the deletion of
|
||||
// a shade in the middle makes all of this very difficult.
|
||||
for(uint8_t i = 1; i < SOMFY_MAX_SHADES - 1; i++) {
|
||||
bool id_exists = false;
|
||||
for(uint8_t j = 0; j < SOMFY_MAX_SHADES; j++) {
|
||||
SomfyShade *shade = &this->shades[j];
|
||||
if(shade->getShadeId() == i) {
|
||||
id_exists = true;
|
||||
break;
|
||||
}
|
||||
return maxId + 1;
|
||||
}
|
||||
if(!id_exists) {
|
||||
Serial.print("Got next Shade Id:");
|
||||
Serial.print(i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 255;
|
||||
}
|
||||
uint8_t SomfyShadeController::shadeCount() {
|
||||
uint8_t count = 0;
|
||||
|
|
@ -629,164 +732,103 @@ uint8_t SomfyShadeController::shadeCount() {
|
|||
}
|
||||
return count;
|
||||
}
|
||||
uint32_t SomfyShadeController::getNextRemoteAddress(uint8_t shadeId) {
|
||||
uint32_t address = this->startingAddress + shadeId;
|
||||
uint8_t i = 0;
|
||||
while(i < SOMFY_MAX_SHADES) {
|
||||
if(this->shades[i].getShadeId() != 255) {
|
||||
if(this->shades[i].getRemoteAddress() == address) {
|
||||
address++;
|
||||
i = 0; // Start over we cannot share addresses.
|
||||
}
|
||||
else i++;
|
||||
}
|
||||
else i++;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
SomfyShade *SomfyShadeController::addShade(JsonObject &obj) {
|
||||
SomfyShade *shade = this->addShade();
|
||||
if(shade) {
|
||||
shade->fromJSON(obj);
|
||||
shade->save();
|
||||
shade->emitState("shadeAdded");
|
||||
}
|
||||
return shade;
|
||||
}
|
||||
SomfyShade *SomfyShadeController::addShade() {
|
||||
uint8_t shadeId = getNextShadeId();
|
||||
uint8_t shadeId = this->getNextShadeId();
|
||||
SomfyShade *shade = nullptr;
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() == 255)
|
||||
if(this->shades[i].getShadeId() == 255) {
|
||||
shade = &this->shades[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(shade) {
|
||||
shade->setShadeId(shadeId);
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(this->m_shadeIds[i] == 255) this->m_shadeIds[i] = shadeId;
|
||||
this->m_shadeIds[i] = this->shades[i].getShadeId();
|
||||
}
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
uint8_t id = 0;
|
||||
// Eliminate the duplicates.
|
||||
// This little diddy is about a bug I had previously that left duplicates in the
|
||||
// sorted array. So we will walk the sorted array until we hit a duplicate where the previous
|
||||
// value == the current value. Set it to 255 then sort the array again.
|
||||
// 1,1,2,2,3,3,255...
|
||||
bool hadDups = false;
|
||||
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
|
||||
if(id == this->m_shadeIds[i]) this->m_shadeIds[i] = 255;
|
||||
if(this->m_shadeIds[i] == 255) break;
|
||||
if(id == this->m_shadeIds[i]) {
|
||||
id = this->m_shadeIds[i];
|
||||
this->m_shadeIds[i] = 255;
|
||||
hadDups = true;
|
||||
}
|
||||
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
else {
|
||||
id = this->m_shadeIds[i];
|
||||
}
|
||||
}
|
||||
if(hadDups) sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
}
|
||||
return shade;
|
||||
}
|
||||
|
||||
void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) {
|
||||
somfy_frame_t frame;
|
||||
frame.rollingCode = this->getNextRollingCode();
|
||||
frame.remoteAddress = this->getRemoteAddress();
|
||||
frame.cmd = cmd;
|
||||
somfy.sendFrame(frame, repeat);
|
||||
somfy.processFrame(frame, true);
|
||||
}
|
||||
void SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat) {
|
||||
somfy.transceiver.beginTransmit();
|
||||
uint16_t rcode = this->getNextRollingCode();
|
||||
Serial.println("------------- Sending -------------");
|
||||
//Serial.println("----------- Sending Raw -------------");
|
||||
Serial.print("CMD:");
|
||||
Serial.print(translateSomfyCommand(cmd));
|
||||
Serial.print(translateSomfyCommand(frame.cmd));
|
||||
Serial.print(" ADDR:");
|
||||
Serial.print(this->getRemoteAddress());
|
||||
Serial.print(frame.remoteAddress);
|
||||
Serial.print(" RCODE:");
|
||||
Serial.println(rcode);
|
||||
Serial.print(frame.rollingCode);
|
||||
Serial.print(" REPEAT:");
|
||||
Serial.println(repeat);
|
||||
|
||||
byte frame[7];
|
||||
this->encodeFrame(frame, cmd, rcode);
|
||||
this->sendFrame(frame, 2);
|
||||
byte frm[10];
|
||||
frame.encodeFrame(frm);
|
||||
this->transceiver.sendFrame(frm, 2);
|
||||
for(uint8_t i = 0; i < repeat; i++) {
|
||||
sendFrame(frame, 7);
|
||||
this->transceiver.sendFrame(frm, 7);
|
||||
}
|
||||
somfy.transceiver.endTransmit();
|
||||
somfy_frame_t rx;
|
||||
this->decodeFrame(frame, &rx);
|
||||
//rx.print();
|
||||
somfy.processFrame(rx);
|
||||
this->transceiver.endTransmit();
|
||||
}
|
||||
void SomfyRemote::decodeFrame(byte *frame, somfy_frame_t *rx) {
|
||||
byte decoded[7];
|
||||
decoded[0] = frame[0];
|
||||
for (byte i = 1; i < 7; i++) {
|
||||
decoded[i] = frame[i] ^ frame[i-1];
|
||||
}
|
||||
byte checksum = 0;
|
||||
// We only want the upper nibble for the command byte.
|
||||
for (byte i = 0; i < 7; i++) {
|
||||
if(i == 1) checksum = checksum ^ (decoded[i] >> 4);
|
||||
else checksum = checksum ^ decoded[i] ^ (decoded[i] >> 4);
|
||||
}
|
||||
checksum &= 0b1111; // We keep the last 4 bits only
|
||||
|
||||
rx->checksum = decoded[1] & 0b1111;
|
||||
rx->encKey = decoded[0];
|
||||
rx->cmd = (somfy_commands)(decoded[1] >> 4);
|
||||
rx->rollingCode = decoded[3] + (decoded[2] << 8);
|
||||
rx->remoteAddress = (decoded[6] + (decoded[5] << 8) + (decoded[4] << 16));
|
||||
rx->valid = rx->checksum == checksum;
|
||||
Serial.println(" KEY 1 2 3 4 5 6 ");
|
||||
Serial.println("--------------------------------");
|
||||
Serial.print("ENC ");
|
||||
for(byte i = 0; i < 7; i++) {
|
||||
if(frame[i] < 10)
|
||||
Serial.print(" ");
|
||||
else if(frame[i] < 100)
|
||||
Serial.print(" ");
|
||||
Serial.print(frame[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print("DEC ");
|
||||
for(byte i = 0; i < 7; i++) {
|
||||
if(decoded[i] < 10)
|
||||
Serial.print(" ");
|
||||
else if(decoded[i] < 100)
|
||||
Serial.print(" ");
|
||||
Serial.print(decoded[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
Serial.print("VALID:");
|
||||
Serial.print(rx->valid ? "true" : "false");
|
||||
Serial.print(" ENCCS:");
|
||||
Serial.print(checksum);
|
||||
Serial.print(" DECCS:");
|
||||
Serial.println(rx->checksum);
|
||||
}
|
||||
void SomfyRemote::encodeFrame(byte *frame, somfy_commands cmd, uint16_t rcode) {
|
||||
const byte btn = static_cast<byte>(cmd);
|
||||
const uint32_t address = this->getRemoteAddress();
|
||||
frame[0] = 0xA7; // Encryption key. Doesn't matter much
|
||||
frame[1] = btn << 4; // Which button did you press? The 4 LSB will be the checksum
|
||||
frame[2] = rcode >> 8; // Rolling code (big endian)
|
||||
frame[3] = rcode; // Rolling code
|
||||
frame[4] = address >> 16; // Remote address
|
||||
frame[5] = address >> 8; // Remote address
|
||||
frame[6] = address; // Remote address
|
||||
byte checksum = 0;
|
||||
for (byte i = 0; i < 7; i++) {
|
||||
checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
|
||||
}
|
||||
checksum &= 0b1111; // We keep the last 4 bits only
|
||||
// Checksum integration
|
||||
frame[1] |= checksum;
|
||||
// Obfuscation: a XOR of all the bytes
|
||||
for (byte i = 1; i < 7; i++) {
|
||||
frame[i] ^= frame[i - 1];
|
||||
}
|
||||
}
|
||||
void SomfyRemote::sendFrame(byte *frame, byte sync) {
|
||||
if (sync == 2) { // Only with the first frame.
|
||||
// Wake-up pulse & Silence
|
||||
this->sendHigh(9415);
|
||||
this->sendLow(9565);
|
||||
delay(80);
|
||||
}
|
||||
// Hardware sync: two sync for the first frame, seven for the following ones.
|
||||
for (int i = 0; i < sync; i++) {
|
||||
this->sendHigh(4 * SYMBOL);
|
||||
this->sendLow(4 * SYMBOL);
|
||||
}
|
||||
// Software sync
|
||||
this->sendHigh(4550);
|
||||
this->sendLow(SYMBOL);
|
||||
// Data: bits are sent one by one, starting with the MSB.
|
||||
for (byte i = 0; i < bit_length; i++) {
|
||||
if (((frame[i / 8] >> (7 - (i % 8))) & 1) == 1) {
|
||||
this->sendLow(SYMBOL);
|
||||
this->sendHigh(SYMBOL);
|
||||
} else {
|
||||
this->sendHigh(SYMBOL);
|
||||
this->sendLow(SYMBOL);
|
||||
}
|
||||
}
|
||||
// Inter-frame silence
|
||||
this->sendLow(415);
|
||||
delay(30);
|
||||
}
|
||||
void SomfyRemote::sendHigh(uint16_t durationInMicroseconds) {
|
||||
digitalWrite(somfy.transceiver.config.TXPin, HIGH);
|
||||
delayMicroseconds(durationInMicroseconds);
|
||||
}
|
||||
void SomfyRemote::sendLow(uint16_t durationInMicroseconds) {
|
||||
digitalWrite(somfy.transceiver.config.TXPin, LOW);
|
||||
delayMicroseconds(durationInMicroseconds);
|
||||
}
|
||||
bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
if(this->shades[i].getShadeId() == shadeId) {
|
||||
shades[i].emitState("shadeRemoved");
|
||||
this->shades[i].setShadeId(255);
|
||||
}
|
||||
}
|
||||
|
|
@ -795,7 +837,8 @@ bool SomfyShadeController::deleteShade(uint8_t shadeId) {
|
|||
this->m_shadeIds[i] = 255;
|
||||
}
|
||||
}
|
||||
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
//qsort(this->m_shadeIds, sizeof(this->m_shadeIds)/sizeof(this->m_shadeIds[0]), sizeof(this->m_shadeIds[0]), sort_asc);
|
||||
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.begin("Shades");
|
||||
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
|
||||
pref.end();
|
||||
|
|
@ -819,7 +862,6 @@ uint16_t SomfyRemote::setRollingCode(uint16_t code) {
|
|||
return code;
|
||||
}
|
||||
bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) {
|
||||
Serial.println("Setting Controller Json");
|
||||
doc["maxShades"] = SOMFY_MAX_SHADES;
|
||||
doc["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES;
|
||||
doc["startingAddress"] = this->startingAddress;
|
||||
|
|
@ -836,13 +878,25 @@ bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) {
|
|||
return true;
|
||||
}
|
||||
bool SomfyShadeController::toJSON(JsonObject &obj) {
|
||||
Serial.print("Setting Transceiver Json ");
|
||||
obj["maxShades"] = SOMFY_MAX_SHADES;
|
||||
obj["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES;
|
||||
obj["startingAddress"] = this->startingAddress;
|
||||
JsonObject oradio = obj.createNestedObject("transceiver");
|
||||
this->transceiver.toJSON(oradio);
|
||||
JsonArray arr = obj.createNestedArray("shades");
|
||||
this->toJSON(arr);
|
||||
/*
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade &shade = this->shades[i];
|
||||
if(shade.getShadeId() != 255) {
|
||||
JsonObject oshade = arr.createNestedObject();
|
||||
shade.toJSON(oshade);
|
||||
}
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
bool SomfyShadeController::toJSON(JsonArray &arr) {
|
||||
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
|
||||
SomfyShade &shade = this->shades[i];
|
||||
if(shade.getShadeId() != 255) {
|
||||
|
|
@ -852,6 +906,7 @@ bool SomfyShadeController::toJSON(JsonObject &obj) {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SomfyShadeController::loop() {
|
||||
this->transceiver.loop();
|
||||
for(uint8_t i; i < SOMFY_MAX_SHADES; i++) {
|
||||
|
|
@ -894,6 +949,51 @@ static struct somfy_rx_t
|
|||
} somfy_rx;
|
||||
uint8_t receive_buffer[10]; // 80 bits
|
||||
bool packet_received = false;
|
||||
void Transceiver::sendFrame(byte *frame, uint8_t sync) {
|
||||
uint32_t pin = 1 << this->config.TXPin;
|
||||
if (sync == 2) { // Only with the first frame.
|
||||
// Wake-up pulse & Silence
|
||||
REG_WRITE(GPIO_OUT_W1TS_REG, pin);
|
||||
delayMicroseconds(9415);
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(9565);
|
||||
delay(80);
|
||||
}
|
||||
// Hardware sync: two sync for the first frame, seven for the following ones.
|
||||
for (int i = 0; i < sync; i++) {
|
||||
REG_WRITE(GPIO_OUT_W1TS_REG, pin);
|
||||
delayMicroseconds(4 * SYMBOL);
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(4 * SYMBOL);
|
||||
}
|
||||
// Software sync
|
||||
REG_WRITE(GPIO_OUT_W1TS_REG, pin);
|
||||
delayMicroseconds(4450);
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(SYMBOL);
|
||||
// Data: bits are sent one by one, starting with the MSB.
|
||||
// TODO: Handle the 80-bit send protocol
|
||||
for (byte i = 0; i < bit_length; i++) {
|
||||
if (((frame[i / 8] >> (7 - (i % 8))) & 1) == 1) {
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(SYMBOL);
|
||||
REG_WRITE(GPIO_OUT_W1TS_REG, pin);
|
||||
delayMicroseconds(SYMBOL);
|
||||
//this->sendLow(SYMBOL);
|
||||
//this->sendHigh(SYMBOL);
|
||||
} else {
|
||||
REG_WRITE(GPIO_OUT_W1TS_REG, pin);
|
||||
delayMicroseconds(SYMBOL);
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(SYMBOL);
|
||||
//this->sendHigh(SYMBOL);
|
||||
//this->sendLow(SYMBOL);
|
||||
}
|
||||
}
|
||||
// Inter-frame silence
|
||||
REG_WRITE(GPIO_OUT_W1TC_REG, pin);
|
||||
delayMicroseconds(30415);
|
||||
}
|
||||
|
||||
void RECEIVE_ATTR Transceiver::handleReceive() {
|
||||
static unsigned long last_time = 0;
|
||||
|
|
@ -909,19 +1009,13 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
case waiting_synchro:
|
||||
if (duration > tempo_synchro_hw_min && duration < tempo_synchro_hw_max) {
|
||||
// We have found a hardware sync bit. There should be at least 4 of these.
|
||||
|
||||
|
||||
// The original code sets gpio 2 on and off but only when in debug mode. I suspect this was to flash an LED
|
||||
// to see the tempo.
|
||||
//SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
|
||||
++somfy_rx.cpt_synchro_hw;
|
||||
//CLR_TP1
|
||||
}
|
||||
else if (duration > tempo_synchro_sw_min && duration < tempo_synchro_sw_max && somfy_rx.cpt_synchro_hw >= 4) {
|
||||
// If we have a full hardware sync then we should look for the software sync. If we have a software sync
|
||||
// bit and enough hardware sync bits then we should start receiving data.
|
||||
|
||||
//SET_TP1 //WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
|
||||
memset(&somfy_rx, 0x00, sizeof(somfy_rx));
|
||||
somfy_rx.status = receiving_data;
|
||||
}
|
||||
|
|
@ -933,14 +1027,12 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
case receiving_data:
|
||||
// We should be receiving data at this point.
|
||||
if (duration > tempo_symbol_min && duration < tempo_symbol_max && !somfy_rx.waiting_half_symbol) {
|
||||
//SET_TP1
|
||||
somfy_rx.previous_bit = 1 - somfy_rx.previous_bit;
|
||||
// Bits come in high order bit first.
|
||||
somfy_rx.payload[somfy_rx.cpt_bits / 8] += somfy_rx.previous_bit << (7 - somfy_rx.cpt_bits % 8);
|
||||
++somfy_rx.cpt_bits;
|
||||
}
|
||||
else if (duration > tempo_half_symbol_min && duration < tempo_half_symbol_max) {
|
||||
//SET_TP1 WAIT CLR_TP1 WAIT SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
|
||||
if (somfy_rx.waiting_half_symbol) {
|
||||
somfy_rx.waiting_half_symbol = false;
|
||||
somfy_rx.payload[somfy_rx.cpt_bits / 8] += somfy_rx.previous_bit << (7 - somfy_rx.cpt_bits % 8);
|
||||
|
|
@ -955,11 +1047,9 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
|
|||
somfy_rx.cpt_synchro_hw = 0;
|
||||
somfy_rx.status = waiting_synchro;
|
||||
}
|
||||
//CLR_TP1
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits == bit_length) {
|
||||
// The original code posted the task so that it would pick it up immediately.
|
||||
|
|
@ -991,8 +1081,6 @@ void Transceiver::enableReceive(void) {
|
|||
interruptPin = digitalPinToInterrupt(this->config.RXPin);
|
||||
ELECHOUSE_cc1101.SetRx();
|
||||
attachInterrupt(interruptPin, handleReceive, CHANGE);
|
||||
Serial.print("Hooked interrupt #");
|
||||
Serial.println(interruptPin);
|
||||
}
|
||||
void Transceiver::disableReceive(void) { detachInterrupt(interruptPin); }
|
||||
bool Transceiver::toJSON(JsonObject& obj) {
|
||||
|
|
@ -1172,13 +1260,19 @@ void transceiver_config_t::apply() {
|
|||
bit_length = this->type;
|
||||
Serial.print("Applying radio settings ");
|
||||
Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin);
|
||||
|
||||
ELECHOUSE_cc1101.setGDO(this->RXPin, this->TXPin);
|
||||
Serial.println("Set GDO");
|
||||
ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
|
||||
Serial.println("Set SPI");
|
||||
ELECHOUSE_cc1101.Init();
|
||||
Serial.println("Initialized");
|
||||
ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
|
||||
Serial.println("Set Frequency");
|
||||
ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
|
||||
Serial.println("Set RxBW");
|
||||
ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
|
||||
//ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode.
|
||||
ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode.
|
||||
ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
|
||||
/*
|
||||
ELECHOUSE_cc1101.setDeviation(this->deviation); // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
|
||||
|
|
@ -1219,15 +1313,16 @@ bool Transceiver::begin() {
|
|||
void Transceiver::loop() {
|
||||
if (this->receive()) {
|
||||
this->clearReceived();
|
||||
somfy.processFrame(this->frame);
|
||||
somfy.processFrame(this->frame, false);
|
||||
char buf[128];
|
||||
sprintf(buf, "{\"address\":%d, \"rcode\":%d, \"command\":\"%s\"}", this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd));
|
||||
sprintf(buf, "{\"encKey\":%d, \"address\":%d, \"rcode\":%d, \"command\":\"%s\"}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd));
|
||||
sockEmit.sendToClients("remoteFrame", buf);
|
||||
}
|
||||
}
|
||||
somfy_frame_t& Transceiver::lastFrame() { return this->frame; }
|
||||
void Transceiver::beginTransmit() {
|
||||
this->disableReceive();
|
||||
pinMode(this->config.TXPin, OUTPUT);
|
||||
ELECHOUSE_cc1101.SetTx();
|
||||
}
|
||||
void Transceiver::endTransmit() {
|
||||
|
|
|
|||
45
Somfy.h
45
Somfy.h
|
|
@ -1,8 +1,8 @@
|
|||
#ifndef SOMFY_H
|
||||
#define SOMFY_H
|
||||
|
||||
#define SOMFY_MAX_SHADES 5
|
||||
#define SOMFY_MAX_LINKED_REMOTES 2
|
||||
#define SOMFY_MAX_SHADES 32
|
||||
#define SOMFY_MAX_LINKED_REMOTES 5
|
||||
|
||||
enum class somfy_commands : byte {
|
||||
My = 0x1,
|
||||
|
|
@ -25,11 +25,11 @@ typedef struct somfy_frame_t {
|
|||
somfy_commands cmd;
|
||||
uint32_t remoteAddress = 0;
|
||||
uint16_t rollingCode = 0;
|
||||
uint8_t encKey = 0;
|
||||
uint8_t encKey = 0xA7;
|
||||
uint8_t checksum = 0;
|
||||
bool valid = false;
|
||||
void print();
|
||||
void encodeFrame(const uint32_t address, const somfy_commands cmd, const uint16_t rcode, byte* frame);
|
||||
void encodeFrame(byte *frame);
|
||||
void decodeFrame(byte* frame);
|
||||
};
|
||||
|
||||
|
|
@ -40,11 +40,6 @@ class SomfyRemote {
|
|||
protected:
|
||||
char m_remotePrefId[10] = "";
|
||||
uint32_t m_remoteAddress = 0;
|
||||
void encodeFrame(byte *frame, somfy_commands cmd, uint16_t rcode);
|
||||
void decodeFrame(byte *frame, somfy_frame_t *decoded);
|
||||
void sendFrame(byte *frame, byte sync);
|
||||
void sendHigh(uint16_t durationInMicroseconds);
|
||||
void sendLow(uint16_t durationInMicroseconds);
|
||||
public:
|
||||
char *getRemotePrefId() {return m_remotePrefId;}
|
||||
virtual bool toJSON(JsonObject &obj);
|
||||
|
|
@ -53,7 +48,7 @@ class SomfyRemote {
|
|||
virtual uint16_t getNextRollingCode();
|
||||
virtual uint16_t setRollingCode(uint16_t code);
|
||||
uint16_t lastRollingCode = 0;
|
||||
void sendCommand(somfy_commands, uint8_t repeat = 4);
|
||||
virtual void sendCommand(somfy_commands cmd, uint8_t repeat = 1);
|
||||
};
|
||||
class SomfyLinkedRemote : public SomfyRemote {
|
||||
public:
|
||||
|
|
@ -64,6 +59,7 @@ class SomfyShade : public SomfyRemote {
|
|||
uint8_t shadeId = 255;
|
||||
uint64_t moveStart = 0;
|
||||
float startPos = 0.0;
|
||||
bool seekingPos = false;
|
||||
public:
|
||||
void load();
|
||||
float currentPos = 0.0;
|
||||
|
|
@ -75,19 +71,22 @@ class SomfyShade : public SomfyRemote {
|
|||
bool paired = false;
|
||||
bool fromJSON(JsonObject &obj);
|
||||
bool toJSON(JsonObject &obj) override;
|
||||
char name[20] = "";
|
||||
char name[21] = "";
|
||||
void setShadeId(uint8_t id) { shadeId = id; }
|
||||
uint8_t getShadeId() { return shadeId; }
|
||||
uint16_t upTime = 10000;
|
||||
uint16_t downTime = 1000;
|
||||
bool save();
|
||||
void checkMovement();
|
||||
void processFrame(somfy_frame_t &frame);
|
||||
void processFrame(somfy_frame_t &frame, bool internal = false);
|
||||
void setMovement(int8_t dir);
|
||||
void setTarget(uint8_t target);
|
||||
void moveToTarget(uint8_t target);
|
||||
void sendCommand(somfy_commands cmd, uint8_t repeat = 1);
|
||||
bool linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
|
||||
bool unlinkRemote(uint32_t remoteAddress);
|
||||
void emitState();
|
||||
void emitConfig();
|
||||
void emitState(const char *evt = "shadeState");
|
||||
void emitState(uint8_t num, const char *evt = "shadeState");
|
||||
void publish();
|
||||
};
|
||||
|
||||
|
|
@ -95,8 +94,8 @@ typedef struct transceiver_config_t {
|
|||
bool printBuffer = false;
|
||||
uint8_t type = 56; // 56 or 80 bit protocol.
|
||||
uint8_t SCKPin = 18;
|
||||
uint8_t TXPin = 6;
|
||||
uint8_t RXPin = 4;
|
||||
uint8_t TXPin = 12;
|
||||
uint8_t RXPin = 13;
|
||||
uint8_t MOSIPin = 23;
|
||||
uint8_t MISOPin = 19;
|
||||
uint8_t CSNPin = 5;
|
||||
|
|
@ -108,8 +107,8 @@ typedef struct transceiver_config_t {
|
|||
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 rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
|
||||
float dataRate = 99.97; // The data rate in kBaud. 0.02 to 1621.83 Default is 99.97.
|
||||
int8_t txPower = 12; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||
uint8_t syncMode = 2; // 0=No preamble/sync,
|
||||
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
|
||||
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,
|
||||
|
|
@ -177,29 +176,35 @@ class Transceiver {
|
|||
void enableReceive();
|
||||
void disableReceive();
|
||||
somfy_frame_t& lastFrame();
|
||||
void sendFrame(byte *frame, uint8_t sync);
|
||||
void beginTransmit();
|
||||
void endTransmit();
|
||||
};
|
||||
class SomfyShadeController {
|
||||
protected:
|
||||
uint8_t m_shadeIds[SOMFY_MAX_SHADES];
|
||||
uint8_t getNextShadeId();
|
||||
public:
|
||||
uint32_t startingAddress;
|
||||
uint8_t getNextShadeId();
|
||||
uint32_t getNextRemoteAddress(uint8_t shadeId);
|
||||
SomfyShadeController();
|
||||
Transceiver transceiver;
|
||||
SomfyShade *addShade();
|
||||
SomfyShade *addShade(JsonObject &obj);
|
||||
bool deleteShade(uint8_t shadeId);
|
||||
bool begin();
|
||||
void loop();
|
||||
void end();
|
||||
SomfyShade shades[SOMFY_MAX_SHADES];
|
||||
bool toJSON(DynamicJsonDocument &doc);
|
||||
bool toJSON(JsonArray &arr);
|
||||
bool toJSON(JsonObject &obj);
|
||||
uint8_t shadeCount();
|
||||
SomfyShade * getShadeById(uint8_t shadeId);
|
||||
SomfyShade * findShadeByRemoteAddress(uint32_t address);
|
||||
void processFrame(somfy_frame_t &frame);
|
||||
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();
|
||||
};
|
||||
|
||||
|
|
|
|||
621
Web.cpp
621
Web.cpp
|
|
@ -15,22 +15,24 @@ extern rebootDelay_t rebootDelay;
|
|||
extern SomfyShadeController somfy;
|
||||
extern Web webServer;
|
||||
|
||||
#define WEB_MAX_RESPONSE 2048
|
||||
#define WEB_MAX_RESPONSE 16384
|
||||
static char g_content[WEB_MAX_RESPONSE];
|
||||
|
||||
// General responses
|
||||
static const char _response_404[] = "404: Not Found";
|
||||
static const char _response_404[] = "404: Service Not Found";
|
||||
|
||||
// Encodings
|
||||
static const char _encoding_text[] = "text/plain";
|
||||
static const char _encoding_html[] = "text/html";
|
||||
static const char _encoding_json[] = "application/json";
|
||||
|
||||
WebServer apiServer(8081);
|
||||
WebServer server(80);
|
||||
void Web::startup() {
|
||||
Serial.println("Launching web server...");
|
||||
}
|
||||
void Web::loop() {
|
||||
apiServer.handleClient();
|
||||
server.handleClient();
|
||||
}
|
||||
void Web::sendCORSHeaders() {
|
||||
|
|
@ -45,6 +47,134 @@ void Web::end() {
|
|||
void Web::begin() {
|
||||
Serial.println("Creating Web MicroServices...");
|
||||
server.enableCORS(true);
|
||||
apiServer.enableCORS(true);
|
||||
apiServer.on("/discovery", []() {
|
||||
HTTPMethod method = apiServer.method();
|
||||
if (method == HTTP_POST || method == HTTP_GET) {
|
||||
Serial.println("Discovery Requested");
|
||||
DynamicJsonDocument doc(16384);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
obj["serverId"] = settings.WIFI.serverId;
|
||||
obj["version"] = settings.fwVersion;
|
||||
obj["model"] = "ESPSomfyRTS";
|
||||
JsonArray arr = obj.createNestedArray("shades");
|
||||
somfy.toJSON(arr);
|
||||
serializeJson(doc, g_content);
|
||||
apiServer.send(200, _encoding_json, g_content);
|
||||
}
|
||||
apiServer.send(500, _encoding_text, "Invalid http method");
|
||||
});
|
||||
apiServer.on("/shades", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = apiServer.method();
|
||||
if (method == HTTP_POST || method == HTTP_GET) {
|
||||
DynamicJsonDocument doc(16384);
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
somfy.toJSON(arr);
|
||||
serializeJson(doc, g_content);
|
||||
apiServer.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else apiServer.send(404, _encoding_text, _response_404);
|
||||
});
|
||||
apiServer.onNotFound([]() {
|
||||
Serial.print("Request 404:");
|
||||
HTTPMethod method = apiServer.method();
|
||||
switch (method) {
|
||||
case HTTP_POST:
|
||||
Serial.print("POST ");
|
||||
break;
|
||||
case HTTP_GET:
|
||||
Serial.print("GET ");
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
Serial.print("PUT ");
|
||||
break;
|
||||
default:
|
||||
Serial.print("[");
|
||||
Serial.print(method);
|
||||
Serial.print("]");
|
||||
break;
|
||||
|
||||
}
|
||||
snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", apiServer.uri().c_str());
|
||||
apiServer.send(404, _encoding_text, g_content);
|
||||
});
|
||||
apiServer.on("/controller", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = apiServer.method();
|
||||
if (method == HTTP_POST || method == HTTP_GET) {
|
||||
DynamicJsonDocument doc(16384);
|
||||
somfy.toJSON(doc);
|
||||
serializeJson(doc, g_content);
|
||||
apiServer.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else apiServer.send(404, _encoding_text, _response_404);
|
||||
});
|
||||
apiServer.on("/shadeCommand", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = apiServer.method();
|
||||
uint8_t shadeId = 255;
|
||||
uint8_t target = 255;
|
||||
somfy_commands command = somfy_commands::My;
|
||||
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (apiServer.hasArg("shadeId")) {
|
||||
shadeId = atoi(apiServer.arg("shadeId").c_str());
|
||||
if (apiServer.hasArg("command")) command = translateSomfyCommand(apiServer.arg("command"));
|
||||
else if(apiServer.hasArg("target")) target = atoi(apiServer.arg("target").c_str());
|
||||
}
|
||||
else if (apiServer.hasArg("plain")) {
|
||||
Serial.println("Sending Shade Command");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, apiServer.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
|
||||
if (obj.containsKey("command")) {
|
||||
String scmd = obj["command"];
|
||||
command = translateSomfyCommand(scmd);
|
||||
}
|
||||
else if(obj.containsKey("target")) {
|
||||
target = obj["target"].as<uint8_t>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
SomfyShade* shade = somfy.getShadeById(shadeId);
|
||||
if (shade) {
|
||||
Serial.print("Received:");
|
||||
Serial.println(apiServer.arg("plain"));
|
||||
// Send the command to the shade.
|
||||
if(target >= 0 && target <= 100)
|
||||
shade->moveToTarget(target);
|
||||
else
|
||||
shade->sendCommand(command);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
apiServer.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else {
|
||||
apiServer.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
}
|
||||
});
|
||||
|
||||
server.on("/upnp.xml", []() {
|
||||
SSDP.schema(server.client());
|
||||
});
|
||||
|
|
@ -54,7 +184,7 @@ void Web::begin() {
|
|||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file index.html");
|
||||
File file = LittleFS.open("/index.html", "r");
|
||||
if(!file) {
|
||||
if (!file) {
|
||||
Serial.println("Error opening data/index.html");
|
||||
server.send(500, _encoding_html, "Unable to open data/index.html");
|
||||
}
|
||||
|
|
@ -66,7 +196,7 @@ void Web::begin() {
|
|||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file configRadio.html");
|
||||
File file = LittleFS.open("/configRadio.html", "r");
|
||||
if(!file) {
|
||||
if (!file) {
|
||||
Serial.println("Error opening configRadio.html");
|
||||
server.send(500, _encoding_text, "configRadio.html");
|
||||
}
|
||||
|
|
@ -78,7 +208,7 @@ void Web::begin() {
|
|||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file main.css");
|
||||
File file = LittleFS.open("/main.css", "r");
|
||||
if(!file) {
|
||||
if (!file) {
|
||||
Serial.println("Error opening data/main.css");
|
||||
server.send(500, _encoding_text, "Unable to open data/main.css");
|
||||
}
|
||||
|
|
@ -90,36 +220,78 @@ void Web::begin() {
|
|||
// Load the index html page from the data directory.
|
||||
Serial.println("Loading file icons.css");
|
||||
File file = LittleFS.open("/icons.css", "r");
|
||||
if(!file) {
|
||||
if (!file) {
|
||||
Serial.println("Error opening data/icons.css");
|
||||
server.send(500, _encoding_text, "Unable to open data/icons.css");
|
||||
}
|
||||
server.streamFile(file, "text/css");
|
||||
file.close();
|
||||
});
|
||||
server.onNotFound([]() {
|
||||
Serial.print("Request 404:");
|
||||
HTTPMethod method = server.method();
|
||||
switch (method) {
|
||||
case HTTP_POST:
|
||||
Serial.print("POST ");
|
||||
break;
|
||||
case HTTP_GET:
|
||||
Serial.print("GET ");
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
Serial.print("PUT ");
|
||||
break;
|
||||
default:
|
||||
Serial.print("[");
|
||||
Serial.print(method);
|
||||
Serial.print("]");
|
||||
break;
|
||||
|
||||
server.onNotFound([]() { server.send(404, _encoding_text, _response_404); });
|
||||
server.on("/somfyController", []() {
|
||||
}
|
||||
snprintf(g_content, sizeof(g_content), "404 Service Not Found: %s", server.uri().c_str());
|
||||
server.send(404, _encoding_text, g_content);
|
||||
});
|
||||
server.on("/controller", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_POST || method == HTTP_GET) {
|
||||
Serial.println("Begin Serialize Somfy...");
|
||||
DynamicJsonDocument doc(2048);
|
||||
if (method == HTTP_POST || method == HTTP_GET) {
|
||||
DynamicJsonDocument doc(16384);
|
||||
somfy.toJSON(doc);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else server.send(404, _encoding_text, _response_404);
|
||||
});
|
||||
server.on("/shades", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if (method == HTTP_POST || method == HTTP_GET) {
|
||||
DynamicJsonDocument doc(16384);
|
||||
JsonArray arr = doc.to<JsonArray>();
|
||||
somfy.toJSON(arr);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else server.send(404, _encoding_text, _response_404);
|
||||
});
|
||||
server.on("/getNextShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
StaticJsonDocument<128> doc;
|
||||
uint8_t shadeId = somfy.getNextShadeId();
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
obj["shadeId"] = shadeId;
|
||||
obj["remoteAddress"] = somfy.getNextRemoteAddress(shadeId);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
});
|
||||
server.on("/addShade", []() {
|
||||
HTTPMethod method = server.method();
|
||||
SomfyShade *shade;
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
SomfyShade* shade;
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
Serial.println("Adding a shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -132,20 +304,15 @@ void Web::begin() {
|
|||
}
|
||||
}
|
||||
else {
|
||||
Serial.println("Convering to JsonObject");
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
Serial.println("Counting shades");
|
||||
if(somfy.shadeCount() > SOMFY_MAX_SHADES) {
|
||||
if (somfy.shadeCount() > SOMFY_MAX_SHADES) {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Maximum number of shades exceeded.\"}"));
|
||||
}
|
||||
else {
|
||||
Serial.println("Adding shade");
|
||||
shade = somfy.addShade();
|
||||
if(shade) {
|
||||
Serial.println("Persisting toJSON");
|
||||
shade->fromJSON(obj);
|
||||
Serial.println("Saving to Preferences");
|
||||
shade->save();
|
||||
shade = somfy.addShade(obj);
|
||||
if (shade) {
|
||||
DynamicJsonDocument sdoc(256);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
|
|
@ -153,13 +320,13 @@ void Web::begin() {
|
|||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Max number of shades added.\"}"));
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Error adding shade.\"}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(shade) {
|
||||
Serial.println("Serializing shade");
|
||||
if (shade) {
|
||||
//Serial.println("Serializing shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
|
|
@ -174,11 +341,11 @@ void Web::begin() {
|
|||
server.on("/shade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_GET) {
|
||||
if(server.hasArg("shadeId")) {
|
||||
if (method == HTTP_GET) {
|
||||
if (server.hasArg("shadeId")) {
|
||||
int shadeId = atoi(server.arg("shadeId").c_str());
|
||||
SomfyShade *shade = somfy.getShadeById(shadeId);
|
||||
if(shade) {
|
||||
SomfyShade* shade = somfy.getShadeById(shadeId);
|
||||
if (shade) {
|
||||
DynamicJsonDocument doc(256);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
|
|
@ -191,14 +358,14 @@ void Web::begin() {
|
|||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"You must supply a valid shade id.\"}"));
|
||||
}
|
||||
}
|
||||
else if(method == HTTP_PUT || method == HTTP_POST) {
|
||||
else if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade.
|
||||
if(server.hasArg("plain")) {
|
||||
if (server.hasArg("plain")) {
|
||||
Serial.println("Updating a shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -212,9 +379,9 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) {
|
||||
SomfyShade *shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if(shade) {
|
||||
if (obj.containsKey("shadeId")) {
|
||||
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if (shade) {
|
||||
shade->fromJSON(obj);
|
||||
shade->save();
|
||||
DynamicJsonDocument sdoc(256);
|
||||
|
|
@ -234,14 +401,14 @@ void Web::begin() {
|
|||
server.on("/saveShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade.
|
||||
if(server.hasArg("plain")) {
|
||||
if (server.hasArg("plain")) {
|
||||
Serial.println("Updating a shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -255,9 +422,9 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) {
|
||||
SomfyShade *shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if(shade) {
|
||||
if (obj.containsKey("shadeId")) {
|
||||
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if (shade) {
|
||||
shade->fromJSON(obj);
|
||||
shade->save();
|
||||
DynamicJsonDocument sdoc(256);
|
||||
|
|
@ -274,16 +441,81 @@ void Web::begin() {
|
|||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
});
|
||||
server.on("/unlinkRemote", []() {
|
||||
server.on("/shadeCommand", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade by adding a linked remote.
|
||||
if(server.hasArg("plain")) {
|
||||
uint8_t shadeId = 255;
|
||||
uint8_t target = 255;
|
||||
somfy_commands command = somfy_commands::My;
|
||||
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (server.hasArg("shadeId")) {
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
if (server.hasArg("command")) command = translateSomfyCommand(server.arg("command"));
|
||||
else if(server.hasArg("target")) target = atoi(server.arg("target").c_str());
|
||||
}
|
||||
else if (server.hasArg("plain")) {
|
||||
Serial.println("Sending Shade Command");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
|
||||
if (obj.containsKey("command")) {
|
||||
String scmd = obj["command"];
|
||||
command = translateSomfyCommand(scmd);
|
||||
}
|
||||
else if(obj.containsKey("target")) {
|
||||
target = obj["target"].as<uint8_t>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
SomfyShade* shade = somfy.getShadeById(shadeId);
|
||||
if (shade) {
|
||||
Serial.print("Received:");
|
||||
Serial.println(server.arg("plain"));
|
||||
// Send the command to the shade.
|
||||
if(target >= 0 && target <= 100)
|
||||
shade->moveToTarget(target);
|
||||
else
|
||||
shade->sendCommand(command);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
}
|
||||
});
|
||||
server.on("/pairShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
uint8_t shadeId = 255;
|
||||
if (server.hasArg("plain")) {
|
||||
// Its coming in the body.
|
||||
StaticJsonDocument<129> doc;
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -297,10 +529,101 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) {
|
||||
SomfyShade *shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if(shade) {
|
||||
if(obj.containsKey("remoteAddress")) {
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
}
|
||||
}
|
||||
else if (server.hasArg("shadeId"))
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
SomfyShade* shade = nullptr;
|
||||
if (shadeId != 255) shade = somfy.getShadeById(shadeId);
|
||||
if (!shade) {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}"));
|
||||
}
|
||||
else {
|
||||
shade->sendCommand(somfy_commands::Prog, 4);
|
||||
shade->paired = true;
|
||||
shade->save();
|
||||
StaticJsonDocument<256> doc;
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
}
|
||||
});
|
||||
server.on("/unpairShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
uint8_t shadeId = 255;
|
||||
if (server.hasArg("plain")) {
|
||||
// Its coming in the body.
|
||||
StaticJsonDocument<129> doc;
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
}
|
||||
}
|
||||
else if (server.hasArg("shadeId"))
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
SomfyShade* shade = nullptr;
|
||||
if (shadeId != 255) shade = somfy.getShadeById(shadeId);
|
||||
if (!shade) {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to unpair\"}"));
|
||||
}
|
||||
else {
|
||||
shade->sendCommand(somfy_commands::Prog, 4);
|
||||
shade->paired = false;
|
||||
shade->save();
|
||||
StaticJsonDocument<256> doc;
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
shade->toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
}
|
||||
});
|
||||
server.on("/unlinkRemote", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade by adding a linked remote.
|
||||
if (server.hasArg("plain")) {
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
case DeserializationError::NoMemory:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}"));
|
||||
break;
|
||||
default:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if (obj.containsKey("shadeId")) {
|
||||
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if (shade) {
|
||||
if (obj.containsKey("remoteAddress")) {
|
||||
shade->unlinkRemote(obj["remoteAddress"]);
|
||||
}
|
||||
else {
|
||||
|
|
@ -323,14 +646,14 @@ void Web::begin() {
|
|||
server.on("/linkRemote", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (method == HTTP_PUT || method == HTTP_POST) {
|
||||
// We are updating an existing shade by adding a linked remote.
|
||||
if(server.hasArg("plain")) {
|
||||
if (server.hasArg("plain")) {
|
||||
Serial.println("Linking a remote");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -344,11 +667,11 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) {
|
||||
SomfyShade *shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if(shade) {
|
||||
if(obj.containsKey("remoteAddress")) {
|
||||
if(obj.containsKey("rollingCode")) shade->linkRemote(obj["remoteAddress"], obj["rollingCode"]);
|
||||
if (obj.containsKey("shadeId")) {
|
||||
SomfyShade* shade = somfy.getShadeById(obj["shadeId"]);
|
||||
if (shade) {
|
||||
if (obj.containsKey("remoteAddress")) {
|
||||
if (obj.containsKey("rollingCode")) shade->linkRemote(obj["remoteAddress"], obj["rollingCode"]);
|
||||
else shade->linkRemote(obj["remoteAddress"]);
|
||||
}
|
||||
else {
|
||||
|
|
@ -368,21 +691,20 @@ void Web::begin() {
|
|||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No remote object supplied.\"}"));
|
||||
}
|
||||
});
|
||||
|
||||
server.on("/deleteShade", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
uint8_t shadeId = 255;
|
||||
if(method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if(server.hasArg("shadeId")) {
|
||||
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if (server.hasArg("shadeId")) {
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
}
|
||||
else if(server.hasArg("plain")) {
|
||||
else if (server.hasArg("plain")) {
|
||||
Serial.println("Deleting a shade");
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -396,14 +718,14 @@ void Web::begin() {
|
|||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) shadeId = obj["shadeId"];//obj.getMember("shadeId").as<uint8_t>();
|
||||
if (obj.containsKey("shadeId")) shadeId = obj["shadeId"];//obj.getMember("shadeId").as<uint8_t>();
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
|
||||
}
|
||||
}
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
SomfyShade *shade = somfy.getShadeById(shadeId);
|
||||
if(!shade) server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
SomfyShade* shade = somfy.getShadeById(shadeId);
|
||||
if (!shade) server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
else {
|
||||
somfy.deleteShade(shadeId);
|
||||
server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Shade deleted.\"}"));
|
||||
|
|
@ -411,7 +733,7 @@ void Web::begin() {
|
|||
});
|
||||
server.on("/updateFirmware", HTTP_POST, []() {
|
||||
webServer.sendCORSHeaders();
|
||||
if(Update.hasError())
|
||||
if (Update.hasError())
|
||||
server.send(500, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Error updating firmware: \"}");
|
||||
else
|
||||
server.send(200, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"Updating firmware: \"}");
|
||||
|
|
@ -424,15 +746,18 @@ void Web::begin() {
|
|||
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
/* flashing firmware to ESP*/
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
|
|
@ -450,15 +775,18 @@ void Web::begin() {
|
|||
if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) { //start with max available size and tell it we are updating the file system.
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||
/* flashing firmware to ESP*/
|
||||
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
} else if (upload.status == UPLOAD_FILE_END) {
|
||||
}
|
||||
else if (upload.status == UPLOAD_FILE_END) {
|
||||
if (Update.end(true)) { //true to set the size to the current progress
|
||||
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Update.printError(Serial);
|
||||
}
|
||||
}
|
||||
|
|
@ -470,9 +798,9 @@ void Web::begin() {
|
|||
Serial.print("Scanned ");
|
||||
Serial.print(n);
|
||||
Serial.println(" networks");
|
||||
String content = "{\"connected\": {\"name\":\"" + String(settings.WIFI.ssid) + "\",\"passphrase\":\"" + String(settings.WIFI.passphrase) + "\"}, \"accessPoints\":[";
|
||||
for(int i = 0; i < n; ++i) {
|
||||
if(i != 0) content += ",";
|
||||
String content = "{\"connected\": {\"name\":\"" + String(settings.WIFI.ssid) + "\",\"passphrase\":\"" + String(settings.WIFI.passphrase) + "\",\"strength\":" + WiFi.RSSI() + ",\"channel\":" + WiFi.channel() + "}, \"accessPoints\":[";
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (i != 0) content += ",";
|
||||
content += "{\"name\":\"" + WiFi.SSID(i) + "\",\"channel\":" + WiFi.channel(i) + ",\"encryption\":\"" + settings.WIFI.mapEncryptionType(WiFi.encryptionType(i)) + "\",\"strength\":" + WiFi.RSSI(i) + ",\"macAddress\":\"" + WiFi.BSSIDstr(i) + "\"}";
|
||||
delay(10);
|
||||
}
|
||||
|
|
@ -482,7 +810,7 @@ void Web::begin() {
|
|||
server.on("/reboot", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
Serial.println("Rebooting ESP...");
|
||||
rebootDelay.reboot = true;
|
||||
rebootDelay.rebootTime = millis() + 500;
|
||||
|
|
@ -496,7 +824,7 @@ void Web::begin() {
|
|||
webServer.sendCORSHeaders();
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
if (err) {
|
||||
Serial.print("Error parsing JSON ");
|
||||
Serial.println(err.c_str());
|
||||
String msg = err.c_str();
|
||||
|
|
@ -505,7 +833,7 @@ void Web::begin() {
|
|||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
somfy.transceiver.fromJSON(obj);
|
||||
somfy.transceiver.save();
|
||||
DynamicJsonDocument sdoc(512);
|
||||
|
|
@ -528,56 +856,24 @@ void Web::begin() {
|
|||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
});
|
||||
|
||||
/* THE Following endpoints have been deprecated now that I now can tune the radio settings without hacking around. The relevant settings
|
||||
* have been included when the transceiver is saved.
|
||||
server.on("/setRadioConfig", []() {
|
||||
server.on("/sendRemoteCommand", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
DynamicJsonDocument doc(1280);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
Serial.print("Error parsing JSON ");
|
||||
Serial.println(err.c_str());
|
||||
String msg = err.c_str();
|
||||
server.send(400, _encoding_html, "Error parsing JSON body<br>" + msg);
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
transceiver_config_t cfg;
|
||||
cfg.fromJSON(obj);
|
||||
cfg.save();
|
||||
cfg.apply();
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
});
|
||||
server.on("/getRadioConfig", []() {
|
||||
webServer.sendCORSHeaders();
|
||||
DynamicJsonDocument doc(1024);
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
transceiver_config_t cfg;
|
||||
cfg.load();
|
||||
cfg.toJSON(obj);
|
||||
serializeJson(doc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
});
|
||||
*/
|
||||
server.on("/sendShadeCommand", [](){
|
||||
server.sendHeader("Access-Control-Allow-Origin", "*");
|
||||
HTTPMethod method = server.method();
|
||||
uint8_t shadeId = 255;
|
||||
somfy_commands command = somfy_commands::My;
|
||||
if(method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
if(server.hasArg("shadeId")) {
|
||||
shadeId = atoi(server.arg("shadeId").c_str());
|
||||
if(server.hasArg("command")) command = translateSomfyCommand(server.arg("command"));
|
||||
if (method == HTTP_GET || method == HTTP_PUT || method == HTTP_POST) {
|
||||
somfy_frame_t frame;
|
||||
uint8_t repeats = 0;
|
||||
if (server.hasArg("address")) {
|
||||
frame.remoteAddress = atoi(server.arg("address").c_str());
|
||||
if (server.hasArg("encKey")) frame.encKey = atoi(server.arg("encKey").c_str());
|
||||
if (server.hasArg("command")) frame.cmd = translateSomfyCommand(server.arg("command"));
|
||||
if (server.hasArg("rcode")) frame.rollingCode = atoi(server.arg("rcode").c_str());
|
||||
if (server.hasArg("repeats")) repeats = atoi(server.arg("repeats").c_str());
|
||||
}
|
||||
else if(server.hasArg("plain")) {
|
||||
Serial.println("Sending Shade Command");
|
||||
DynamicJsonDocument doc(256);
|
||||
else if (server.hasArg("plain")) {
|
||||
StaticJsonDocument<128> doc;
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
switch(err.code()) {
|
||||
if (err) {
|
||||
switch (err.code()) {
|
||||
case DeserializationError::InvalidInput:
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}"));
|
||||
break;
|
||||
|
|
@ -588,31 +884,25 @@ void Web::begin() {
|
|||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}"));
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
if(obj.containsKey("shadeId")) shadeId = obj["shadeId"];
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade id was supplied.\"}"));
|
||||
if(obj.containsKey("command")) {
|
||||
String scmd = obj["command"];
|
||||
command = translateSomfyCommand(scmd);
|
||||
String scmd;
|
||||
if (obj.containsKey("address")) frame.remoteAddress = obj["address"];
|
||||
if (obj.containsKey("command")) scmd = obj["command"].as<String>();
|
||||
if (obj.containsKey("repeats")) repeats = obj["repeats"];
|
||||
if (obj.containsKey("rcode")) frame.rollingCode = obj["rcode"];
|
||||
if (obj.containsKey("encKey")) frame.encKey = obj["encKey"];
|
||||
frame.cmd = translateSomfyCommand(scmd.c_str());
|
||||
}
|
||||
}
|
||||
if (frame.remoteAddress > 0 && frame.rollingCode > 0) {
|
||||
somfy.sendFrame(frame, repeats);
|
||||
server.send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}"));
|
||||
}
|
||||
else server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No shade object supplied.\"}"));
|
||||
}
|
||||
SomfyShade *shade = somfy.getShadeById(shadeId);
|
||||
if(shade) {
|
||||
// Send the command to the shade.
|
||||
shade->sendCommand(command);
|
||||
DynamicJsonDocument sdoc(256);
|
||||
JsonObject sobj = sdoc.to<JsonObject>();
|
||||
shade->toJSON(sobj);
|
||||
serializeJson(sdoc, g_content);
|
||||
server.send(200, _encoding_json, g_content);
|
||||
}
|
||||
else {
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade with the specified id not found.\"}"));
|
||||
else
|
||||
server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}"));
|
||||
}
|
||||
});
|
||||
server.on("/setgeneral", []() {
|
||||
|
|
@ -620,7 +910,7 @@ void Web::begin() {
|
|||
int statusCode = 200;
|
||||
DynamicJsonDocument doc(256);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
if (err) {
|
||||
Serial.print("Error parsing JSON ");
|
||||
Serial.println(err.c_str());
|
||||
String msg = err.c_str();
|
||||
|
|
@ -629,13 +919,13 @@ void Web::begin() {
|
|||
else {
|
||||
JsonObject obj = doc.as<JsonObject>();
|
||||
HTTPMethod method = server.method();
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
// Parse out all the inputs.
|
||||
if(obj.containsKey("hostname")) {
|
||||
if (obj.containsKey("hostname")) {
|
||||
settings.WIFI.fromJSON(obj);
|
||||
settings.WIFI.save();
|
||||
}
|
||||
if(obj.containsKey("ntpServer") || obj.containsKey("ntpServer")) {
|
||||
if (obj.containsKey("ntpServer") || obj.containsKey("ntpServer")) {
|
||||
settings.NTP.fromJSON(obj);
|
||||
settings.NTP.save();
|
||||
}
|
||||
|
|
@ -652,7 +942,7 @@ void Web::begin() {
|
|||
Serial.println("Settings WIFI connection...");
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
if (err) {
|
||||
Serial.print("Error parsing JSON ");
|
||||
Serial.println(err.c_str());
|
||||
String msg = err.c_str();
|
||||
|
|
@ -663,25 +953,25 @@ void Web::begin() {
|
|||
HTTPMethod method = server.method();
|
||||
//Serial.print(F("HTTP Method: "));
|
||||
//Serial.println(server.method());
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
String ssid = "";
|
||||
String passphrase = "";
|
||||
if(obj.containsKey("ssid")) ssid = obj["ssid"].as<String>();
|
||||
if(obj.containsKey("passphrase")) passphrase = obj["passphrase"].as<String>();
|
||||
if (obj.containsKey("ssid")) ssid = obj["ssid"].as<String>();
|
||||
if (obj.containsKey("passphrase")) passphrase = obj["passphrase"].as<String>();
|
||||
bool reboot;
|
||||
if(ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true;
|
||||
if(passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true;
|
||||
if(!settings.WIFI.ssidExists(ssid.c_str())) {
|
||||
if (ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true;
|
||||
if (passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true;
|
||||
if (!settings.WIFI.ssidExists(ssid.c_str())) {
|
||||
server.send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}");
|
||||
}
|
||||
else {
|
||||
SETCHARPROP(settings.WIFI.ssid, ssid.c_str(), sizeof(settings.WIFI.ssid));
|
||||
SETCHARPROP(settings.WIFI.passphrase, passphrase.c_str(), sizeof(settings.WIFI.passphrase));
|
||||
if(obj.containsKey("ssdpBroadcast")) settings.WIFI.ssdpBroadcast = obj["ssdpBroadcast"].as<bool>();
|
||||
if (obj.containsKey("ssdpBroadcast")) settings.WIFI.ssdpBroadcast = obj["ssdpBroadcast"].as<bool>();
|
||||
settings.WIFI.save();
|
||||
settings.WIFI.print();
|
||||
server.send(201, _encoding_json, "{\"status\":\"OK\",\"desc\":\"Successfully set server connection\"}");
|
||||
if(reboot) {
|
||||
if (reboot) {
|
||||
Serial.println("Rebooting ESP for new WiFi settings...");
|
||||
rebootDelay.reboot = true;
|
||||
rebootDelay.rebootTime = millis() + 1000;
|
||||
|
|
@ -706,7 +996,7 @@ void Web::begin() {
|
|||
server.on("/connectmqtt", []() {
|
||||
DynamicJsonDocument doc(512);
|
||||
DeserializationError err = deserializeJson(doc, server.arg("plain"));
|
||||
if(err) {
|
||||
if (err) {
|
||||
Serial.print("Error parsing JSON ");
|
||||
Serial.println(err.c_str());
|
||||
String msg = err.c_str();
|
||||
|
|
@ -717,7 +1007,7 @@ void Web::begin() {
|
|||
HTTPMethod method = server.method();
|
||||
Serial.print(F("HTTP Method: "));
|
||||
Serial.println(server.method());
|
||||
if(method == HTTP_POST || method == HTTP_PUT) {
|
||||
if (method == HTTP_POST || method == HTTP_PUT) {
|
||||
settings.MQTT.fromJSON(obj);
|
||||
settings.MQTT.save();
|
||||
StaticJsonDocument<512> sdoc;
|
||||
|
|
@ -740,4 +1030,5 @@ void Web::begin() {
|
|||
server.send(200, _encoding_json, g_content);
|
||||
});
|
||||
server.begin();
|
||||
apiServer.begin();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,23 @@
|
|||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
let runRepair = false;
|
||||
function runRemoteRepair() {
|
||||
let obj = { address: parseInt(document.getElementById('fldRepairAddress').value, 10), command: 'Down', rcode: parseInt(document.getElementById('fldRepairRCode').value, 10), repeats: 1 };
|
||||
if (runRepair) {
|
||||
// Call the service.
|
||||
console.log(obj);
|
||||
putJSON('/sendRemoteCommand', obj, (err, ret) => {
|
||||
console.log(ret);
|
||||
document.getElementById('fldRepairRCode').value = obj.rcode + 1;
|
||||
setTimeout(() => { runRemoteRepair() }, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
function stopRemoteRepair() {
|
||||
runRepair = false;
|
||||
}
|
||||
|
||||
let baseUrl = ''; //'http://192.168.1.204'; This is the current server.
|
||||
|
||||
Number.prototype.round = function (dec) { return Number(Math.round(this + 'e' + dec) + 'e-' + dec); };
|
||||
|
|
@ -378,6 +395,16 @@
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div syle="white-space:nowrap;">
|
||||
<label for="repairAddress">Repair Address</label>
|
||||
<input id="fldRepairAddress" name="repairAddress" type="number" style="margin-right: 10px; text-align: right; display: inline-block; width: 127px;" value="4624451" />
|
||||
<label for="repairRCode">Rolling Code</label>
|
||||
<input id="fldRepairRCode" name="repairRCode" type="number" style="margin-right:10px;text-align:right;display:inline-block;width:127px;" value="1641" />
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<button id="btnRepairRemote" style="width:127px" onclick="runRepair = true; runRemoteRepair();">Repair Remote</button>
|
||||
<button id="btnStopRepairRemote" style="width:127px" onclick="stopRemoteRepair();">Stop Repair</button>
|
||||
</div>
|
||||
<div id="divFields"></div>
|
||||
<div class="button-container">
|
||||
<button id="btnSaveSetting" type="button" style="display:inline-block;width:44%" onclick="resetDefaults();">
|
||||
|
|
|
|||
1877
data/index.html
1877
data/index.html
File diff suppressed because it is too large
Load diff
|
|
@ -512,3 +512,42 @@ div.waitoverlay > .lds-roller {
|
|||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.somfyShadeCtl {
|
||||
height:60px;
|
||||
border-bottom:dotted 2px gainsboro;
|
||||
position:relative;
|
||||
}
|
||||
.shade-positioner {
|
||||
position:absolute;
|
||||
width:100%;
|
||||
background-color:gainsboro;
|
||||
color:gray;
|
||||
height:60px;
|
||||
top:0px;
|
||||
padding-left:7px;
|
||||
padding-right:7px;
|
||||
}
|
||||
.shade-positioner .shade-name {
|
||||
display:block;
|
||||
font-size:22px;
|
||||
width:100%;
|
||||
margin-top:-1px;
|
||||
}
|
||||
.shade-positioner input[type=range] {
|
||||
width:100%;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
}
|
||||
.shade-positioner label {
|
||||
display:block;
|
||||
font-size:1em;
|
||||
margin-top:-3px;
|
||||
margin-left:27px;
|
||||
}
|
||||
.shade-positioner label > span:last-child {
|
||||
float: right;
|
||||
margin-right: 7px;
|
||||
}
|
||||
.shade-positioner label .shade-target {
|
||||
display:inline-block;
|
||||
}
|
||||
14
debug.cfg
Normal file
14
debug.cfg
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Example OpenOCD configuration file for ESP32-WROVER-KIT board.
|
||||
#
|
||||
# For example, OpenOCD can be started for ESP32 debugging on
|
||||
#
|
||||
# openocd -f board/esp32-wrover-kit-3.3v.cfg
|
||||
#
|
||||
|
||||
# Source the JTAG interface configuration file
|
||||
source [find interface/ftdi/esp32_devkitj_v1.cfg]
|
||||
set ESP32_FLASH_VOLTAGE 3.3
|
||||
# Source the ESP32 configuration file
|
||||
source [find target/esp32.cfg]
|
||||
Loading…
Add table
Add a link
Reference in a new issue