80-bit and 56-bit protocols on same transceiver

Added the ability to process 80-bit and 56-bit motors on the same ESPSomfy RTS device.
This commit is contained in:
Robert Strouse 2023-03-18 18:41:04 -07:00
parent 3f662b7bd0
commit 370b8f1a23
10 changed files with 314 additions and 107 deletions

View file

@ -6,9 +6,9 @@
extern Preferences pref; extern Preferences pref;
#define SHADE_HDR_VER 1 #define SHADE_HDR_VER 2
#define SHADE_HDR_SIZE 16 #define SHADE_HDR_SIZE 16
#define SHADE_REC_SIZE 176 #define SHADE_REC_SIZE 180
bool ConfigFile::begin(const char* filename, bool readOnly) { bool ConfigFile::begin(const char* filename, bool readOnly) {
this->file = LittleFS.open(filename, readOnly ? "r" : "w"); this->file = LittleFS.open(filename, readOnly ? "r" : "w");
@ -228,7 +228,7 @@ bool ShadeConfigFile::validate() {
} }
// We should know the file size based upon the record information in the header // We should know the file size based upon the record information in the header
if(this->file.size() != this->header.length + (this->header.recordSize * this->header.records)) { if(this->file.size() != this->header.length + (this->header.recordSize * this->header.records)) {
Serial.printf("File size is not correct should be %d and got %d", this->header.length + (this->header.recordSize * this->header.records), this->file.size()); Serial.printf("File size is not correct should be %d and got %d\n", this->header.length + (this->header.recordSize * this->header.records), this->file.size());
} }
// Next check to see if the records match the header length. // Next check to see if the records match the header length.
uint8_t recs = 0; uint8_t recs = 0;
@ -282,6 +282,9 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) {
shade->setRemoteAddress(this->readUInt32(0)); shade->setRemoteAddress(this->readUInt32(0));
this->readString(shade->name, sizeof(shade->name)); this->readString(shade->name, sizeof(shade->name));
shade->hasTilt = this->readBool(false); shade->hasTilt = this->readBool(false);
if(this->header.version > 1) {
shade->bitLength = this->readUInt8(56);
}
shade->upTime = this->readUInt32(shade->upTime); shade->upTime = this->readUInt32(shade->upTime);
shade->downTime = this->readUInt32(shade->downTime); shade->downTime = this->readUInt32(shade->downTime);
shade->tiltTime = this->readUInt32(shade->tiltTime); shade->tiltTime = this->readUInt32(shade->tiltTime);
@ -314,6 +317,7 @@ bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) {
this->writeUInt32(shade->getRemoteAddress()); this->writeUInt32(shade->getRemoteAddress());
this->writeString(shade->name, sizeof(shade->name)); this->writeString(shade->name, sizeof(shade->name));
this->writeBool(shade->hasTilt); this->writeBool(shade->hasTilt);
this->writeUInt8(shade->bitLength);
this->writeUInt32(shade->upTime); this->writeUInt32(shade->upTime);
this->writeUInt32(shade->downTime); this->writeUInt32(shade->downTime);
this->writeUInt32(shade->tiltTime); this->writeUInt32(shade->tiltTime);

View file

@ -3,7 +3,7 @@
#ifndef configsettings_h #ifndef configsettings_h
#define configsettings_h #define configsettings_h
#define FW_VERSION "v1.4.1" #define FW_VERSION "v1.4.2"
enum DeviceStatus { enum DeviceStatus {
DS_OK = 0, DS_OK = 0,
DS_ERROR = 1, DS_ERROR = 1,

306
Somfy.cpp
View file

@ -1,4 +1,3 @@
#include <Arduino.h>
#include <Preferences.h> #include <Preferences.h>
#include <ELECHOUSE_CC1101_SRC_DRV.h> #include <ELECHOUSE_CC1101_SRC_DRV.h>
#include <SPI.h> #include <SPI.h>
@ -45,11 +44,12 @@ somfy_commands translateSomfyCommand(const String& string) {
else if (string.equalsIgnoreCase("Down")) return somfy_commands::Down; else if (string.equalsIgnoreCase("Down")) return somfy_commands::Down;
else if (string.equalsIgnoreCase("MyDown")) return somfy_commands::MyDown; else if (string.equalsIgnoreCase("MyDown")) return somfy_commands::MyDown;
else if (string.equalsIgnoreCase("UpDown")) return somfy_commands::UpDown; else if (string.equalsIgnoreCase("UpDown")) return somfy_commands::UpDown;
else if (string.equalsIgnoreCase("MyUpDown")) return somfy_commands::MyUpDown;
else if (string.equalsIgnoreCase("Prog")) return somfy_commands::Prog; else if (string.equalsIgnoreCase("Prog")) return somfy_commands::Prog;
else if (string.equalsIgnoreCase("SunFlag")) return somfy_commands::SunFlag; else if (string.equalsIgnoreCase("SunFlag")) return somfy_commands::SunFlag;
else if (string.equalsIgnoreCase("StepUp")) return somfy_commands::StepUp; else if (string.equalsIgnoreCase("StepUp")) return somfy_commands::StepUp;
else if (string.equalsIgnoreCase("StepDown")) return somfy_commands::StepDown;
else if (string.equalsIgnoreCase("Flag")) return somfy_commands::Flag; else if (string.equalsIgnoreCase("Flag")) return somfy_commands::Flag;
else if (string.startsWith("mud") || string.startsWith("MUD")) return somfy_commands::MyUpDown;
else if (string.startsWith("md") || string.startsWith("MD")) return somfy_commands::MyDown; else if (string.startsWith("md") || string.startsWith("MD")) return somfy_commands::MyDown;
else if (string.startsWith("ud") || string.startsWith("UD")) return somfy_commands::UpDown; else if (string.startsWith("ud") || string.startsWith("UD")) return somfy_commands::UpDown;
else if (string.startsWith("mu") || string.startsWith("MU")) return somfy_commands::MyUp; else if (string.startsWith("mu") || string.startsWith("MU")) return somfy_commands::MyUp;
@ -78,6 +78,8 @@ String translateSomfyCommand(const somfy_commands cmd) {
return "Up+Down"; return "Up+Down";
case somfy_commands::MyDown: case somfy_commands::MyDown:
return "My+Down"; return "My+Down";
case somfy_commands::MyUpDown:
return "My+Up+Down";
case somfy_commands::Prog: case somfy_commands::Prog:
return "Prog"; return "Prog";
case somfy_commands::SunFlag: case somfy_commands::SunFlag:
@ -95,12 +97,16 @@ String translateSomfyCommand(const somfy_commands cmd) {
void somfy_frame_t::decodeFrame(byte* frame) { void somfy_frame_t::decodeFrame(byte* frame) {
byte decoded[10]; byte decoded[10];
decoded[0] = frame[0]; decoded[0] = frame[0];
for (byte i = 1; i < 10; i++) { // The last 3 bytes are not encoded even on 80-bits. Go figure.
decoded[7] = frame[7];
decoded[8] = frame[8];
decoded[9] = frame[9];
for (byte i = 1; i < 7; i++) {
decoded[i] = frame[i] ^ frame[i - 1]; decoded[i] = frame[i] ^ frame[i - 1];
} }
byte checksum = 0; byte checksum = 0;
// We only want the upper nibble for the command byte. // We only want the upper nibble for the command byte.
for (byte i = 0; i < 10; i++) { for (byte i = 0; i < 7; i++) {
if (i == 1) checksum = checksum ^ (decoded[i] >> 4); if (i == 1) checksum = checksum ^ (decoded[i] >> 4);
else checksum = checksum ^ decoded[i] ^ (decoded[i] >> 4); else checksum = checksum ^ decoded[i] ^ (decoded[i] >> 4);
} }
@ -108,7 +114,8 @@ void somfy_frame_t::decodeFrame(byte* frame) {
this->checksum = decoded[1] & 0b1111; this->checksum = decoded[1] & 0b1111;
this->encKey = decoded[0]; this->encKey = decoded[0];
this->cmd = (somfy_commands)(decoded[1] >> 4); // Pull in the 80-bit commands. The upper nibble will be 0 even on 80 bit packets.
this->cmd = (somfy_commands)((decoded[1] >> 4) | ((decoded[8] & 0x08) << 4));
this->rollingCode = decoded[3] + (decoded[2] << 8); this->rollingCode = decoded[3] + (decoded[2] << 8);
this->remoteAddress = (decoded[6] + (decoded[5] << 8) + (decoded[4] << 16)); this->remoteAddress = (decoded[6] + (decoded[5] << 8) + (decoded[4] << 16));
this->valid = this->checksum == checksum && this->remoteAddress < 16777215; this->valid = this->checksum == checksum && this->remoteAddress < 16777215;
@ -122,15 +129,16 @@ void somfy_frame_t::decodeFrame(byte* frame) {
case somfy_commands::Down: case somfy_commands::Down:
case somfy_commands::MyDown: case somfy_commands::MyDown:
case somfy_commands::UpDown: case somfy_commands::UpDown:
case somfy_commands::MyUpDown:
case somfy_commands::Prog: case somfy_commands::Prog:
case somfy_commands::SunFlag: case somfy_commands::SunFlag:
case somfy_commands::Flag: case somfy_commands::Flag:
case somfy_commands::StepUp: case somfy_commands::StepUp:
case somfy_commands::StepDown: case somfy_commands::UnknownC:
case somfy_commands::Unknown7:
case somfy_commands::UnknownD: case somfy_commands::UnknownD:
case somfy_commands::UnknownE: case somfy_commands::UnknownE:
case somfy_commands::UnknownF: case somfy_commands::UnknownF:
case somfy_commands::StepDown:
break; break;
default: default:
this->valid = false; this->valid = false;
@ -147,6 +155,8 @@ void somfy_frame_t::decodeFrame(byte* frame) {
Serial.print(translateSomfyCommand(this->cmd)); Serial.print(translateSomfyCommand(this->cmd));
Serial.print(" RCODE:"); Serial.print(" RCODE:");
Serial.print(this->rollingCode); Serial.print(this->rollingCode);
Serial.print(" BITS:");
Serial.print(this->bitLength);
Serial.print(" HWSYNC:"); Serial.print(" HWSYNC:");
Serial.println(this->hwsync); Serial.println(this->hwsync);
} }
@ -184,16 +194,44 @@ void somfy_frame_t::decodeFrame(byte* frame) {
Serial.println(); Serial.println();
} }
} }
void somfy_frame_t::decodeFrame(somfy_rx_t *rx) {
this->hwsync = rx->cpt_synchro_hw;
this->pulseCount = rx->pulseCount;
this->bitLength = rx->bit_length;
this->rssi = ELECHOUSE_cc1101.getRssi();
this->decodeFrame(rx->payload);
}
void somfy_frame_t::encodeFrame(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] = this->encKey; // 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 & 0x0F) << 4; // Which button did you press? The 4 LSB will be the checksum
frame[2] = this->rollingCode >> 8; // Rolling code (big endian) frame[2] = this->rollingCode >> 8; // Rolling code (big endian)
frame[3] = this->rollingCode; // Rolling code frame[3] = this->rollingCode; // Rolling code
frame[4] = this->remoteAddress >> 16; // Remote address frame[4] = this->remoteAddress >> 16; // Remote address
frame[5] = this->remoteAddress >> 8; // Remote address frame[5] = this->remoteAddress >> 8; // Remote address
frame[6] = this->remoteAddress; // Remote address frame[6] = this->remoteAddress; // Remote address
frame[7] = 132;
frame[8] = 0;
frame[9] = 29;
switch(this->cmd) {
case somfy_commands::StepUp:
frame[7] = 136;
frame[8] = 52;
frame[9] = 22;
break;
case somfy_commands::StepDown:
frame[7] = 132;
frame[8] = 48;
frame[9] = 30;
break;
case somfy_commands::Prog:
frame[7] = 196;
frame[8] = 0;
frame[9] = 25;
break;
}
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);
} }
@ -347,6 +385,18 @@ bool SomfyShadeController::begin() {
this->loadLegacy(); this->loadLegacy();
} }
this->transceiver.begin(); this->transceiver.begin();
// Set the radio type for shades that have yet to be specified.
bool saveFlag = false;
for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) {
SomfyShade *shade = &this->shades[i];
if(shade->getShadeId() != 255 && shade->bitLength == 0) {
Serial.printf("Setting bit length to %d\n", this->transceiver.config.type);
shade->bitLength = this->transceiver.config.type;
saveFlag = true;
}
}
if(saveFlag) somfy.commit();
return true; return true;
} }
void SomfyShadeController::commit() { void SomfyShadeController::commit() {
@ -508,10 +558,10 @@ void SomfyShade::checkMovement() {
Serial.print("% target "); Serial.print("% target ");
Serial.print(this->target); Serial.print(this->target);
Serial.println("%"); Serial.println("%");
if(!this->seekingMyPos) this->sendCommand(somfy_commands::My); if(!this->seekingFixedPos) this->sendCommand(somfy_commands::My);
else this->direction = 0; else this->direction = 0;
this->seekingPos = false; this->seekingPos = false;
this->seekingMyPos = false; this->seekingFixedPos = false;
} }
} }
} }
@ -549,9 +599,9 @@ void SomfyShade::checkMovement() {
Serial.print("% target "); Serial.print("% target ");
Serial.print(this->target); Serial.print(this->target);
Serial.println("%"); Serial.println("%");
if(!this->seekingMyPos) this->sendCommand(somfy_commands::My); if(!this->seekingFixedPos) this->sendCommand(somfy_commands::My);
else this->direction = 0; else this->direction = 0;
this->seekingMyPos = false; this->seekingFixedPos = false;
this->seekingPos = false; this->seekingPos = false;
} }
} }
@ -628,7 +678,7 @@ void SomfyShade::checkMovement() {
else this->myPos = this->position; else this->myPos = this->position;
SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS); SomfyRemote::sendCommand(somfy_commands::My, SETMY_REPEATS);
this->settingMyPos = false; this->settingMyPos = false;
this->seekingMyPos = false; this->seekingFixedPos = false;
this->commitMyPosition(); this->commitMyPosition();
} }
} }
@ -778,6 +828,26 @@ void SomfyShade::processWaitingFrame() {
if(this->lastFrame.processed) return; if(this->lastFrame.processed) return;
if(this->lastFrame.await > 0 && (millis() > this->lastFrame.await)) { if(this->lastFrame.await > 0 && (millis() > this->lastFrame.await)) {
switch(this->lastFrame.cmd) { switch(this->lastFrame.cmd) {
case somfy_commands::StepUp:
this->lastFrame.processed = true;
// Simply move the shade up by 1%.
if(this->position > 0) {
this->seekingFixedPos = true;
this->seekingPos = true;
this->target = this->position - 1;
this->setMovement(-1);
}
break;
case somfy_commands::StepDown:
this->lastFrame.processed = true;
// Simply move the shade down by 1%.
if(this->position < 100) {
this->seekingFixedPos = true;
this->seekingPos = true;
this->target = this->position + 1;
this->setMovement(1);
}
break;
case somfy_commands::Down: case somfy_commands::Down:
case somfy_commands::Up: case somfy_commands::Up:
if(this->hasTilt) { // Theoretically this should get here unless it does have a tilt. if(this->hasTilt) { // Theoretically this should get here unless it does have a tilt.
@ -818,7 +888,7 @@ void SomfyShade::processWaitingFrame() {
int8_t dir = 0; int8_t dir = 0;
if(myPos < this->position) dir = -1; if(myPos < this->position) dir = -1;
else if(myPos > this->position) dir = 1; else if(myPos > this->position) dir = 1;
if(dir != 0) this->seekingMyPos = true; if(dir != 0) this->seekingFixedPos = true;
this->seekingPos = true; this->seekingPos = true;
this->target = this->myPos; this->target = this->myPos;
this->setMovement(dir); this->setMovement(dir);
@ -862,7 +932,6 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
if(!internal) this->lastFrame.await = millis() + 500; if(!internal) this->lastFrame.await = millis() + 500;
else if(this->lastFrame.repeats >= TILT_REPEATS) { else if(this->lastFrame.repeats >= TILT_REPEATS) {
// This is an internal tilt command. // This is an internal tilt command.
Serial.println("Processing Tilt UP...");
this->setTiltMovement(-1); this->setTiltMovement(-1);
this->lastFrame.processed = true; this->lastFrame.processed = true;
return; return;
@ -885,7 +954,6 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
if(!internal) this->lastFrame.await = millis() + 500; if(!internal) this->lastFrame.await = millis() + 500;
else if(this->lastFrame.repeats >= TILT_REPEATS) { else if(this->lastFrame.repeats >= TILT_REPEATS) {
// This is an internal tilt command. // This is an internal tilt command.
Serial.println("Processing Tilt DOWN...");
this->setTiltMovement(1); this->setTiltMovement(1);
this->lastFrame.processed = true; this->lastFrame.processed = true;
return; return;
@ -918,7 +986,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
else if(this->myPos > this->position) dir = 1; else if(this->myPos > this->position) dir = 1;
if(dir != 0) { if(dir != 0) {
Serial.println("Start moving to My Position"); Serial.println("Start moving to My Position");
this->seekingMyPos = true; this->seekingFixedPos = true;
} }
this->seekingPos = true; this->seekingPos = true;
this->target = this->myPos; this->target = this->myPos;
@ -929,6 +997,36 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) {
// is no my position set. // is no my position set.
this->lastFrame.processed = true; this->lastFrame.processed = true;
break; break;
case somfy_commands::StepUp:
if(!internal) {
this->lastFrame.await = millis() + 200;
}
else {
this->lastFrame.processed = true;
// Simply move the shade up by 1%.
if(this->position > 0) {
this->seekingFixedPos = true;
this->seekingPos = true;
this->target = this->position - 1;
dir = -1;
}
}
break;
case somfy_commands::StepDown:
if(!internal) {
this->lastFrame.await = millis() + 200;
}
else {
this->lastFrame.processed = true;
// Simply move the shade down by 1%.
if(this->position < 100) {
this->seekingFixedPos = true;
this->seekingPos = true;
this->target = this->position + 1;
dir = 1;
}
}
break;
default: default:
dir = 0; dir = 0;
break; break;
@ -1007,7 +1105,7 @@ void SomfyShade::moveToMyPosition() {
Serial.print("Seeking my Position:"); Serial.print("Seeking my Position:");
Serial.print(this->myPos); Serial.print(this->myPos);
Serial.println("%"); Serial.println("%");
this->seekingMyPos = true; this->seekingFixedPos = true;
this->target = this->myPos; this->target = this->myPos;
Serial.print("Moving to "); Serial.print("Moving to ");
Serial.print(this->target); Serial.print(this->target);
@ -1019,6 +1117,7 @@ void SomfyShade::moveToMyPosition() {
SomfyRemote::sendCommand(somfy_commands::My); SomfyRemote::sendCommand(somfy_commands::My);
} }
void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) { void SomfyShade::sendCommand(somfy_commands cmd, uint8_t repeat) {
if(this->bitLength == 0) this->bitLength = somfy.transceiver.config.type;
if(cmd == somfy_commands::Up) { if(cmd == somfy_commands::Up) {
this->target = 0; this->target = 0;
this->seekingPos = false; this->seekingPos = false;
@ -1133,6 +1232,7 @@ bool SomfyShade::fromJSON(JsonObject &obj) {
if(obj.containsKey("remoteAddress")) this->setRemoteAddress(obj["remoteAddress"]); if(obj.containsKey("remoteAddress")) this->setRemoteAddress(obj["remoteAddress"]);
if(obj.containsKey("tiltTime")) this->tiltTime = obj["tiltTime"]; if(obj.containsKey("tiltTime")) this->tiltTime = obj["tiltTime"];
if(obj.containsKey("hasTilt")) this->hasTilt = obj["hasTilt"]; if(obj.containsKey("hasTilt")) this->hasTilt = obj["hasTilt"];
if(obj.containsKey("bitLength")) this->bitLength = obj["bitLength"];
if(obj.containsKey("shadeType")) { if(obj.containsKey("shadeType")) {
if(obj["shadeType"].is<const char *>()) { if(obj["shadeType"].is<const char *>()) {
if(strncmp(obj["shadeType"].as<const char *>(), "roller", 7) == 0) if(strncmp(obj["shadeType"].as<const char *>(), "roller", 7) == 0)
@ -1184,6 +1284,7 @@ bool SomfyShade::toJSON(JsonObject &obj) {
obj["hasTilt"] = this->hasTilt; obj["hasTilt"] = this->hasTilt;
obj["tiltTime"] = this->tiltTime; obj["tiltTime"] = this->tiltTime;
obj["shadeType"] = static_cast<uint8_t>(this->shadeType); obj["shadeType"] = static_cast<uint8_t>(this->shadeType);
obj["bitLength"] = this->bitLength;
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++) {
@ -1347,6 +1448,8 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) {
frame.remoteAddress = this->getRemoteAddress(); frame.remoteAddress = this->getRemoteAddress();
frame.cmd = cmd; frame.cmd = cmd;
frame.repeats = repeat; frame.repeats = repeat;
frame.bitLength = this->bitLength;
if(frame.bitLength == 0) frame.bitLength = bit_length;
this->lastRollingCode = frame.rollingCode; this->lastRollingCode = frame.rollingCode;
somfy.sendFrame(frame, repeat); somfy.sendFrame(frame, repeat);
somfy.processFrame(frame, true); somfy.processFrame(frame, true);
@ -1365,9 +1468,22 @@ void SomfyShadeController::sendFrame(somfy_frame_t &frame, uint8_t repeat) {
byte frm[10]; byte frm[10];
frame.encodeFrame(frm); frame.encodeFrame(frm);
this->transceiver.sendFrame(frm, 2); this->transceiver.sendFrame(frm, frame.bitLength == 56 ? 2 : 12);
// Transform the repeat bytes
switch(frame.cmd) {
case somfy_commands::StepUp:
case somfy_commands::StepDown:
break;
case somfy_commands::Prog:
frm[7] = 132;
frm[9] = 63;
break;
default:
frm[9] = 46;
break;
}
for(uint8_t i = 0; i < repeat; i++) { for(uint8_t i = 0; i < repeat; i++) {
this->transceiver.sendFrame(frm, 7); this->transceiver.sendFrame(frm, frame.bitLength == 56 ? 7 : 6);
} }
this->transceiver.endTransmit(); this->transceiver.endTransmit();
} }
@ -1479,32 +1595,37 @@ static const uint32_t tempo_symbol_max = SYMBOL * 2 * TOLERANCE_MAX;
static const uint32_t tempo_inter_frame_gap = 30415; static const uint32_t tempo_inter_frame_gap = 30415;
static int16_t bitMin = SYMBOL * TOLERANCE_MIN; static int16_t bitMin = SYMBOL * TOLERANCE_MIN;
typedef enum {
waiting_synchro = 0,
receiving_data = 1,
complete = 2
} t_status;
#define MAX_TIMINGS 500
static uint16_t timing_index = 0; static uint16_t timing_index = 0;
static uint32_t timings[MAX_TIMINGS]; //static uint32_t timings[MAX_TIMINGS];
static struct somfy_rx_t static somfy_rx_t somfy_rx;
{ static somfy_rx_queue_t rx_queue;
t_status status;
uint8_t cpt_synchro_hw;
uint8_t cpt_bits; void somfy_rx_queue_t::init() {
uint8_t previous_bit; Serial.println("Initializing RX Queue");
bool waiting_half_symbol; memset(&this->items[0], 0x00, sizeof(somfy_rx_t) * MAX_RX_BUFFER);
uint8_t payload[10]; memset(&this->index[0], 0xFF, MAX_RX_BUFFER);
unsigned int pulses[MAX_TIMINGS]; this->length = 0;
uint16_t pulseCount; }
} somfy_rx; bool somfy_rx_queue_t::pop(somfy_rx_t *rx) {
uint8_t receive_buffer[10]; // 80 bits // Read off the data from the oldest index.
bool packet_received = false; //Serial.println("Popping RX Queue");
uint8_t m_hwsync = 0; for(uint8_t i = MAX_RX_BUFFER - 1; i >= 0; i--) {
if(this->index[i] < MAX_RX_BUFFER) {
uint8_t ndx = this->index[i];
memcpy(rx, &this->items[this->index[i]], sizeof(somfy_rx_t));
memset(&this->items[ndx], 0x00, sizeof(somfy_rx_t));
this->length--;
this->index[i] = 255;
return true;
}
}
return false;
}
void Transceiver::sendFrame(byte *frame, uint8_t sync) { void Transceiver::sendFrame(byte *frame, uint8_t sync) {
if(!this->config.enabled) return; if(!this->config.enabled) return;
uint32_t pin = 1 << this->config.TXPin; uint32_t pin = 1 << this->config.TXPin;
if (sync == 2) { // Only with the first frame. if (sync == 2 || sync == 12) { // Only with the first frame.
// Wake-up pulse & Silence // Wake-up pulse & Silence
REG_WRITE(GPIO_OUT_W1TS_REG, pin); REG_WRITE(GPIO_OUT_W1TS_REG, pin);
delayMicroseconds(9415); delayMicroseconds(9415);
@ -1532,15 +1653,11 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync) {
delayMicroseconds(SYMBOL); delayMicroseconds(SYMBOL);
REG_WRITE(GPIO_OUT_W1TS_REG, pin); REG_WRITE(GPIO_OUT_W1TS_REG, pin);
delayMicroseconds(SYMBOL); delayMicroseconds(SYMBOL);
//this->sendLow(SYMBOL);
//this->sendHigh(SYMBOL);
} else { } else {
REG_WRITE(GPIO_OUT_W1TS_REG, pin); REG_WRITE(GPIO_OUT_W1TS_REG, pin);
delayMicroseconds(SYMBOL); delayMicroseconds(SYMBOL);
REG_WRITE(GPIO_OUT_W1TC_REG, pin); REG_WRITE(GPIO_OUT_W1TC_REG, pin);
delayMicroseconds(SYMBOL); delayMicroseconds(SYMBOL);
//this->sendHigh(SYMBOL);
//this->sendLow(SYMBOL);
} }
} }
// Inter-frame silence // Inter-frame silence
@ -1560,19 +1677,25 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
last_time = time; last_time = time;
switch (somfy_rx.status) { switch (somfy_rx.status) {
case waiting_synchro: case waiting_synchro:
somfy_rx.pulses[somfy_rx.pulseCount++] = duration; if(somfy_rx.pulseCount < MAX_TIMINGS) somfy_rx.pulses[somfy_rx.pulseCount++] = duration;
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.
++somfy_rx.cpt_synchro_hw; ++somfy_rx.cpt_synchro_hw;
} }
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. It turns out that a 56 bit packet
//memset(&somfy_rx, 0x00, sizeof(somfy_rx)); // with give 4 or 14 bits of hardware sync. An 80 bit packet give 12 or 24 bits of hw sync. Early on
// I had some shorter and longer hw syncs but I can no longer repeat this.
memset(somfy_rx.payload, 0x00, sizeof(somfy_rx.payload)); memset(somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
somfy_rx.previous_bit = 0x00; somfy_rx.previous_bit = 0x00;
somfy_rx.waiting_half_symbol = false; somfy_rx.waiting_half_symbol = false;
somfy_rx.cpt_bits = 0; somfy_rx.cpt_bits = 0;
// Keep an eye on this as it is possible that we might get fewer or more synchro bits.
if (somfy_rx.cpt_synchro_hw <= 7) somfy_rx.bit_length = 56;
else if (somfy_rx.cpt_synchro_hw == 14) somfy_rx.bit_length = 56;
else if (somfy_rx.cpt_synchro_hw == 12) somfy_rx.bit_length = 80;
else if (somfy_rx.cpt_synchro_hw > 17) somfy_rx.bit_length = 80;
somfy_rx.status = receiving_data; somfy_rx.status = receiving_data;
} }
else { else {
@ -1582,7 +1705,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
} }
break; break;
case receiving_data: case receiving_data:
somfy_rx.pulses[somfy_rx.pulseCount++] = duration; if(somfy_rx.pulseCount < MAX_TIMINGS) somfy_rx.pulses[somfy_rx.pulseCount++] = duration;
// 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) {
somfy_rx.previous_bit = 1 - somfy_rx.previous_bit; somfy_rx.previous_bit = 1 - somfy_rx.previous_bit;
@ -1608,40 +1731,68 @@ void RECEIVE_ATTR Transceiver::handleReceive() {
somfy_rx.previous_bit = 0x00; somfy_rx.previous_bit = 0x00;
somfy_rx.waiting_half_symbol = false; somfy_rx.waiting_half_symbol = false;
somfy_rx.cpt_bits = 0; somfy_rx.cpt_bits = 0;
somfy_rx.bit_length = 56;
somfy_rx.status = waiting_synchro; somfy_rx.status = waiting_synchro;
} }
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 == somfy_rx.bit_length) {
timing_index = somfy_rx.pulseCount; // Since we are operating within the interrupt all data really needs to be static
memcpy(timings, somfy_rx.pulses, somfy_rx.pulseCount * sizeof(uint32_t)); // for the handoff to the frame decoder. For this reason we are buffering up to
memcpy(receive_buffer, somfy_rx.payload, sizeof(receive_buffer)); // 3 total frames. Althought it may not matter considering the lenght of a packet
packet_received = true; // will likely not push over the loop timing. For now lets assume that there
m_hwsync = somfy_rx.cpt_synchro_hw; // may be some pressure on the loop for features.
if(rx_queue.length >= MAX_RX_BUFFER) {
// We have overflowed the buffer simply empty the last item
// in this instance we will simply throw it away.
uint8_t ndx = rx_queue.index[MAX_RX_BUFFER - 1];
if(ndx < MAX_RX_BUFFER) rx_queue.items[ndx].pulseCount = 0;
//memset(&this->items[ndx], 0x00, sizeof(somfy_rx_t));
rx_queue.index[MAX_RX_BUFFER - 1] = 255;
rx_queue.length--;
}
uint8_t first = 0;
// Place this record in the first empty slot. There will
// be one since we cleared a space above should there
// be an overflow.
for(uint8_t i = 0; i < MAX_RX_BUFFER; i++) {
if(rx_queue.items[i].pulseCount == 0) {
first = i;
memcpy(&rx_queue.items[i], &somfy_rx, sizeof(somfy_rx_t));
break;
}
}
// Move the index so that it is the at position 0. The oldest item will fall off.
for(uint8_t i = MAX_RX_BUFFER - 1; i > 0; i--) {
rx_queue.index[i] = rx_queue.index[i - 1];
}
rx_queue.length++;
rx_queue.index[0] = first;
memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload)); memset(&somfy_rx.payload, 0x00, sizeof(somfy_rx.payload));
somfy_rx.cpt_synchro_hw = 0; somfy_rx.cpt_synchro_hw = 0;
somfy_rx.previous_bit = 0x00; somfy_rx.previous_bit = 0x00;
somfy_rx.waiting_half_symbol = false; somfy_rx.waiting_half_symbol = false;
somfy_rx.cpt_bits = 0; somfy_rx.cpt_bits = 0;
somfy_rx.pulseCount = 0;
somfy_rx.status = waiting_synchro; somfy_rx.status = waiting_synchro;
} }
} }
bool Transceiver::receive() { bool Transceiver::receive() {
if (packet_received) { // Check to see if there is anything in the buffer
packet_received = false; if(rx_queue.length > 0) {
this->frame.hwsync = m_hwsync; //Serial.printf("Processing receive %d\n", rx_queue.length);
this->frame.rssi = ELECHOUSE_cc1101.getRssi(); somfy_rx_t rx;
this->frame.decodeFrame(receive_buffer); rx_queue.pop(&rx);
//this->frame.lqi = ELECHOUSE_cc1101.getLqi(); this->frame.decodeFrame(&rx);
if (!this->frame.valid) this->clearReceived(); this->emitFrame(&this->frame, &rx);
this->emitFrame(&this->frame); return this->frame.valid;
return this->frame.valid;
} }
return false; return false;
} }
void Transceiver::emitFrame(somfy_frame_t *frame) { void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) {
if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) { if(sockEmit.activeClients(ROOM_EMIT_FRAME) > 0) {
ClientSocketEvent evt("remoteFrame"); ClientSocketEvent evt("remoteFrame");
char buf[30]; char buf[30];
@ -1655,19 +1806,23 @@ void Transceiver::emitFrame(somfy_frame_t *frame) {
evt.appendMessage(buf); evt.appendMessage(buf);
snprintf(buf, sizeof(buf), "\"rssi\":%d,", frame->rssi); snprintf(buf, sizeof(buf), "\"rssi\":%d,", frame->rssi);
evt.appendMessage(buf); evt.appendMessage(buf);
snprintf(buf, sizeof(buf), "\"bits\":%d,", rx->bit_length);
evt.appendMessage(buf);
snprintf(buf, sizeof(buf), "\"sync\":%d,\"pulses\":[", frame->hwsync); snprintf(buf, sizeof(buf), "\"sync\":%d,\"pulses\":[", frame->hwsync);
evt.appendMessage(buf); evt.appendMessage(buf);
for(uint16_t i = 0; i < timing_index; i++) { if(rx) {
snprintf(buf, sizeof(buf), "%s%d", i != 0 ? "," : "", timings[i]); for(uint16_t i = 0; i < rx->pulseCount; i++) {
evt.appendMessage(buf); snprintf(buf, sizeof(buf), "%s%d", i != 0 ? "," : "", rx->pulses[i]);
evt.appendMessage(buf);
}
} }
evt.appendMessage("]}"); evt.appendMessage("]}");
sockEmit.sendToRoom(ROOM_EMIT_FRAME, &evt); sockEmit.sendToRoom(ROOM_EMIT_FRAME, &evt);
} }
} }
void Transceiver::clearReceived(void) { void Transceiver::clearReceived(void) {
packet_received = false; //packet_received = false;
memset(receive_buffer, 0x00, sizeof(receive_buffer)); //memset(receive_buffer, 0x00, sizeof(receive_buffer));
if(this->config.enabled) if(this->config.enabled)
attachInterrupt(interruptPin, handleReceive, CHANGE); attachInterrupt(interruptPin, handleReceive, CHANGE);
} }
@ -1862,7 +2017,6 @@ void transceiver_config_t::load() {
this->enabled = pref.getBool("enabled", this->enabled); this->enabled = pref.getBool("enabled", this->enabled);
this->txPower = pref.getChar("txPower", this->txPower); this->txPower = pref.getChar("txPower", this->txPower);
this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth); this->rxBandwidth = pref.getFloat("rxBandwidth", this->rxBandwidth);
this->removeNVSKey("internalCCMode"); this->removeNVSKey("internalCCMode");
this->removeNVSKey("modulationMode"); this->removeNVSKey("modulationMode");
@ -1962,17 +2116,13 @@ void transceiver_config_t::apply() {
bool Transceiver::begin() { bool Transceiver::begin() {
this->config.load(); this->config.load();
this->config.apply(); this->config.apply();
rx_queue.init();
return true; return true;
} }
void Transceiver::loop() { void Transceiver::loop() {
if (this->receive()) { if (this->receive()) {
this->clearReceived(); //this->clearReceived();
somfy.processFrame(this->frame, false); somfy.processFrame(this->frame, false);
/*
char buf[177];
snprintf(buf, sizeof(buf), "{\"encKey\":%d,\"address\":%d,\"rcode\":%d,\"command\":\"%s\",\"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi);
sockEmit.sendToClients("remoteFrame", buf);
*/
} }
else { else {
somfy.processWaitingFrame(); somfy.processWaitingFrame();

46
Somfy.h
View file

@ -18,15 +18,17 @@ enum class somfy_commands : byte {
Down = 0x4, Down = 0x4,
MyDown = 0x5, MyDown = 0x5,
UpDown = 0x6, UpDown = 0x6,
Unknown7 = 0x7, MyUpDown = 0x7,
Prog = 0x8, Prog = 0x8,
SunFlag = 0x9, SunFlag = 0x9,
Flag = 0xA, Flag = 0xA,
StepUp = 0xB, StepUp = 0xB,
StepDown = 0xC, UnknownC = 0xC,
UnknownD = 0xD, UnknownD = 0xD,
UnknownE = 0xE, UnknownE = 0xE,
UnknownF = 0xF, UnknownF = 0xF,
// Command extensions for 80 bit frames
StepDown = 0x8B
}; };
enum class shade_types : byte { enum class shade_types : byte {
roller = 0x00, roller = 0x00,
@ -37,6 +39,36 @@ enum class shade_types : byte {
String translateSomfyCommand(const somfy_commands cmd); String translateSomfyCommand(const somfy_commands cmd);
somfy_commands translateSomfyCommand(const String& string); somfy_commands translateSomfyCommand(const String& string);
#define MAX_TIMINGS 300
#define MAX_RX_BUFFER 3
typedef enum {
waiting_synchro = 0,
receiving_data = 1,
complete = 2
} t_status;
typedef struct somfy_rx_t {
t_status status;
uint8_t bit_length = 56;
uint8_t cpt_synchro_hw = 0;
uint8_t cpt_bits = 0;
uint8_t previous_bit = 0;
bool waiting_half_symbol;
uint8_t payload[10];
unsigned int pulses[MAX_TIMINGS];
uint16_t pulseCount = 0;
};
// A simple FIFO queue to hold rx buffers. We are using
// a byte index to make it so we don't have to reorganize
// the storage each time we push or pop.
typedef struct somfy_rx_queue_t {
void init();
uint8_t length = 0;
uint8_t index[MAX_RX_BUFFER];
somfy_rx_t items[MAX_RX_BUFFER];
//void push(somfy_rx_t *rx);
bool pop(somfy_rx_t *rx);
};
typedef struct somfy_frame_t { typedef struct somfy_frame_t {
bool valid = false; bool valid = false;
bool processed = false; bool processed = false;
@ -50,9 +82,12 @@ typedef struct somfy_frame_t {
uint8_t hwsync = 0; uint8_t hwsync = 0;
uint8_t repeats = 0; uint8_t repeats = 0;
uint32_t await = 0; uint32_t await = 0;
uint8_t bitLength = 56;
uint16_t pulseCount = 0;
void print(); void print();
void encodeFrame(byte *frame); void encodeFrame(byte *frame);
void decodeFrame(byte* frame); void decodeFrame(byte* frame);
void decodeFrame(somfy_rx_t *rx);
bool isRepeat(somfy_frame_t &f); bool isRepeat(somfy_frame_t &f);
void copy(somfy_frame_t &f); void copy(somfy_frame_t &f);
}; };
@ -65,6 +100,7 @@ class SomfyRemote {
char m_remotePrefId[10] = ""; char m_remotePrefId[10] = "";
uint32_t m_remoteAddress = 0; uint32_t m_remoteAddress = 0;
public: public:
uint8_t bitLength = 0;
char *getRemotePrefId() {return m_remotePrefId;} char *getRemotePrefId() {return m_remotePrefId;}
virtual bool toJSON(JsonObject &obj); virtual bool toJSON(JsonObject &obj);
virtual void setRemoteAddress(uint32_t address); virtual void setRemoteAddress(uint32_t address);
@ -87,7 +123,7 @@ class SomfyShade : public SomfyRemote {
float startTiltPos = 0.00; float startTiltPos = 0.00;
bool seekingPos = false; bool seekingPos = false;
bool seekingTiltPos = false; bool seekingTiltPos = false;
bool seekingMyPos = false; bool seekingFixedPos = false;
bool settingMyPos = false; bool settingMyPos = false;
uint32_t awaitMy = 0; uint32_t awaitMy = 0;
public: public:
@ -153,7 +189,7 @@ typedef struct transceiver_config_t {
bool radioInit = false; bool radioInit = false;
float frequency = 433.42; // Basic frequency float frequency = 433.42; // Basic frequency
float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz. float deviation = 47.60; // Set the Frequency deviation in kHz. Value from 1.58 to 380.85. Default is 47.60 kHz.
float rxBandwidth = 812.5; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz. float rxBandwidth = 99.97; // Receive bandwidth in kHz. Value from 58.03 to 812.50. Default is 99.97kHz.
int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12. int8_t txPower = 10; // Transmission power {-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12}. Default is 12.
/* /*
bool internalCCMode = false; // Use internal transmission mode FIFO buffers. bool internalCCMode = false; // Use internal transmission mode FIFO buffers.
@ -234,7 +270,7 @@ class Transceiver {
void sendFrame(byte *frame, uint8_t sync); void sendFrame(byte *frame, uint8_t sync);
void beginTransmit(); void beginTransmit();
void endTransmit(); void endTransmit();
void emitFrame(somfy_frame_t *frame); void emitFrame(somfy_frame_t *frame, somfy_rx_t *rx = nullptr);
}; };
class SomfyShadeController { class SomfyShadeController {
protected: protected:

Binary file not shown.

Binary file not shown.

View file

@ -348,7 +348,7 @@ void Web::begin() {
// TODO: Do some validation of the file. // TODO: Do some validation of the file.
Serial.println("Validating restore"); Serial.println("Validating restore");
// Go through the uploaded file to determine if it is valid. // Go through the uploaded file to determine if it is valid.
somfy.loadShadesFile("/shades.tmp"); if(somfy.loadShadesFile("/shades.tmp")) somfy.commit();
} }
}); });
server.on("/index.js", []() { server.on("/index.js", []() {
@ -471,6 +471,7 @@ void Web::begin() {
JsonObject obj = doc.to<JsonObject>(); JsonObject obj = doc.to<JsonObject>();
obj["shadeId"] = shadeId; obj["shadeId"] = shadeId;
obj["remoteAddress"] = somfy.getNextRemoteAddress(shadeId); obj["remoteAddress"] = somfy.getNextRemoteAddress(shadeId);
obj["bitLength"] = somfy.transceiver.config.type;
serializeJson(doc, g_content); serializeJson(doc, g_content);
server.send(200, _encoding_json, g_content); server.send(200, _encoding_json, g_content);
}); });

View file

@ -3,10 +3,10 @@
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="main.css?v=1.4.1" type="text/css" /> <link rel="stylesheet" href="main.css?v=1.4.2" type="text/css" />
<link rel="stylesheet" href="icons.css?v=1.4.1" type="text/css" /> <link rel="stylesheet" href="icons.css?v=1.4.2" type="text/css" />
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/png" href="favicon.png" />
<script type="text/javascript" src="index.js?v=1.4.1"></script> <script type="text/javascript" src="index.js?v=1.4.2"></script>
</head> </head>
<body> <body>
<div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;"> <div id="divContainer" class="container" style="user-select:none;position:relative;border-radius:27px;">
@ -219,20 +219,28 @@
<div> <div>
<div class="field-group" style="width:127px;display:inline-block;margin-top:-20px;float:left;"> <div class="field-group" style="width:127px;display:inline-block;margin-top:-20px;float:left;">
<div class="field-group"> <div class="field-group">
<select id="selShadeType" name="shadeType" style="width:100%;" onchange="somfy.onShadeTypeChanged(this);"> <div class="field-group">
<option value="0">Roller Shade</option> <select id="selShadeBitLength" name="bitLength" style="width:100%;">
<option value="1">Blind</option> <option value="56">56-BIT</option>
<option value="2">Drapery</option> <option value="80">80-BIT</option>
</select> </select>
<label for="selShadeType">Type</label> <label for="selShadeType">Bit Length</label>
</div>
<div class="field-group">
<select id="selShadeType" name="shadeType" style="width:100%;" onchange="somfy.onShadeTypeChanged(this);">
<option value="0">Roller Shade</option>
<option value="1">Blind</option>
<option value="2">Drapery</option>
</select>
<label for="selShadeType">Type</label>
</div>
<div class="field-group">
<input id="fldShadeAddress" name="shadeAddress" type="number" length=5 placeholder="Address" style="width:100%;text-align:right;">
<label for="fldShadeAddress">Remote Address</label>
</div>
</div> </div>
<div class="field-group">
<input id="fldShadeAddress" name="shadeAddress" type="number" length=5 placeholder="Address" style="width:100%;text-align:right;">
<label for="fldShadeAddress">Remote Address</label>
</div>
</div> </div>
<div id="divSomfyButtons" style="float:right;margin-top:10px;position:relative"> <div id="divSomfyButtons" style="float:right;margin-top:27px;position:relative">
<div style="display:inline-block;margin-right:7px;position:relative;font-size:48px;"><i id="icoShade" class="somfy-shade-icon icss-window-shade" data-shadeid="0" style="--shade-position:0%;vertical-align:middle;"></i><i class="icss-window-tilt" data-tiltposition="0" style="display:none;"></i></div> <div style="display:inline-block;margin-right:7px;position:relative;font-size:48px;"><i id="icoShade" class="somfy-shade-icon icss-window-shade" data-shadeid="0" style="--shade-position:0%;vertical-align:middle;"></i><i class="icss-window-tilt" data-tiltposition="0" style="display:none;"></i></div>
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');"><i class="icss-somfy-up"></i></div> <div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'up');"><i class="icss-somfy-up"></i></div>
<div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="font-size: 2em; padding: 10px;"><span>my</span></div> <div class="button-outline" onclick="somfy.sendCommand(parseInt(document.getElementById('spanShadeId').innerText, 10), 'my');" style="font-size: 2em; padding: 10px;"><span>my</span></div>
@ -418,7 +426,7 @@
<div id="divFrameLog" class="frame-log" style="display:none;"> <div id="divFrameLog" class="frame-log" style="display:none;">
<h1 style="text-align: center;padding:10px;"><span>Frame Logs</span><span class="button-outline" onclick="document.getElementById('divFrameLog').style.display = 'none';" style="float:right;font-size:1.25rem;display:inline-block;vertical-align:middle;width:38px;height:38px;position:relative;padding-top:4px;"><span style="vertical-align:middle;clear:both;text-align:center;display:inline-block;"><i id="icoConfig" class="icss-x" style=""></i></span></span></h1> <h1 style="text-align: center;padding:10px;"><span>Frame Logs</span><span class="button-outline" onclick="document.getElementById('divFrameLog').style.display = 'none';" style="float:right;font-size:1.25rem;display:inline-block;vertical-align:middle;width:38px;height:38px;position:relative;padding-top:4px;"><span style="vertical-align:middle;clear:both;text-align:center;display:inline-block;"><i id="icoConfig" class="icss-x" style=""></i></span></span></h1>
<hr style="margin:0px;" /> <hr style="margin:0px;" />
<div class="frame-header"><span>Key</span><span>Address</span><span>Command</span><span>Code</span><span>RSSI</span></div> <div class="frame-header"><span>Key</span><span>Address</span><span>Command</span><span>Code</span><span>RSSI</span><span>Bits</span></div>
<div id="divFrames" class="frame-list"></div> <div id="divFrames" class="frame-list"></div>
<div class="button-container" style="text-align:center"> <div class="button-container" style="text-align:center">
<button type="button" class="btnCopyFrame" style="display:inline-block;width:44%;" onclick="somfy.framesToClipboard();">Copy</button> <button type="button" class="btnCopyFrame" style="display:inline-block;width:44%;" onclick="somfy.framesToClipboard();">Copy</button>

View file

@ -372,7 +372,7 @@ async function reopenSocket() {
await initSockets(); await initSockets();
} }
class General { class General {
appVersion = 'v1.4.1'; appVersion = 'v1.4.2';
reloadApp = false; reloadApp = false;
async init() { async init() {
this.setAppVersion(); this.setAppVersion();
@ -1300,7 +1300,7 @@ class Somfy {
let frames = document.getElementById('divFrames'); let frames = document.getElementById('divFrames');
let row = document.createElement('div'); let row = document.createElement('div');
row.classList.add('frame-row'); row.classList.add('frame-row');
let html = `<span>${frame.encKey}</span><span>${frame.address}</span><span>${frame.command}</span><span>${frame.rcode}</span><span>${frame.rssi}dBm</span><div class="frame-pulses">`; let html = `<span>${frame.encKey}</span><span>${frame.address}</span><span>${frame.command}</span><span>${frame.rcode}</span><span>${frame.rssi}dBm</span><span>${frame.bits}</span><div class="frame-pulses">`;
for (let i = 0; i < frame.pulses.length; i++) { for (let i = 0; i < frame.pulses.length; i++) {
if (i !== 0) html += ','; if (i !== 0) html += ',';
html += `${frame.pulses[i]}`; html += `${frame.pulses[i]}`;
@ -1398,6 +1398,7 @@ class Somfy {
document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress; document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress;
document.getElementById('divLinkedRemoteList').innerHTML = ''; document.getElementById('divLinkedRemoteList').innerHTML = '';
document.getElementById('btnSetRollingCode').style.display = 'none'; document.getElementById('btnSetRollingCode').style.display = 'none';
document.getElementById('selShadeBitLength').value = shade.bitLength || 56;
} }
}); });
} }
@ -1475,7 +1476,8 @@ class Somfy {
upTime: parseInt(document.getElementsByName('shadeUpTime')[0].value, 10), upTime: parseInt(document.getElementsByName('shadeUpTime')[0].value, 10),
downTime: parseInt(document.getElementsByName('shadeDownTime')[0].value, 10), downTime: parseInt(document.getElementsByName('shadeDownTime')[0].value, 10),
shadeType: parseInt(document.getElementById('selShadeType').value, 10), shadeType: parseInt(document.getElementById('selShadeType').value, 10),
tiltTime: parseInt(document.getElementById('fldTiltTime').value, 10) tiltTime: parseInt(document.getElementById('fldTiltTime').value, 10),
bitLength: parseInt(document.getElementById('selShadeBitLength').value, 10) || 56
}; };
if (obj.shadeType == 1) { if (obj.shadeType == 1) {
obj.hasTilt = document.getElementById('cbHasTilt').checked; obj.hasTilt = document.getElementById('cbHasTilt').checked;

View file

@ -726,6 +726,12 @@ div.frame-header > span {
div.frame-header > span:nth-child(5) { div.frame-header > span:nth-child(5) {
width: 57px; width: 57px;
} }
div.frame-row > span:nth-child(6),
div.frame-header > span:nth-child(6) {
width: 47px;
text-align:center;
}
div.frame-list > div:nth-child(2n+1) { div.frame-list > div:nth-child(2n+1) {
background: beige; background: beige;
} }