diff --git a/ConfigFile.cpp b/ConfigFile.cpp index 90d8578..edde5ab 100644 --- a/ConfigFile.cpp +++ b/ConfigFile.cpp @@ -7,12 +7,13 @@ extern Preferences pref; -#define SHADE_HDR_VER 20 -#define SHADE_HDR_SIZE 66 +#define SHADE_HDR_VER 21 +#define SHADE_HDR_SIZE 76 #define SHADE_REC_SIZE 276 #define GROUP_REC_SIZE 194 #define TRANS_REC_SIZE 74 #define ROOM_REC_SIZE 29 +#define REPEATER_REC_SIZE 77 extern ConfigSettings settings; @@ -51,7 +52,8 @@ bool ConfigFile::writeHeader(const config_header_t &hdr) { this->writeUInt8(hdr.shadeRecords); this->writeUInt16(hdr.groupRecordSize); this->writeUInt8(hdr.groupRecords); - + this->writeUInt16(hdr.repeaterRecordSize); + this->writeUInt8(hdr.repeaterRecords); this->writeUInt16(hdr.settingsRecordSize); this->writeUInt16(hdr.netRecordSize); this->writeUInt16(hdr.transRecordSize); @@ -76,6 +78,10 @@ bool ConfigFile::readHeader() { else this->header.groupRecordSize = this->readUInt8(this->header.groupRecordSize); this->header.groupRecords = this->readUInt8(this->header.groupRecords); } + if(this->header.version >= 21) { + this->header.repeaterRecordSize = this->readUInt16(this->header.repeaterRecordSize); + this->header.repeaterRecords = this->readUInt8(this->header.repeaterRecords); + } if(this->header.version > 13) { this->header.settingsRecordSize = this->readUInt16(this->header.settingsRecordSize); this->header.netRecordSize = this->readUInt16(this->header.netRecordSize); @@ -292,6 +298,8 @@ bool ShadeConfigFile::save(SomfyShadeController *s) { this->header.shadeRecords = s->shadeCount(); this->header.groupRecordSize = GROUP_REC_SIZE; this->header.groupRecords = s->groupCount(); + this->header.repeaterRecords = 1; + this->header.repeaterRecordSize = REPEATER_REC_SIZE; this->header.settingsRecordSize = 0; this->header.netRecordSize = 0; this->header.transRecordSize = 0; @@ -311,6 +319,7 @@ bool ShadeConfigFile::save(SomfyShadeController *s) { if(group->getGroupId() != 255) this->writeGroupRecord(group); } + this->writeRepeaterRecord(s); return true; } bool ShadeConfigFile::backup(SomfyShadeController *s) { @@ -322,6 +331,8 @@ bool ShadeConfigFile::backup(SomfyShadeController *s) { this->header.shadeRecords = s->shadeCount(); this->header.groupRecordSize = GROUP_REC_SIZE; this->header.groupRecords = s->groupCount(); + this->header.repeaterRecords = 1; + this->header.repeaterRecordSize = REPEATER_REC_SIZE; this->header.settingsRecordSize = settings.calcSettingsRecSize(); this->header.netRecordSize = settings.calcNetRecSize(); this->header.transRecordSize = TRANS_REC_SIZE; @@ -341,6 +352,7 @@ bool ShadeConfigFile::backup(SomfyShadeController *s) { if(group->getGroupId() != 255) this->writeGroupRecord(group); } + this->writeRepeaterRecord(s); this->writeSettingsRecord(); this->writeNetRecord(); this->writeTransRecord(s->transceiver.config); @@ -394,6 +406,9 @@ bool ShadeConfigFile::validate() { fsize += (this->header.netRecordSize); fsize += (this->header.transRecordSize); } + if(this->header.version >= 21) { + fsize += (this->header.repeaterRecordSize * this->header.repeaterRecords); + } if(this->file.size() != fsize) { Serial.printf("File size is not correct should be %d and got %d\n", fsize, this->file.size()); } @@ -442,6 +457,17 @@ bool ShadeConfigFile::validate() { } } } + if(this->header.version >= 21) { + recs = 0; + while(recs < this->header.repeaterRecords) { + uint32_t pos = this->file.position(); + if(!this->seekChar(CFG_REC_END)) { + Serial.printf("Failed to find the repeater record end %d\n", recs); + } + recs++; + + } + } this->file.seek(startPos, SeekSet); return true; } @@ -519,6 +545,15 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename, + (this->header.shadeRecords * this->header.shadeRecordSize) + (this->header.groupRecords * this->header.groupRecordSize), SeekSet); // Start at the beginning of the file after the header. } + if(opts.repeaters) { + Serial.println("Restoring Repeaters..."); + if(this->header.repeaterRecords > 0) { + memset(s->repeaters, 0x00, sizeof(uint32_t) * SOMFY_MAX_REPEATERS); + for(uint8_t i = 0; i < this->header.repeaterRecords; i++) { + this->readRepeaterRecord(s); + } + } + } if(opts.settings) { // First read out the data. this->readSettingsRecord(); @@ -654,6 +689,18 @@ bool ShadeConfigFile::readGroupRecord(SomfyGroup *group) { } return true; } +bool ShadeConfigFile::readRepeaterRecord(SomfyShadeController *s) { + uint32_t startPos = this->file.position(); + + for(uint8_t i; i < SOMFY_MAX_REPEATERS; i++) { + s->linkRepeater(this->readUInt32(0)); + } + if(this->file.position() != startPos + this->header.repeaterRecordSize) { + Serial.println("Reading to end of repeater record"); + this->seekChar(CFG_REC_END); + } + return true; +} bool ShadeConfigFile::readRoomRecord(SomfyRoom *room) { uint32_t startPos = this->file.position(); room->roomId = this->readUInt8(0); @@ -795,6 +842,11 @@ bool ShadeConfigFile::loadFile(SomfyShadeController *s, const char *filename) { ((SomfyGroup *)&s->groups[ndx++])->clear(); } } + if(this->header.repeaterRecords > 0) { + memset(s->repeaters, 0x00, sizeof(uint32_t) * SOMFY_MAX_REPEATERS); + for(uint8_t i = 0; i < this->header.repeaterRecords; i++) + this->readRepeaterRecord(s); + } if(opened) { Serial.println("Closing shade config file"); this->end(); @@ -817,13 +869,18 @@ bool ShadeConfigFile::writeGroupRecord(SomfyGroup *group) { this->writeUInt8(group->roomId, CFG_REC_END); return true; } +bool ShadeConfigFile::writeRepeaterRecord(SomfyShadeController *s) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + this->writeUInt32(s->repeaters[i], i == SOMFY_MAX_REPEATERS - 1 ? CFG_REC_END : CFG_VALUE_SEP); + } + return true; +} bool ShadeConfigFile::writeRoomRecord(SomfyRoom *room) { this->writeUInt8(room->roomId); this->writeString(room->name, sizeof(room->name)); this->writeUInt8(room->sortOrder, CFG_REC_END); return true; } - bool ShadeConfigFile::writeShadeRecord(SomfyShade *shade) { if(shade->tiltType == tilt_types::none || shade->shadeType != shade_types::blind) { shade->myTiltPos = -1; diff --git a/ConfigFile.h b/ConfigFile.h index e95668f..be603b1 100644 --- a/ConfigFile.h +++ b/ConfigFile.h @@ -14,6 +14,8 @@ struct config_header_t { uint8_t version = 1; + uint8_t repeaterRecords = 0; + uint8_t repeaterRecordSize = 0; uint8_t roomRecords = 0; uint16_t roomRecordSize = 0; uint16_t shadeRecordSize = 0; @@ -65,12 +67,14 @@ class ConfigFile { }; class ShadeConfigFile : public ConfigFile { protected: + bool writeRepeaterRecord(SomfyShadeController *s); bool writeRoomRecord(SomfyRoom *room); bool writeShadeRecord(SomfyShade *shade); bool writeGroupRecord(SomfyGroup *group); bool writeSettingsRecord(); bool writeNetRecord(); bool writeTransRecord(transceiver_config_t &cfg); + bool readRepeaterRecord(SomfyShadeController *s); bool readRoomRecord(SomfyRoom *room); bool readShadeRecord(SomfyShade *shade); bool readGroupRecord(SomfyGroup *group); diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index 409102d..2ca9308 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -14,6 +14,7 @@ void restore_options_t::fromJSON(JsonObject &obj) { if(obj.containsKey("settings")) this->settings = obj["settings"]; if(obj.containsKey("network")) this->network = obj["network"]; if(obj.containsKey("transceiver")) this->transceiver = obj["transceiver"]; + if(obj.containsKey("repeaters")) this->repeaters = obj["repeaters"]; } int8_t appver_t::compare(appver_t &ver) { if(this->major == ver.major && this->minor == ver.minor && this->build == ver.build) return 0; diff --git a/ConfigSettings.h b/ConfigSettings.h index 0f124a7..88bf3db 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -3,7 +3,7 @@ #ifndef configsettings_h #define configsettings_h -#define FW_VERSION "v2.3.3" +#define FW_VERSION "v2.4.0" enum DeviceStatus { DS_OK = 0, DS_ERROR = 1, @@ -14,6 +14,7 @@ struct restore_options_t { bool shades = false; bool network = false; bool transceiver = false; + bool repeaters = false; void fromJSON(JsonObject &obj); }; struct appver_t { diff --git a/Somfy.cpp b/Somfy.cpp index ab6acd7..56f2c55 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -29,6 +29,7 @@ uint8_t rxmode = 0; // Indicates whether the radio is in receive mode. Just to #define SETMY_REPEATS 35 #define TILT_REPEATS 15 +#define TX_QUEUE_DELAY 100 int sort_asc(const void *cmp1, const void *cmp2) { int a = *((uint8_t *)cmp1); @@ -613,7 +614,7 @@ void SomfyShade::clear() { this->lastRollingCode = 0; this->shadeType = shade_types::roller; this->tiltType = tilt_types::none; - this->txQueue.clear(); + //this->txQueue.clear(); this->currentPos = 0.0f; this->currentTiltPos = 0.0f; this->direction = 0; @@ -804,6 +805,17 @@ void SomfyGroup::compressLinkedShadeIds() { } } } +void SomfyShadeController::compressRepeaters() { + for(uint8_t i = 0, j = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(this->repeaters[i] != 0) { + if(i != j) { + this->repeaters[j] = this->repeaters[i]; + this->repeaters[i] = 0; + } + j++; + } + } +} bool SomfyGroup::hasShadeId(uint8_t shadeId) { for(uint8_t i = 0; i < SOMFY_MAX_GROUPED_SHADES; i++) { if(this->linkedShades[i] == 0) break; @@ -3164,8 +3176,9 @@ bool SomfyRemote::toJSON(JsonObject &obj) { void SomfyRemote::setRemoteAddress(uint32_t address) { this->m_remoteAddress = address; snprintf(this->m_remotePrefId, sizeof(this->m_remotePrefId), "_%lu", (unsigned long)this->m_remoteAddress); } uint32_t SomfyRemote::getRemoteAddress() { return this->m_remoteAddress; } void SomfyShadeController::processFrame(somfy_frame_t &frame, bool internal) { - 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) this->shades[i].processFrame(frame, internal); + } } void SomfyShadeController::processWaitingFrame() { for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) @@ -3300,7 +3313,13 @@ int8_t SomfyShadeController::getMaxRoomOrder() { } return order; } - +uint8_t SomfyShadeController::repeaterCount() { + uint8_t count = 0; + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(this->repeaters[i] != 0) count++; + } + return count; +} uint8_t SomfyShadeController::roomCount() { uint8_t count = 0; for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { @@ -3411,6 +3430,30 @@ SomfyShade *SomfyShadeController::addShade() { } return shade; } +bool SomfyShadeController::unlinkRepeater(uint32_t address) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(this->repeaters[i] == address) this->repeaters[i] = 0; + } + this->compressRepeaters(); + this->isDirty = true; + return true; +} +bool SomfyShadeController::linkRepeater(uint32_t address) { + bool bSet = false; + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(!bSet && this->repeaters[i] == address) bSet = true; + else if(bSet && this->repeaters[i] == address) this->repeaters[i] = 0; + } + if(!bSet) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(this->repeaters[i] == 0) { + this->repeaters[i] = address; + return true; + } + } + } + return true; +} SomfyRoom *SomfyShadeController::addRoom(JsonObject &obj) { SomfyRoom *room = this->addRoom(); if(room) { @@ -3721,6 +3764,13 @@ bool SomfyShadeController::toJSON(JsonObject &obj) { this->toJSONGroups(arrGroups); return true; } +bool SomfyShadeController::toJSONRepeaters(JsonArray &arr) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(somfy.repeaters[i] != 0) arr.add(somfy.repeaters[i]); + } + return true; +} + bool SomfyShadeController::toJSONRooms(JsonArray &arr) { for(uint8_t i = 0; i < SOMFY_MAX_ROOMS; i++) { SomfyRoom &room = this->rooms[i]; @@ -3791,6 +3841,7 @@ static const uint32_t tempo_if_gap = 30415; // Gap between frames static int16_t bitMin = SYMBOL * TOLERANCE_MIN; static somfy_rx_t somfy_rx; static somfy_rx_queue_t rx_queue; +static somfy_tx_queue_t tx_queue; bool somfy_tx_queue_t::pop(somfy_tx_t *tx) { // Read the oldest index. for(int8_t i = MAX_TX_BUFFER - 1; i >= 0; i--) { @@ -3798,39 +3849,44 @@ bool somfy_tx_queue_t::pop(somfy_tx_t *tx) { uint8_t ndx = this->index[i]; memcpy(tx, &this->items[ndx], sizeof(somfy_tx_t)); this->items[ndx].clear(); - this->length--; + if(this->length > 0) this->length--; this->index[i] = 255; return true; } } return false; } -bool somfy_tx_queue_t::push(uint32_t await, somfy_commands cmd, uint8_t repeats) { +void somfy_tx_queue_t::push(somfy_rx_t *rx) { this->push(rx->cpt_synchro_hw, rx->payload, rx->bit_length); } +void somfy_tx_queue_t::push(uint8_t hwsync, uint8_t *payload, uint8_t bit_length) { if(this->length >= MAX_TX_BUFFER) { + // We have overflowed the buffer simply empty the last item + // in this instance we will simply throw it away. uint8_t ndx = this->index[MAX_TX_BUFFER - 1]; + if(ndx < MAX_TX_BUFFER) this->items[ndx].clear(); this->index[MAX_TX_BUFFER - 1] = 255; - this->length = MAX_TX_BUFFER - 1; - if (ndx < MAX_TX_BUFFER) - this->items[ndx].clear(); + this->length--; } - // Place the command in the first empty slot. Empty slots are those - // with a millis of 0. We will shift the indexes right so that this - // is indexed int slot 0. + 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_TX_BUFFER; i++) { - if(this->items[i].await == 0) { - this->items[i].await = await; - this->items[i].cmd = cmd; - this->items[i].repeats = repeats; - // Move the index so that it is the at position 0. The oldest item will fall off. - for(uint8_t j = MAX_TX_BUFFER - 1; j > 0; j--) { - this->index[j] = this->index[j - 1]; - } - this->length++; - this->index[0] = i; - return true; + if(this->items[i].bit_length == 0) { + first = i; + this->items[i].bit_length = bit_length; + this->items[i].hwsync = hwsync; + memcpy(&this->items[i].payload, payload, sizeof(this->items[i].payload)); + break; } } - return false; + // Move the index so that it is the at position 0. The oldest item will fall off. + for(uint8_t i = MAX_TX_BUFFER - 1; i > 0; i--) { + this->index[i] = this->index[i - 1]; + } + this->length++; + // When popping from the queue we always pull from the end + this->index[0] = first; + this->delay_time = millis() + TX_QUEUE_DELAY; // We do not want to process this frame until a full frame beat has passed. } void somfy_rx_queue_t::init() { Serial.println("Initializing RX Queue"); @@ -3847,13 +3903,14 @@ bool somfy_rx_queue_t::pop(somfy_rx_t *rx) { uint8_t ndx = this->index[i]; memcpy(rx, &this->items[this->index[i]], sizeof(somfy_rx_t)); this->items[ndx].clear(); - this->length--; + if(this->length > 0) this->length--; this->index[i] = 255; return true; } } return false; } + void Transceiver::sendFrame(byte *frame, uint8_t sync, uint8_t bitLength) { if(!this->config.enabled) return; uint32_t pin = 1 << this->config.TXPin; @@ -3926,7 +3983,7 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync, uint8_t bitLength) { delayMicroseconds(13717); delayMicroseconds(13717); } - + Serial.println("Frame Sent..."); /* if(bitLength == 80) delayMicroseconds(15055); @@ -4026,7 +4083,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() { if (somfy_rx.status == receiving_data && somfy_rx.cpt_bits >= somfy_rx.bit_length) { // Since we are operating within the interrupt all data really needs to be static // for the handoff to the frame decoder. For this reason we are buffering up to - // 3 total frames. Althought it may not matter considering the lenght of a packet + // 3 total frames. Althought it may not matter considering the length of a packet // will likely not push over the loop timing. For now lets assume that there // may be some pressure on the loop for features. if(rx_queue.length >= MAX_RX_BUFFER) { @@ -4127,14 +4184,13 @@ void Transceiver::emitFrequencyScan(uint8_t num) { if(num >= 255) sockEmit.sendToClients("frequencyScan", buf); else sockEmit.sendToClient(num, "frequencyScan", buf); } -bool Transceiver::receive() { +bool Transceiver::receive(somfy_rx_t *rx) { // Check to see if there is anything in the buffer if(rx_queue.length > 0) { //Serial.printf("Processing receive %d\n", rx_queue.length); - somfy_rx_t rx; - rx_queue.pop(&rx); - this->frame.decodeFrame(&rx); - this->emitFrame(&this->frame, &rx); + rx_queue.pop(rx); + this->frame.decodeFrame(rx); + this->emitFrame(&this->frame, rx); return this->frame.valid; } return false; @@ -4555,21 +4611,54 @@ bool Transceiver::begin() { return true; } void Transceiver::loop() { + somfy_rx_t rx; if(rxmode == 3) { - if(rx_queue.length > 0) { - //Serial.printf("Processing receive %d\n", rx_queue.length); - somfy_rx_t rx; - rx_queue.pop(&rx); - this->frame.decodeFrame(&rx); - this->processFrequencyScan(this->frame.valid); - } + if(this->receive(&rx)) + this->processFrequencyScan(true); else this->processFrequencyScan(false); } - else if (this->receive()) + else if (this->receive(&rx)) { + for(uint8_t i = 0; i < SOMFY_MAX_REPEATERS; i++) { + if(somfy.repeaters[i] == frame.remoteAddress) { + tx_queue.push(&rx); + Serial.println("Queued repeater frame..."); + break; + } + } somfy.processFrame(this->frame, false); - else + } + else { somfy.processWaitingFrame(); + // Check to see if there is anything in the buffer + if(tx_queue.length > 0 && millis() > tx_queue.delay_time && somfy_rx.cpt_synchro_hw == 0) { + this->beginTransmit(); + somfy_tx_t tx; + + tx_queue.pop(&tx); + Serial.printf("Sending frame %d - %d-BIT [", tx.hwsync, tx.bit_length); + for(uint8_t j = 0; j < 10; j++) { + Serial.print(tx.payload[j]); + if(j < 9) Serial.print(", "); + } + Serial.println("]"); + this->sendFrame(tx.payload, tx.hwsync, tx.bit_length); + tx_queue.delay_time = millis() + TX_QUEUE_DELAY; + + /* + while(tx_queue.length > 0 && tx_queue.pop(&tx)) { + Serial.printf("Sending frame %d - %d-BIT [", tx.hwsync, tx.bit_length); + for(uint8_t j = 0; j < 10; j++) { + Serial.print(tx.payload[j]); + if(j < 9) Serial.print(", "); + } + Serial.println("]"); + this->sendFrame(tx.payload, tx.hwsync, tx.bit_length); + } + */ + this->endTransmit(); + } + } } somfy_frame_t& Transceiver::lastFrame() { return this->frame; } void Transceiver::beginTransmit() { diff --git a/Somfy.h b/Somfy.h index b91814d..414beec 100644 --- a/Somfy.h +++ b/Somfy.h @@ -7,6 +7,7 @@ #define SOMFY_MAX_LINKED_REMOTES 7 #define SOMFY_MAX_GROUPED_SHADES 32 #define SOMFY_MAX_ROOMS 16 +#define SOMFY_MAX_REPEATERS 7 #define SECS_TO_MILLIS(x) ((x) * 1000) #define MINS_TO_MILLIS(x) SECS_TO_MILLIS((x) * 60) @@ -77,7 +78,7 @@ somfy_commands translateSomfyCommand(const String& string); #define MAX_TIMINGS 300 #define MAX_RX_BUFFER 3 -#define MAX_TX_BUFFER 3 +#define MAX_TX_BUFFER 5 typedef enum { waiting_synchro = 0, @@ -115,34 +116,35 @@ struct somfy_rx_queue_t { uint8_t length = 0; uint8_t index[MAX_RX_BUFFER]; somfy_rx_t items[MAX_RX_BUFFER]; - //void push(somfy_rx_t *rx); + void push(somfy_rx_t *rx); bool pop(somfy_rx_t *rx); }; struct somfy_tx_t { void clear() { - this->await = 0; - this->cmd = somfy_commands::Unknown0; - this->repeats = 0; + this->hwsync = 0; + this->bit_length = 0; + memset(this->payload, 0x00, sizeof(this->payload)); } - uint32_t await = 0; - somfy_commands cmd; - uint8_t repeats; + uint8_t hwsync = 0; + uint8_t bit_length = 0; + uint8_t payload[10] = {}; }; struct somfy_tx_queue_t { - somfy_tx_queue_t() { - this->clear(); - } + somfy_tx_queue_t() { this->clear(); } void clear() { for (uint8_t i = 0; i < MAX_TX_BUFFER; i++) { this->index[i] = 255; this->items[i].clear(); } + this->length = 0; } + unsigned long delay_time = 0; uint8_t length = 0; - uint8_t index[MAX_TX_BUFFER]; + uint8_t index[MAX_TX_BUFFER] = {255}; somfy_tx_t items[MAX_TX_BUFFER]; bool pop(somfy_tx_t *tx); - bool push(uint32_t await, somfy_commands cmd, uint8_t repeats); + void push(somfy_rx_t *rx); // Used for repeats + void push(uint8_t hwsync, byte *payload, uint8_t bit_length); }; enum class somfy_flags_t : byte { @@ -157,6 +159,11 @@ enum class somfy_flags_t : byte { enum class gpio_flags_t : byte { LowLevelTrigger = 0x01 }; +struct somfy_relay_t { + uint32_t remoteAddress = 0; + uint8_t sync = 0; + byte frame[10] = {0}; +}; struct somfy_frame_t { bool valid = false; bool processed = false; @@ -270,7 +277,7 @@ class SomfyShade : public SomfyRemote { #ifdef USE_NVS void load(); #endif - somfy_tx_queue_t txQueue; + //somfy_tx_queue_t txQueue; float currentPos = 0.0f; float currentTiltPos = 0.0f; //uint16_t movement = 0; @@ -479,7 +486,7 @@ class Transceiver { bool begin(); void loop(); bool end(); - bool receive(); + bool receive(somfy_rx_t *rx); void clearReceived(); void enableReceive(); void disableReceive(); @@ -523,14 +530,20 @@ class SomfyShadeController { bool begin(); void loop(); void end(); + void compressRepeaters(); + uint32_t repeaters[SOMFY_MAX_REPEATERS] = {0}; SomfyRoom rooms[SOMFY_MAX_ROOMS]; SomfyShade shades[SOMFY_MAX_SHADES]; SomfyGroup groups[SOMFY_MAX_GROUPS]; + bool linkRepeater(uint32_t address); + bool unlinkRepeater(uint32_t address); bool toJSON(DynamicJsonDocument &doc); bool toJSON(JsonObject &obj); bool toJSONRooms(JsonArray &arr); bool toJSONShades(JsonArray &arr); bool toJSONGroups(JsonArray &arr); + bool toJSONRepeaters(JsonArray &arr); + uint8_t repeaterCount(); uint8_t roomCount(); uint8_t shadeCount(); uint8_t groupCount(); diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index dcf250d..6ce0f47 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/SomfyController.ino.esp32s3.bin b/SomfyController.ino.esp32s3.bin index 5d1a538..f0c25b9 100644 Binary files a/SomfyController.ino.esp32s3.bin and b/SomfyController.ino.esp32s3.bin differ diff --git a/SomfyController.littlefs.bin b/SomfyController.littlefs.bin index dcda1de..2bc9a4a 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/Web.cpp b/Web.cpp index 4934d89..32be4a3 100644 --- a/Web.cpp +++ b/Web.cpp @@ -312,6 +312,14 @@ void Web::handleController(WebServer &server) { this->chunkShadesResponse(server); server.sendContent(",\"groups\":"); this->chunkGroupsResponse(server); + server.sendContent(",\"repeaters\":"); + { + DynamicJsonDocument doc(512); + JsonArray r = doc.to(); + somfy.toJSONRepeaters(r); + serializeJson(doc, g_content); + server.sendContent(g_content); + } server.sendContent("}"); server.sendContent("", 0); } @@ -331,6 +339,19 @@ void Web::handleLoginContext(WebServer &server) { serializeJson(doc, g_content); server.send(200, _encoding_json, g_content); } +void Web::handleGetRepeaters(WebServer &server) { + webServer.sendCORSHeaders(server); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + HTTPMethod method = server.method(); + if (method == HTTP_POST || method == HTTP_GET) { + DynamicJsonDocument doc(512); + JsonArray r = doc.to(); + somfy.toJSONRepeaters(r); + serializeJson(doc, g_content); + server.send(200, _encoding_json, g_content); + } + else server.send(404, _encoding_text, _response_404); +} void Web::handleGetRooms(WebServer &server) { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } @@ -1654,6 +1675,77 @@ void Web::begin() { } } }); + server.on("/linkRepeater", []() { + webServer.sendCORSHeaders(server); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + HTTPMethod method = server.method(); + if (method == HTTP_PUT || method == HTTP_POST) { + // We are adding a linked repeater. + uint32_t address = 0; + if (server.hasArg("plain")) { + Serial.println("Linking a repeater"); + DynamicJsonDocument doc(512); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + webServer.handleDeserializationError(server, err); + return; + } + else { + JsonObject obj = doc.as(); + if (obj.containsKey("address")) address = obj["address"]; + else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; + } + } + else if(server.hasArg("address")) + address = atoi(server.arg("address").c_str()); + if(address == 0) + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); + else { + somfy.linkRepeater(address); + DynamicJsonDocument doc(512); + JsonArray r = doc.to(); + somfy.toJSONRepeaters(r); + serializeJson(doc, g_content); + server.send(200, _encoding_json, g_content); + } + } + }); + server.on("/unlinkRepeater", []() { + webServer.sendCORSHeaders(server); + if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } + HTTPMethod method = server.method(); + if (method == HTTP_PUT || method == HTTP_POST) { + // We are adding a linked repeater. + uint32_t address = 0; + if (server.hasArg("plain")) { + Serial.println("Unlinking a repeater"); + DynamicJsonDocument doc(512); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if (err) { + webServer.handleDeserializationError(server, err); + return; + } + else { + JsonObject obj = doc.as(); + if (obj.containsKey("address")) address = obj["address"]; + else if(obj.containsKey("remoteAddress")) address = obj["remoteAddress"]; + } + } + else if(server.hasArg("address")) + address = atoi(server.arg("address").c_str()); + if(address == 0) + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No repeater address was supplied.\"}")); + else { + somfy.unlinkRepeater(address); + DynamicJsonDocument doc(512); + JsonArray r = doc.to(); + somfy.toJSONRepeaters(r); + serializeJson(doc, g_content); + server.send(200, _encoding_json, g_content); + } + } + }); + server.on("/unlinkRemote", []() { webServer.sendCORSHeaders(server); if(server.method() == HTTP_OPTIONS) { server.send(200, "OK"); return; } diff --git a/Web.h b/Web.h index d7bddaf..b01ac23 100644 --- a/Web.h +++ b/Web.h @@ -12,6 +12,7 @@ class Web { void handleStreamFile(WebServer &server, const char *filename, const char *encoding); void handleController(WebServer &server); void handleLoginContext(WebServer &server); + void handleGetRepeaters(WebServer &server); void handleGetRooms(WebServer &server); void handleGetShades(WebServer &server); void handleGetGroups(WebServer &server); diff --git a/data/appversion b/data/appversion index 45674f1..9183195 100644 --- a/data/appversion +++ b/data/appversion @@ -1 +1 @@ -2.3.3 \ No newline at end of file +2.4.0 \ No newline at end of file diff --git a/data/index.html b/data/index.html index 3dab4c6..e04b3d8 100644 --- a/data/index.html +++ b/data/index.html @@ -3,11 +3,11 @@ - - - + + + - +
@@ -295,7 +295,7 @@