Version 1.003 Changes

This commit is contained in:
Robert Strouse 2023-01-21 14:56:56 -08:00
parent a63e881a63
commit 72d1cdcccb
15 changed files with 48597 additions and 1691 deletions

View file

@ -166,6 +166,18 @@ bool NTPSettings::apply() {
setenv("TZ", this->posixZone, 1); setenv("TZ", this->posixZone, 1);
return true; 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() { bool WifiSettings::begin() {
this->load(); this->load();
return true; return true;

View file

@ -2,7 +2,7 @@
#ifndef configsettings_h #ifndef configsettings_h
#define configsettings_h #define configsettings_h
#define FW_VERSION "v0.90.1" #define FW_VERSION "v1.03.1"
enum DeviceStatus { enum DeviceStatus {
DS_OK = 0, DS_OK = 0,
DS_ERROR = 1, DS_ERROR = 1,
@ -35,6 +35,8 @@ class NTPSettings: BaseSettings {
}; };
class WifiSettings: BaseSettings { class WifiSettings: BaseSettings {
public: public:
WifiSettings();
char serverId[10] = "";
char hostname[32] = ""; char hostname[32] = "";
char ssid[32] = ""; char ssid[32] = "";
char passphrase[32] = ""; char passphrase[32] = "";

View file

@ -26,6 +26,68 @@ bool MQTTClass::loop() {
mqttClient.loop(); mqttClient.loop();
return true; 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() { bool MQTTClass::connect() {
if(mqttClient.connected()) { if(mqttClient.connected()) {
if(!settings.MQTT.enabled) if(!settings.MQTT.enabled)
@ -42,6 +104,9 @@ bool MQTTClass::connect() {
Serial.print("Successfully connected MQTT client "); Serial.print("Successfully connected MQTT client ");
Serial.println(this->clientId); Serial.println(this->clientId);
somfy.publish(); somfy.publish();
this->subscribe("shades/+/target/set");
this->subscribe("shades/+/direction/set");
mqttClient.setCallback(MQTTClass::receive);
return true; return true;
} }
else { else {
@ -56,10 +121,36 @@ bool MQTTClass::connect() {
} }
bool MQTTClass::disconnect() { bool MQTTClass::disconnect() {
if(mqttClient.connected()) { if(mqttClient.connected()) {
this->unsubscribe("shades/+/target/set");
this->unsubscribe("shades/+/direction/set");
mqttClient.disconnect(); mqttClient.disconnect();
} }
return true; 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) { bool MQTTClass::publish(const char *topic, const char *payload) {
if(mqttClient.connected()) { if(mqttClient.connected()) {
char top[64]; char top[64];

4
MQTT.h
View file

@ -20,6 +20,10 @@ class MQTTClass {
bool publish(const char *topic, int8_t val); bool publish(const char *topic, int8_t val);
bool publish(const char *topic, uint32_t val); bool publish(const char *topic, uint32_t val);
bool publish(const char *topic, uint16_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 #endif

View file

@ -45,9 +45,14 @@ void Network::loop() {
} }
void Network::emitSockets() { void Network::emitSockets() {
if(WiFi.status() == WL_CONNECTED) { if(WiFi.status() == WL_CONNECTED) {
char buf[50];
sprintf(buf, "{\"ssid\":\"%s\", \"strength\":%d, \"channel\":%d}", WiFi.SSID(), WiFi.RSSI(), WiFi.channel()); if(abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 2 || WiFi.channel() != this->lastChannel) {
sockEmit.sendToClients("wifiStrength", buf); 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 else
sockEmit.sendToClients("wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}"); sockEmit.sendToClients("wifiStrength", "{\"ssid\":\"\", \"strength\":-100,\"channel\":-1}");
@ -88,29 +93,32 @@ void Network::setConnected() {
SSDP.setHTTPPort(80); SSDP.setHTTPPort(80);
SSDP.setSchemaURL(0, "upnp.xml"); SSDP.setSchemaURL(0, "upnp.xml");
SSDP.setChipId(0, this->getChipId()); 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.setName(0, settings.WIFI.hostname);
//SSDP.setSerialNumber(0, "C2496952-5610-47E6-A968-2FC19737A0DB"); //SSDP.setSerialNumber(0, "C2496952-5610-47E6-A968-2FC19737A0DB");
//SSDP.setUUID(0, settings.uuid); //SSDP.setUUID(0, settings.uuid);
SSDP.setModelName(0, "Somfy Server"); SSDP.setModelName(0, "ESPSomfy RTS");
SSDP.setModelNumber(0, "SS v1"); 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.setManufacturer(0, "rstrouse");
SSDP.setManufacturerURL(0, "https://github.com/rstrouse"); SSDP.setManufacturerURL(0, "https://github.com/rstrouse");
SSDP.setURL(0, "/"); SSDP.setURL(0, "/");
if(MDNS.begin(settings.WIFI.hostname)) { if(MDNS.begin(settings.WIFI.hostname)) {
Serial.println(F("MDNS Responder Started")); Serial.printf("MDNS Responder Started: serverId=%s\n", settings.WIFI.serverId);
MDNS.addService("_http", "_tcp", 80); MDNS.addService("http", "tcp", 80);
MDNS.addServiceTxt("_http", "_tcp", "board", "ESP32"); MDNS.addServiceTxt("http", "tcp", "board", "ESP32");
//MDNS.addServiceTxt("_osc", "_udp", "board", settings.WIFI.hostname); 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(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(); else if(SSDP.isStarted) SSDP.end();
//digitalWrite(LED_BUILTIN, HIGH);
this->emitSockets(); this->emitSockets();
} }
bool Network::connect() { bool Network::connect() {
@ -243,7 +251,7 @@ bool Network::openSoftAP() {
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_AP_STA);
delay(100); delay(100);
WiFi.softAP("Somfy Controller", ""); WiFi.softAP("ESPSomfy RTS", "");
Serial.println("Initializing AP for credentials modification"); Serial.println("Initializing AP for credentials modification");
Serial.println(); Serial.println();
Serial.print("SoftAP IP: "); Serial.print("SoftAP IP: ");
@ -251,6 +259,7 @@ bool Network::openSoftAP() {
pinMode(D0, INPUT_PULLUP); pinMode(D0, INPUT_PULLUP);
long startTime = millis(); long startTime = millis();
int c = 0; int c = 0;
while ((WiFi.status() != WL_CONNECTED)) while ((WiFi.status() != WL_CONNECTED))
{ {
for(int i = 0; i < 3; i++) { for(int i = 0; i < 3; i++) {

View file

@ -5,6 +5,8 @@
class Network { class Network {
protected: protected:
unsigned long lastEmit = 0; unsigned long lastEmit = 0;
int lastRSSI = 0;
int lastChannel = 0;
public: public:
String ssid; String ssid;
String mac; String mac;

View file

@ -3,9 +3,12 @@
#include <WebSocketsServer.h> #include <WebSocketsServer.h>
#include "Sockets.h" #include "Sockets.h"
#include "ConfigSettings.h" #include "ConfigSettings.h"
#include "Somfy.h"
extern ConfigSettings settings; extern ConfigSettings settings;
extern SomfyShadeController somfy;
WebSocketsServer sockServer = WebSocketsServer(8080); WebSocketsServer sockServer = WebSocketsServer(8080);
char g_buffer[1024]; 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. // Send all the current Sensor readings to the client.
sockServer.sendTXT(num, "Connected"); sockServer.sendTXT(num, "Connected");
settings.emitSockets(); settings.emitSockets();
//settings.Inputs.emitSocket(num); somfy.emitState(num);
} }
break; break;
case WStype_TEXT: case WStype_TEXT:

489
Somfy.cpp
View file

@ -22,6 +22,14 @@ extern MQTTClass mqtt;
#define RECEIVE_ATTR #define RECEIVE_ATTR
#endif #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 int interruptPin = 0;
static uint8_t bit_length = 56; static uint8_t bit_length = 56;
somfy_commands translateSomfyCommand(const String& string) { somfy_commands translateSomfyCommand(const String& string) {
@ -141,7 +149,9 @@ void somfy_frame_t::decodeFrame(byte* frame) {
Serial.print(" DECCS:"); Serial.print(" DECCS:");
Serial.println(this->checksum); Serial.println(this->checksum);
*/ */
Serial.print("ADDR:"); Serial.print("KEY:");
Serial.print(this->encKey);
Serial.print(" ADDR:");
Serial.print(this->remoteAddress); Serial.print(this->remoteAddress);
Serial.print(" CMD:"); Serial.print(" CMD:");
Serial.print(translateSomfyCommand(this->cmd)); Serial.print(translateSomfyCommand(this->cmd));
@ -150,7 +160,9 @@ void somfy_frame_t::decodeFrame(byte* frame) {
} }
else { else {
Serial.print("INVALID FRAME "); Serial.print("INVALID FRAME ");
Serial.print("ADDR:"); Serial.print("KEY:");
Serial.print(this->encKey);
Serial.print(" ADDR:");
Serial.print(this->remoteAddress); Serial.print(this->remoteAddress);
Serial.print(" CMD:"); Serial.print(" CMD:");
Serial.print(translateSomfyCommand(this->cmd)); Serial.print(translateSomfyCommand(this->cmd));
@ -180,26 +192,26 @@ void somfy_frame_t::decodeFrame(byte* frame) {
Serial.println(); 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); 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[1] = btn << 4; // Which button did you press? The 4 LSB will be the checksum
frame[2] = rcode >> 8; // Rolling code (big endian) frame[2] = this->rollingCode >> 8; // Rolling code (big endian)
frame[3] = rcode; // Rolling code frame[3] = this->rollingCode; // Rolling code
frame[4] = address >> 16; // Remote address frame[4] = this->remoteAddress >> 16; // Remote address
frame[5] = address >> 8; // Remote address frame[5] = this->remoteAddress >> 8; // Remote address
frame[6] = address; // Remote address frame[6] = this->remoteAddress; // Remote address
byte checksum = 0; byte checksum = 0;
for (byte i = 0; i < 7; i++) { for (byte i = 0; i < 7; i++) {
checksum = checksum ^ frame[i] ^ (frame[i] >> 4); checksum = checksum ^ frame[i] ^ (frame[i] >> 4);
} }
checksum &= 0b1111; // We keep the last 4 bits only checksum &= 0b1111; // We keep the last 4 bits only
// Checksum integration // Checksum integration
frame[1] |= checksum; frame[1] |= checksum;
// Obfuscation: a XOR of all the bytes // Obfuscation: a XOR of all the bytes
for (byte i = 1; i < 7; i++) { for (byte i = 1; i < 7; i++) {
frame[i] ^= frame[i - 1]; frame[i] ^= frame[i - 1];
} }
} }
void somfy_frame_t::print() { void somfy_frame_t::print() {
Serial.println("----------- Receiving -------------"); Serial.println("----------- Receiving -------------");
@ -223,7 +235,7 @@ void SomfyShadeController::end() { this->transceiver.disableReceive(); }
SomfyShadeController::SomfyShadeController() { SomfyShadeController::SomfyShadeController() {
memset(this->m_shadeIds, 255, sizeof(this->m_shadeIds)); memset(this->m_shadeIds, 255, sizeof(this->m_shadeIds));
uint64_t mac = ESP.getEfuseMac(); uint64_t mac = ESP.getEfuseMac();
this->startingAddress = mac & 0xFFFF00; this->startingAddress = mac & 0x0FFFFF;
} }
SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) { SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
@ -239,18 +251,32 @@ SomfyShade *SomfyShadeController::findShadeByRemoteAddress(uint32_t address) {
} }
bool SomfyShadeController::begin() { bool SomfyShadeController::begin() {
// Load up all the configuration data. // Load up all the configuration data.
Serial.printf("sizeof(SomfyShade) = %d\n", sizeof(SomfyShade));
pref.begin("Shades"); pref.begin("Shades");
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
pref.end(); pref.end();
this->transceiver.begin(); 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; uint8_t id = 0;
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
if(this->m_shadeIds[i] == id) this->m_shadeIds[i] = 255; if(this->m_shadeIds[i] == id) this->m_shadeIds[i] = 255;
id = this->m_shadeIds[i]; id = this->m_shadeIds[i];
SomfyShade *shade = &this->shades[i]; SomfyShade *shade = &this->shades[i];
shade->setShadeId(id); shade->setShadeId(id);
if(id == 255) continue; if(id == 255) {
continue;
}
shade->load(); shade->load();
} }
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
@ -355,6 +381,18 @@ void SomfyShade::checkMovement() {
} }
} }
this->position = floor(this->currentPos * 100); 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) { else if(this->direction < 0) {
@ -383,6 +421,17 @@ void SomfyShade::checkMovement() {
} }
} }
this->position = floor(this->currentPos * 100); 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) { if(currDir != this->direction || currPos != this->position) {
// We need to emit on the socket that our state has changed. // We need to emit on the socket that our state has changed.
@ -402,6 +451,7 @@ void SomfyShade::checkMovement() {
void SomfyShade::load() { void SomfyShade::load() {
char shadeKey[15]; char shadeKey[15];
uint32_t linkedAddresses[SOMFY_MAX_LINKED_REMOTES]; 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); snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
// Now load up each of the shades into memory. // Now load up each of the shades into memory.
Serial.print("key:"); Serial.print("key:");
@ -414,6 +464,7 @@ void SomfyShade::load() {
this->setRemoteAddress(pref.getULong("remoteAddress", 0)); this->setRemoteAddress(pref.getULong("remoteAddress", 0));
this->currentPos = pref.getFloat("currentPos", 0); this->currentPos = pref.getFloat("currentPos", 0);
this->position = (uint8_t)floor(this->currentPos * 100); this->position = (uint8_t)floor(this->currentPos * 100);
this->target = this->position;
pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses)); pref.getBytes("linkedAddr", linkedAddresses, sizeof(linkedAddresses));
pref.end(); pref.end();
Serial.print("shadeId:"); Serial.print("shadeId:");
@ -424,7 +475,6 @@ void SomfyShade::load() {
Serial.print(this->getRemoteAddress()); Serial.print(this->getRemoteAddress());
Serial.print(" position:"); Serial.print(" position:");
Serial.println(this->position); Serial.println(this->position);
for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) { for(uint8_t j = 0; j < SOMFY_MAX_LINKED_REMOTES; j++) {
SomfyLinkedRemote &lremote = this->linkedRemotes[j]; SomfyLinkedRemote &lremote = this->linkedRemotes[j];
lremote.setRemoteAddress(linkedAddresses[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() { void SomfyShade::publish() {
if(mqtt.connected()) { if(mqtt.connected()) {
char topic[32]; char topic[32];
@ -459,12 +503,14 @@ void SomfyShade::publish() {
mqtt.publish(topic, this->lastRollingCode); mqtt.publish(topic, this->lastRollingCode);
} }
} }
void SomfyShade::emitState() { void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
char buf[200]; void SomfyShade::emitState(uint8_t num, const char *evt) {
char buf[220];
char shadeKey[15]; char shadeKey[15];
snprintf(shadeKey, sizeof(shadeKey), "Shade_%u", this->shadeId); 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); 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);
sockEmit.sendToClients("somfyShadeState", buf); if(num >= 255) sockEmit.sendToClients(evt, buf);
else sockEmit.sendToClient(num, evt, buf);
if(mqtt.connected()) { if(mqtt.connected()) {
char topic[32]; char topic[32];
snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId); snprintf(topic, sizeof(topic), "shades/%u/position", this->shadeId);
@ -477,7 +523,7 @@ void SomfyShade::emitState() {
mqtt.publish(topic, this->lastRollingCode); 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; bool hasRemote = this->getRemoteAddress() == frame.remoteAddress;
if(!hasRemote) { if(!hasRemote) {
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { 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; if(!hasRemote) return;
int8_t dir = 0; 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 // At this point we are not processing the combo buttons
// will need to see what the shade does when you press both. // will need to see what the shade does when you press both.
switch(frame.cmd) { switch(frame.cmd) {
case somfy_commands::Up: case somfy_commands::Up:
dir = -1; dir = -1;
if(!internal) this->target = 0;
break; break;
case somfy_commands::Down: case somfy_commands::Down:
dir = 1; dir = 1;
if(!internal) this->target = 100;
break; break;
default: default:
dir = 0; dir = 0;
@ -512,6 +562,7 @@ void SomfyShade::setMovement(int8_t dir) {
this->startPos = this->currentPos; this->startPos = this->currentPos;
this->moveStart = 0; this->moveStart = 0;
this->direction = dir; this->direction = dir;
this->emitState();
} }
else if(this->direction != dir) { else if(this->direction != dir) {
this->moveStart = millis(); this->moveStart = millis();
@ -523,6 +574,39 @@ void SomfyShade::setMovement(int8_t dir) {
this->emitState(); 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() { bool SomfyShade::save() {
char shadeKey[15]; char shadeKey[15];
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId()); snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->getShadeId());
@ -564,10 +648,10 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
return true; return true;
} }
bool SomfyShade::toJSON(JsonObject &obj) { bool SomfyShade::toJSON(JsonObject &obj) {
Serial.print("Serializing Shade:"); //Serial.print("Serializing Shade:");
Serial.print(this->getShadeId()); //Serial.print(this->getShadeId());
Serial.print(" "); //Serial.print(" ");
Serial.println(this->name); //Serial.println(this->name);
obj["shadeId"] = this->getShadeId(); obj["shadeId"] = this->getShadeId();
obj["name"] = this->name; obj["name"] = this->name;
obj["remoteAddress"] = this->m_remoteAddress; obj["remoteAddress"] = this->m_remoteAddress;
@ -577,6 +661,7 @@ bool SomfyShade::toJSON(JsonObject &obj) {
obj["remotePrefId"] = this->getRemotePrefId(); obj["remotePrefId"] = this->getRemotePrefId();
obj["lastRollingCode"] = this->lastRollingCode; obj["lastRollingCode"] = this->lastRollingCode;
obj["position"] = this->position; obj["position"] = this->position;
obj["target"] = this->target;
SomfyRemote::toJSON(obj); SomfyRemote::toJSON(obj);
JsonArray arr = obj.createNestedArray("linkedRemotes"); JsonArray arr = obj.createNestedArray("linkedRemotes");
for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { 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); } 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; } 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++) 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() { void SomfyShadeController::publish() {
StaticJsonDocument<128> doc; StaticJsonDocument<128> doc;
@ -612,15 +704,26 @@ void SomfyShadeController::publish() {
mqtt.publish("shades", arr); mqtt.publish("shades", arr);
} }
uint8_t SomfyShadeController::getNextShadeId() { uint8_t SomfyShadeController::getNextShadeId() {
uint8_t maxId = 0; uint8_t nextId = 0;
uint8_t lastId = 0; // There is no shortcut for this since the deletion of
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); // a shade in the middle makes all of this very difficult.
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { for(uint8_t i = 1; i < SOMFY_MAX_SHADES - 1; i++) {
uint8_t id = this->m_shadeIds[i]; bool id_exists = false;
if(id >= 255) continue; for(uint8_t j = 0; j < SOMFY_MAX_SHADES; j++) {
maxId = max(lastId, id); SomfyShade *shade = &this->shades[j];
if(shade->getShadeId() == i) {
id_exists = true;
break;
}
}
if(!id_exists) {
Serial.print("Got next Shade Id:");
Serial.print(i);
return i;
}
} }
return maxId + 1;
return 255;
} }
uint8_t SomfyShadeController::shadeCount() { uint8_t SomfyShadeController::shadeCount() {
uint8_t count = 0; uint8_t count = 0;
@ -629,164 +732,103 @@ uint8_t SomfyShadeController::shadeCount() {
} }
return count; 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() { SomfyShade *SomfyShadeController::addShade() {
uint8_t shadeId = getNextShadeId(); uint8_t shadeId = this->getNextShadeId();
SomfyShade *shade = nullptr; SomfyShade *shade = nullptr;
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { 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]; shade = &this->shades[i];
break;
}
} }
if(shade) { if(shade) {
shade->setShadeId(shadeId); shade->setShadeId(shadeId);
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { 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; 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++) { 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;
}
else {
id = this->m_shadeIds[i];
}
} }
sortArray(this->m_shadeIds, sizeof(this->m_shadeIds)); if(hadDups) sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
pref.begin("Shades"); pref.begin("Shades");
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
pref.end(); pref.end();
} }
return shade; return shade;
} }
void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) { 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(); somfy.transceiver.beginTransmit();
uint16_t rcode = this->getNextRollingCode(); //Serial.println("----------- Sending Raw -------------");
Serial.println("------------- Sending -------------");
Serial.print("CMD:"); Serial.print("CMD:");
Serial.print(translateSomfyCommand(cmd)); Serial.print(translateSomfyCommand(frame.cmd));
Serial.print(" ADDR:"); Serial.print(" ADDR:");
Serial.print(this->getRemoteAddress()); Serial.print(frame.remoteAddress);
Serial.print(" RCODE:"); Serial.print(" RCODE:");
Serial.println(rcode); Serial.print(frame.rollingCode);
Serial.print(" REPEAT:");
Serial.println(repeat);
byte frame[7]; byte frm[10];
this->encodeFrame(frame, cmd, rcode); frame.encodeFrame(frm);
this->sendFrame(frame, 2); this->transceiver.sendFrame(frm, 2);
for(uint8_t i = 0; i < repeat; i++) { for(uint8_t i = 0; i < repeat; i++) {
sendFrame(frame, 7); this->transceiver.sendFrame(frm, 7);
} }
somfy.transceiver.endTransmit(); this->transceiver.endTransmit();
somfy_frame_t rx;
this->decodeFrame(frame, &rx);
//rx.print();
somfy.processFrame(rx);
}
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) { bool SomfyShadeController::deleteShade(uint8_t shadeId) {
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
if(this->shades[i].getShadeId() == shadeId) { if(this->shades[i].getShadeId() == shadeId) {
shades[i].emitState("shadeRemoved");
this->shades[i].setShadeId(255); this->shades[i].setShadeId(255);
} }
} }
@ -795,7 +837,8 @@ bool SomfyShadeController::deleteShade(uint8_t shadeId) {
this->m_shadeIds[i] = 255; 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.begin("Shades");
pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.putBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
pref.end(); pref.end();
@ -819,7 +862,6 @@ uint16_t SomfyRemote::setRollingCode(uint16_t code) {
return code; return code;
} }
bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) { bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) {
Serial.println("Setting Controller Json");
doc["maxShades"] = SOMFY_MAX_SHADES; doc["maxShades"] = SOMFY_MAX_SHADES;
doc["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES; doc["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES;
doc["startingAddress"] = this->startingAddress; doc["startingAddress"] = this->startingAddress;
@ -836,13 +878,25 @@ bool SomfyShadeController::toJSON(DynamicJsonDocument &doc) {
return true; return true;
} }
bool SomfyShadeController::toJSON(JsonObject &obj) { bool SomfyShadeController::toJSON(JsonObject &obj) {
Serial.print("Setting Transceiver Json ");
obj["maxShades"] = SOMFY_MAX_SHADES; obj["maxShades"] = SOMFY_MAX_SHADES;
obj["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES; obj["maxLinkedRemotes"] = SOMFY_MAX_LINKED_REMOTES;
obj["startingAddress"] = this->startingAddress; obj["startingAddress"] = this->startingAddress;
JsonObject oradio = obj.createNestedObject("transceiver"); JsonObject oradio = obj.createNestedObject("transceiver");
this->transceiver.toJSON(oradio); this->transceiver.toJSON(oradio);
JsonArray arr = obj.createNestedArray("shades"); 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++) { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
SomfyShade &shade = this->shades[i]; SomfyShade &shade = this->shades[i];
if(shade.getShadeId() != 255) { if(shade.getShadeId() != 255) {
@ -852,6 +906,7 @@ bool SomfyShadeController::toJSON(JsonObject &obj) {
} }
return true; return true;
} }
void SomfyShadeController::loop() { void SomfyShadeController::loop() {
this->transceiver.loop(); this->transceiver.loop();
for(uint8_t i; i < SOMFY_MAX_SHADES; i++) { for(uint8_t i; i < SOMFY_MAX_SHADES; i++) {
@ -894,6 +949,51 @@ static struct somfy_rx_t
} somfy_rx; } somfy_rx;
uint8_t receive_buffer[10]; // 80 bits uint8_t receive_buffer[10]; // 80 bits
bool packet_received = false; 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() { void RECEIVE_ATTR Transceiver::handleReceive() {
static unsigned long last_time = 0; static unsigned long last_time = 0;
@ -909,19 +1009,13 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
case waiting_synchro: case waiting_synchro:
if (duration > tempo_synchro_hw_min && duration < tempo_synchro_hw_max) { 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. // 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 // 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. // to see the tempo.
//SET_TP1 WAIT CLR_TP1 WAIT SET_TP1
++somfy_rx.cpt_synchro_hw; ++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) { 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 // 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. // 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)); memset(&somfy_rx, 0x00, sizeof(somfy_rx));
somfy_rx.status = receiving_data; somfy_rx.status = receiving_data;
} }
@ -933,14 +1027,12 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
case receiving_data: case receiving_data:
// We should be receiving data at this point. // We should be receiving data at this point.
if (duration > tempo_symbol_min && duration < tempo_symbol_max && !somfy_rx.waiting_half_symbol) { 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; somfy_rx.previous_bit = 1 - somfy_rx.previous_bit;
// Bits come in high order bit first. // 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.payload[somfy_rx.cpt_bits / 8] += somfy_rx.previous_bit << (7 - somfy_rx.cpt_bits % 8);
++somfy_rx.cpt_bits; ++somfy_rx.cpt_bits;
} }
else if (duration > tempo_half_symbol_min && duration < tempo_half_symbol_max) { 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) { if (somfy_rx.waiting_half_symbol) {
somfy_rx.waiting_half_symbol = false; somfy_rx.waiting_half_symbol = false;
somfy_rx.payload[somfy_rx.cpt_bits / 8] += somfy_rx.previous_bit << (7 - somfy_rx.cpt_bits % 8); 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.cpt_synchro_hw = 0;
somfy_rx.status = waiting_synchro; somfy_rx.status = waiting_synchro;
} }
//CLR_TP1
break; break;
default: default:
break; break;
} }
if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits == bit_length) { 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. // 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); interruptPin = digitalPinToInterrupt(this->config.RXPin);
ELECHOUSE_cc1101.SetRx(); ELECHOUSE_cc1101.SetRx();
attachInterrupt(interruptPin, handleReceive, CHANGE); attachInterrupt(interruptPin, handleReceive, CHANGE);
Serial.print("Hooked interrupt #");
Serial.println(interruptPin);
} }
void Transceiver::disableReceive(void) { detachInterrupt(interruptPin); } void Transceiver::disableReceive(void) { detachInterrupt(interruptPin); }
bool Transceiver::toJSON(JsonObject& obj) { bool Transceiver::toJSON(JsonObject& obj) {
@ -1172,13 +1260,19 @@ void transceiver_config_t::apply() {
bit_length = this->type; bit_length = this->type;
Serial.print("Applying radio settings "); 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); 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); ELECHOUSE_cc1101.setGDO(this->RXPin, this->TXPin);
Serial.println("Set GDO");
ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin); ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
Serial.println("Set SPI");
ELECHOUSE_cc1101.Init(); 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. 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. 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.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.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. 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() { void Transceiver::loop() {
if (this->receive()) { if (this->receive()) {
this->clearReceived(); this->clearReceived();
somfy.processFrame(this->frame); somfy.processFrame(this->frame, false);
char buf[128]; 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); sockEmit.sendToClients("remoteFrame", buf);
} }
} }
somfy_frame_t& Transceiver::lastFrame() { return this->frame; } somfy_frame_t& Transceiver::lastFrame() { return this->frame; }
void Transceiver::beginTransmit() { void Transceiver::beginTransmit() {
this->disableReceive(); this->disableReceive();
pinMode(this->config.TXPin, OUTPUT);
ELECHOUSE_cc1101.SetTx(); ELECHOUSE_cc1101.SetTx();
} }
void Transceiver::endTransmit() { void Transceiver::endTransmit() {

45
Somfy.h
View file

@ -1,8 +1,8 @@
#ifndef SOMFY_H #ifndef SOMFY_H
#define SOMFY_H #define SOMFY_H
#define SOMFY_MAX_SHADES 5 #define SOMFY_MAX_SHADES 32
#define SOMFY_MAX_LINKED_REMOTES 2 #define SOMFY_MAX_LINKED_REMOTES 5
enum class somfy_commands : byte { enum class somfy_commands : byte {
My = 0x1, My = 0x1,
@ -25,11 +25,11 @@ typedef struct somfy_frame_t {
somfy_commands cmd; somfy_commands cmd;
uint32_t remoteAddress = 0; uint32_t remoteAddress = 0;
uint16_t rollingCode = 0; uint16_t rollingCode = 0;
uint8_t encKey = 0; uint8_t encKey = 0xA7;
uint8_t checksum = 0; uint8_t checksum = 0;
bool valid = false; bool valid = false;
void print(); 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); void decodeFrame(byte* frame);
}; };
@ -40,11 +40,6 @@ class SomfyRemote {
protected: protected:
char m_remotePrefId[10] = ""; char m_remotePrefId[10] = "";
uint32_t m_remoteAddress = 0; 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: public:
char *getRemotePrefId() {return m_remotePrefId;} char *getRemotePrefId() {return m_remotePrefId;}
virtual bool toJSON(JsonObject &obj); virtual bool toJSON(JsonObject &obj);
@ -53,7 +48,7 @@ class SomfyRemote {
virtual uint16_t getNextRollingCode(); virtual uint16_t getNextRollingCode();
virtual uint16_t setRollingCode(uint16_t code); virtual uint16_t setRollingCode(uint16_t code);
uint16_t lastRollingCode = 0; 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 { class SomfyLinkedRemote : public SomfyRemote {
public: public:
@ -64,6 +59,7 @@ class SomfyShade : public SomfyRemote {
uint8_t shadeId = 255; uint8_t shadeId = 255;
uint64_t moveStart = 0; uint64_t moveStart = 0;
float startPos = 0.0; float startPos = 0.0;
bool seekingPos = false;
public: public:
void load(); void load();
float currentPos = 0.0; float currentPos = 0.0;
@ -75,19 +71,22 @@ class SomfyShade : public SomfyRemote {
bool paired = false; bool paired = false;
bool fromJSON(JsonObject &obj); bool fromJSON(JsonObject &obj);
bool toJSON(JsonObject &obj) override; bool toJSON(JsonObject &obj) override;
char name[20] = ""; char name[21] = "";
void setShadeId(uint8_t id) { shadeId = id; } void setShadeId(uint8_t id) { shadeId = id; }
uint8_t getShadeId() { return shadeId; } uint8_t getShadeId() { return shadeId; }
uint16_t upTime = 10000; uint16_t upTime = 10000;
uint16_t downTime = 1000; uint16_t downTime = 1000;
bool save(); bool save();
void checkMovement(); void checkMovement();
void processFrame(somfy_frame_t &frame); void processFrame(somfy_frame_t &frame, bool internal = false);
void setMovement(int8_t dir); 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 linkRemote(uint32_t remoteAddress, uint16_t rollingCode = 0);
bool unlinkRemote(uint32_t remoteAddress); bool unlinkRemote(uint32_t remoteAddress);
void emitState(); void emitState(const char *evt = "shadeState");
void emitConfig(); void emitState(uint8_t num, const char *evt = "shadeState");
void publish(); void publish();
}; };
@ -95,8 +94,8 @@ typedef struct transceiver_config_t {
bool printBuffer = false; bool printBuffer = false;
uint8_t type = 56; // 56 or 80 bit protocol. uint8_t type = 56; // 56 or 80 bit protocol.
uint8_t SCKPin = 18; uint8_t SCKPin = 18;
uint8_t TXPin = 6; uint8_t TXPin = 12;
uint8_t RXPin = 4; uint8_t RXPin = 13;
uint8_t MOSIPin = 23; uint8_t MOSIPin = 23;
uint8_t MISOPin = 19; uint8_t MISOPin = 19;
uint8_t CSNPin = 5; 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 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 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. 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. int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
uint8_t syncMode = 2; // 0=No preamble/sync, uint8_t syncMode = 0; // 0=No preamble/sync,
// 1=16 sync word bits detected, // 1=16 sync word bits detected,
// 2=16/16 sync words bits detected. // 2=16/16 sync words bits detected.
// 3=30/32 sync word bits detected, // 3=30/32 sync word bits detected,
@ -177,29 +176,35 @@ class Transceiver {
void enableReceive(); void enableReceive();
void disableReceive(); void disableReceive();
somfy_frame_t& lastFrame(); somfy_frame_t& lastFrame();
void sendFrame(byte *frame, uint8_t sync);
void beginTransmit(); void beginTransmit();
void endTransmit(); void endTransmit();
}; };
class SomfyShadeController { class SomfyShadeController {
protected: protected:
uint8_t m_shadeIds[SOMFY_MAX_SHADES]; uint8_t m_shadeIds[SOMFY_MAX_SHADES];
uint8_t getNextShadeId();
public: public:
uint32_t startingAddress; uint32_t startingAddress;
uint8_t getNextShadeId();
uint32_t getNextRemoteAddress(uint8_t shadeId);
SomfyShadeController(); SomfyShadeController();
Transceiver transceiver; Transceiver transceiver;
SomfyShade *addShade(); SomfyShade *addShade();
SomfyShade *addShade(JsonObject &obj);
bool deleteShade(uint8_t shadeId); bool deleteShade(uint8_t shadeId);
bool begin(); bool begin();
void loop(); void loop();
void end(); void end();
SomfyShade shades[SOMFY_MAX_SHADES]; SomfyShade shades[SOMFY_MAX_SHADES];
bool toJSON(DynamicJsonDocument &doc); bool toJSON(DynamicJsonDocument &doc);
bool toJSON(JsonArray &arr);
bool toJSON(JsonObject &obj); bool toJSON(JsonObject &obj);
uint8_t shadeCount(); uint8_t shadeCount();
SomfyShade * getShadeById(uint8_t shadeId); SomfyShade * getShadeById(uint8_t shadeId);
SomfyShade * findShadeByRemoteAddress(uint32_t address); 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(); void publish();
}; };

1521
Web.cpp

File diff suppressed because it is too large Load diff

View file

@ -29,6 +29,23 @@
} }
</style> </style>
<script type="text/javascript"> <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. 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); }; Number.prototype.round = function (dec) { return Number(Math.round(this + 'e' + dec) + 'e-' + dec); };
@ -378,6 +395,16 @@
</script> </script>
</head> </head>
<body> <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 id="divFields"></div>
<div class="button-container"> <div class="button-container">
<button id="btnSaveSetting" type="button" style="display:inline-block;width:44%" onclick="resetDefaults();"> <button id="btnSaveSetting" type="button" style="display:inline-block;width:44%" onclick="resetDefaults();">

File diff suppressed because it is too large Load diff

View file

@ -512,3 +512,42 @@ div.waitoverlay > .lds-roller {
display: block; display: block;
overflow: hidden; 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
View 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]

46087
esp32.svd Normal file

File diff suppressed because it is too large Load diff