diff --git a/Somfy.cpp b/Somfy.cpp index 4b7199e..833c983 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -50,12 +50,14 @@ somfy_commands translateSomfyCommand(const String& string) { 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("Sensor")) return somfy_commands::Sensor; 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("ud") || string.startsWith("UD")) return somfy_commands::UpDown; else if (string.startsWith("mu") || string.startsWith("MU")) return somfy_commands::MyUp; else if (string.startsWith("su") || string.startsWith("SU")) return somfy_commands::StepUp; else if (string.startsWith("sd") || string.startsWith("SD")) return somfy_commands::StepDown; + else if (string.startsWith("sen") || string.startsWith("SEN")) return somfy_commands::Sensor; else if (string.startsWith("p") || string.startsWith("P")) return somfy_commands::Prog; else if (string.startsWith("u") || string.startsWith("U")) return somfy_commands::Up; else if (string.startsWith("d") || string.startsWith("D")) return somfy_commands::Down; @@ -91,6 +93,8 @@ String translateSomfyCommand(const somfy_commands cmd) { return "Step Up"; case somfy_commands::StepDown: return "Step Down"; + case somfy_commands::Sensor: + return "Sensor"; default: return "Unknown(" + String((uint8_t)cmd) + ")"; } @@ -172,6 +176,7 @@ void somfy_frame_t::decodeFrame(byte* frame) { case somfy_commands::Prog: case somfy_commands::Flag: case somfy_commands::SunFlag: + case somfy_commands::Sensor: break; case somfy_commands::UnknownC: case somfy_commands::UnknownD: @@ -291,8 +296,6 @@ void somfy_frame_t::encodeFrame(byte *frame) { case somfy_commands::Flag: frame[0] = 142; break; - default: - break; } } else { @@ -312,8 +315,6 @@ void somfy_frame_t::encodeFrame(byte *frame) { frame[8] = 0; frame[9] = 25; break; - default: - break; } } byte checksum = 0; @@ -915,6 +916,9 @@ void SomfyShade::publish() { mqtt.publish(topic, static_cast(this->shadeType)); snprintf(topic, sizeof(topic), "shades/%u/tiltType", this->shadeId); mqtt.publish(topic, static_cast(this->tiltType)); + snprintf(topic, sizeof(topic), "shades/%u/flags", this->shadeId); + mqtt.publish(topic, this->flags); + if(this->tiltType != tilt_types::none) { snprintf(topic, sizeof(topic), "shades/%u/tiltDirection", this->shadeId); mqtt.publish(topic, this->tiltDirection); @@ -1043,8 +1047,6 @@ void SomfyShade::processWaitingFrame() { Serial.println(" repeats"); } break; - default: - break; } } } @@ -1057,7 +1059,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { if(!hasRemote) { for(uint8_t i = 0; i < SOMFY_MAX_LINKED_REMOTES; i++) { if(this->linkedRemotes[i].getRemoteAddress() == frame.remoteAddress) { - this->linkedRemotes[i].setRollingCode(frame.rollingCode); + if(frame.cmd != somfy_commands::Sensor) this->linkedRemotes[i].setRollingCode(frame.rollingCode); hasRemote = true; break; } @@ -1066,6 +1068,7 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { if(!hasRemote) return; this->lastFrame.copy(frame); int8_t dir = 0; + int8_t tiltDir = 0; this->moveStart = this->tiltStart = millis(); this->startPos = this->currentPos; this->startTiltPos = this->currentTiltPos; @@ -1075,12 +1078,20 @@ void SomfyShade::processFrame(somfy_frame_t &frame, bool internal) { // At this point we are not processing the combo buttons // will need to see what the shade does when you press both. switch(frame.cmd) { + case somfy_commands::Sensor: + if((frame.rollingCode << 4) & static_cast(somfy_flags_t::Sunny)) this->flags |= static_cast(somfy_flags_t::Sunny); + else this->flags &= ~(static_cast(somfy_flags_t::Sunny)); + if((frame.rollingCode << 4) & static_cast(somfy_flags_t::Windy)) this->flags |= static_cast(somfy_flags_t::Windy); + else this->flags &= ~(static_cast(somfy_flags_t::Windy)); + this->emitState(); + break; + case somfy_commands::Flag: - this->flags &= ~(static_cast(somfy_flags_t::Sun)); + this->flags &= ~(static_cast(somfy_flags_t::SunFlag)); this->emitState(); break; case somfy_commands::SunFlag: - this->flags |= static_cast(somfy_flags_t::Sun); + this->flags |= static_cast(somfy_flags_t::SunFlag); this->emitState(); break; case somfy_commands::Up: @@ -1341,6 +1352,7 @@ void SomfyShade::sendTiltCommand(somfy_commands cmd) { } } void SomfyShade::moveToTiltTarget(float target) { + int8_t newDir = 0; somfy_commands cmd = somfy_commands::My; if(target < this->currentTiltPos) cmd = somfy_commands::Up; @@ -1365,6 +1377,7 @@ void SomfyShade::moveToTiltTarget(float target) { this->settingTiltPos = true; } void SomfyShade::moveToTarget(float pos, float tilt) { + int8_t newDir = 0; somfy_commands cmd = somfy_commands::My; if(pos < this->currentPos) cmd = somfy_commands::Up; @@ -1556,6 +1569,7 @@ void SomfyShadeController::publish() { mqtt.publish("shades", arr); } uint8_t SomfyShadeController::getNextShadeId() { + uint8_t nextId = 0; // There is no shortcut for this since the deletion of // a shade in the middle makes all of this very difficult. for(uint8_t i = 1; i < SOMFY_MAX_SHADES - 1; i++) { @@ -1795,7 +1809,7 @@ bool SomfyShadeController::toJSON(JsonArray &arr) { } void SomfyShadeController::loop() { this->transceiver.loop(); - for(uint8_t i = 0; i < SOMFY_MAX_SHADES; i++) { + for(uint8_t i; i < SOMFY_MAX_SHADES; i++) { if(this->shades[i].getShadeId() != 255) this->shades[i].checkMovement(); } // Only commit the file once per second. @@ -1827,16 +1841,17 @@ static const uint32_t tempo_if_gap = 30415; // Gap between frames static int16_t bitMin = SYMBOL * TOLERANCE_MIN; +static uint16_t timing_index = 0; static somfy_rx_t somfy_rx; static somfy_rx_queue_t rx_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--) { + for(uint8_t i = MAX_TX_BUFFER - 1; i >= 0; i--) { if(this->index[i] < MAX_TX_BUFFER) { uint8_t ndx = this->index[i]; memcpy(tx, &this->items[ndx], sizeof(somfy_tx_t)); - this->items[ndx].clear(); + memset(&this->items[ndx], 0x00, sizeof(somfy_tx_t)); this->length--; this->index[i] = 255; return true; @@ -1849,8 +1864,7 @@ bool somfy_tx_queue_t::push(uint32_t await, somfy_commands cmd, uint8_t repeats) uint8_t ndx = this->index[MAX_TX_BUFFER - 1]; this->index[MAX_TX_BUFFER - 1] = 255; this->length = MAX_TX_BUFFER - 1; - if(ndx < MAX_TX_BUFFER) - this->items[ndx].clear(); + if(ndx < MAX_TX_BUFFER) memset(&this->items[ndx], 0x00, sizeof(somfy_tx_t)); } // 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 @@ -1873,8 +1887,7 @@ bool somfy_tx_queue_t::push(uint32_t await, somfy_commands cmd, uint8_t repeats) } void somfy_rx_queue_t::init() { Serial.println("Initializing RX Queue"); - for (uint8_t i = 0; i < MAX_RX_BUFFER; i++) - this->items[i].clear(); + memset(&this->items[0], 0x00, sizeof(somfy_rx_t) * MAX_RX_BUFFER); memset(&this->index[0], 0xFF, MAX_RX_BUFFER); this->length = 0; } @@ -1882,11 +1895,11 @@ void somfy_rx_queue_t::init() { bool somfy_rx_queue_t::pop(somfy_rx_t *rx) { // Read off the data from the oldest index. //Serial.println("Popping RX Queue"); - for(int8_t i = MAX_RX_BUFFER - 1; i >= 0; i--) { + 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)); - this->items[ndx].clear(); + memset(&this->items[ndx], 0x00, sizeof(somfy_rx_t)); this->length--; this->index[i] = 255; return true; @@ -2128,7 +2141,7 @@ void Transceiver::emitFrame(somfy_frame_t *frame, somfy_rx_t *rx) { evt.appendMessage(buf); snprintf(buf, sizeof(buf), "\"rcode\":%d,", frame->rollingCode); evt.appendMessage(buf); - snprintf(buf, sizeof(buf), "\"command\":\"%s\",", translateSomfyCommand(frame->cmd).c_str()); + snprintf(buf, sizeof(buf), "\"command\":\"%s\",", translateSomfyCommand(frame->cmd)); evt.appendMessage(buf); snprintf(buf, sizeof(buf), "\"rssi\":%d,", frame->rssi); evt.appendMessage(buf); diff --git a/Somfy.h b/Somfy.h index d9ba5fc..54663c5 100644 --- a/Somfy.h +++ b/Somfy.h @@ -28,7 +28,7 @@ enum class somfy_commands : byte { StepDown = 0xB, UnknownC = 0xC, UnknownD = 0xD, - UnknownE = 0xE, + Sensor = 0xE, RTWProto = 0xF, // RTW Protocol // Command extensions for 80 bit frames StepUp = 0x8B @@ -59,17 +59,6 @@ typedef enum { } t_status; struct somfy_rx_t { - void clear() { - this->status = t_status::waiting_synchro; - this->bit_length = 56; - this->cpt_synchro_hw = 0; - this->cpt_bits = 0; - this->previous_bit = 0; - this->waiting_half_symbol = false; - memset(this->payload, 0, sizeof(this->payload)); - memset(this->pulses, 0, sizeof(this->pulses)); - this->pulseCount = 0; - } t_status status; uint8_t bit_length = 56; uint8_t cpt_synchro_hw = 0; @@ -92,25 +81,13 @@ struct somfy_rx_queue_t { bool pop(somfy_rx_t *rx); }; struct somfy_tx_t { - void clear() { - this->await = 0; - this->cmd = somfy_commands::Unknown0; - this->repeats = 0; - } uint32_t await = 0; somfy_commands cmd; uint8_t repeats; }; struct somfy_tx_queue_t { - somfy_tx_queue_t() { - this->clear(); - } - void clear() { - for (uint8_t i = 0; i < MAX_TX_BUFFER; i++) { - this->index[i] = 255; - this->items[i].clear(); - } - } + somfy_tx_queue_t() { memset(this->index, 255, MAX_TX_BUFFER); memset(&this->items[0], 0x00, sizeof(somfy_tx_queue_t) * MAX_TX_BUFFER); } + void clear() { memset(&this->index[0], 255, MAX_TX_BUFFER); memset(&this->items[0], 0x00, sizeof(somfy_tx_queue_t) * MAX_TX_BUFFER); } uint8_t length = 0; uint8_t index[MAX_TX_BUFFER]; somfy_tx_t items[MAX_TX_BUFFER]; @@ -119,7 +96,9 @@ struct somfy_tx_queue_t { }; enum class somfy_flags_t : byte { - Sun = 1 + SunFlag = 0x01, + Windy = 0x10, + Sunny = 0x20 }; struct somfy_frame_t { bool valid = false; diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index 605c853..4636f35 100644 Binary files a/SomfyController.ino.esp32.bin and b/SomfyController.ino.esp32.bin differ diff --git a/SomfyController.littlefs.bin b/SomfyController.littlefs.bin index 25a5187..240d795 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/data/icons.css b/data/icons.css index b1b9ac7..524c552 100644 --- a/data/icons.css +++ b/data/icons.css @@ -863,3 +863,56 @@ div.button-sunflag { font-size: 32px; color: orange; } +i.icss-warning { + width: 1em; + height: .1em; + border-radius: 0.05em; + margin: .82em 0 .06em 0; + transform-origin: 50% -.33em; +} +div.indicator { + position: absolute; + top:34px; + font-size:20px; +} +div.indicator-wind { + color: orangered; + clip-path: polygon(48% 18%, 0% 95%, 110% 92%); + background:yellow; + top:30%; + left:13px; +} +div.indicator-sun { + color: darkorange; + left: -13px; + top:3px; +} +.somfyShadeCtl[data-sunny="false"] .indicator-sun { + display:none; +} +.somfyShadeCtl[data-windy="false"] .indicator-wind { + display: none; +} + + i.icss-warning:before { + width: 1.23em; + height: 1.23em; + border: 0.2em solid currentColor; + border-color: currentColor currentColor transparent transparent; + border-radius: .1em .13em; + transform: translateX(-50%) scaleX(0.6) rotate(-45deg); + top: -.55em; + left: 50%; + } + + i.icss-warning:after { + width: .125em; + border: .0em solid transparent; + border-top: .25em solid currentColor; + border-bottom: .07em solid transparent; + box-shadow: 0 .1em 0 -.012em; + top: -.44em; + left: 50%; + transform: translateX(-50%); + } + diff --git a/data/index.js b/data/index.js index 8d86368..575223a 100644 --- a/data/index.js +++ b/data/index.js @@ -1056,8 +1056,9 @@ class Somfy { divCfg += ''; divCtl += `
`; divCtl += ``; divCtl += shade.tiltType !== 0 ? `
` : '
'; + divCtl += `
`; divCtl += `
`; + divCtl += `${shade.name}`; divCtl += `${shade.myPos > 0 ? shade.myPos + '%' : '---'}` if (shade.myTiltPos > 0) divCtl += `${shade.myTiltPos > 0 ? shade.myTiltPos + '%' : '---'}` @@ -1321,6 +1324,8 @@ class Somfy { divs[i].setAttribute('data-position', state.position); divs[i].setAttribute('data-target', state.target); divs[i].setAttribute('data-mypos', state.mypos); + divs[i].setAttribute('data-windy', (state.flags & 0x10) === 0x10 ? 'true' : 'false'); + divs[i].setAttribute('data-sunny', (state.flags & 0x20) === 0x20 ? 'true' : 'false'); if (typeof state.myTiltPos !== 'undefined') divs[i].setAttribute('data-mytiltpos', state.myTiltPos); else divs[i].setAttribute('data-mytiltpos', -1); if (state.tiltType !== 0) {