diff --git a/Somfy.cpp b/Somfy.cpp index 6f48bf0..195a7a7 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -91,8 +91,6 @@ String translateSomfyCommand(const somfy_commands cmd) { return "Step Up"; case somfy_commands::StepDown: return "Step Down"; - case somfy_commands::Status: - return "Status"; default: return "Unknown(" + String((uint8_t)cmd) + ")"; } @@ -172,12 +170,8 @@ void somfy_frame_t::decodeFrame(byte* frame) { case somfy_commands::UpDown: case somfy_commands::MyUpDown: case somfy_commands::Prog: - case somfy_commands::SunFlag: case somfy_commands::Flag: - break; - case somfy_commands::Status: - this->rollingCode = 0; - this->status = static_cast(decoded[3]); + case somfy_commands::SunFlag: break; case somfy_commands::UnknownC: case somfy_commands::UnknownD: @@ -931,11 +925,11 @@ void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); } void SomfyShade::emitState(uint8_t num, const char *evt) { char buf[320]; if(this->tiltType != tilt_types::none) - snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d}", - this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast(floor(this->currentPos)), static_cast(floor(this->target)), static_cast(floor(this->myPos)), static_cast(this->myTiltPos), static_cast(this->tiltType), this->tiltDirection, static_cast(floor(this->tiltTarget)), static_cast(floor(this->currentTiltPos))); + snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"myTiltPos\":%d,\"tiltType\":%u,\"tiltDirection\":%d,\"tiltTarget\":%d,\"tiltPosition\":%d,\"flags\":%d}", + this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast(floor(this->currentPos)), static_cast(floor(this->target)), static_cast(floor(this->myPos)), static_cast(this->myTiltPos), static_cast(this->tiltType), this->tiltDirection, static_cast(floor(this->tiltTarget)), static_cast(floor(this->currentTiltPos)),this->flags); else - snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"tiltType\":%u}", - this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast(floor(this->currentPos)), static_cast(floor(this->target)), static_cast(floor(this->myPos)), static_cast(this->tiltType)); + snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"type\":%u,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d,\"mypos\":%d,\"tiltType\":%u,\"flags\":%d}", + this->shadeId, static_cast(this->shadeType), this->getRemoteAddress(), this->name, this->direction, static_cast(floor(this->currentPos)), static_cast(floor(this->target)), static_cast(floor(this->myPos)), static_cast(this->tiltType), this->flags); if(num >= 255) sockEmit.sendToClients(evt, buf); else sockEmit.sendToClient(num, evt, buf); if(mqtt.connected()) { @@ -952,6 +946,7 @@ void SomfyShade::emitState(uint8_t num, const char *evt) { mqtt.publish(topic, static_cast(floor(this->myPos))); snprintf(topic, sizeof(topic), "shades/%u/tiltType", this->shadeId); mqtt.publish(topic, static_cast(this->tiltType)); + snprintf(topic, sizeof(topic), "shades/%u/flags", this->flags); if(this->tiltType != tilt_types::none) { snprintf(topic, sizeof(topic), "shades/%u/myTiltPos", this->shadeId); mqtt.publish(topic, static_cast(floor(this->myTiltPos))); @@ -1075,6 +1070,14 @@ 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::Flag: + this->flags &= ~(static_cast(somfy_flags_t::Sun)); + this->emitState(); + break; + case somfy_commands::SunFlag: + this->flags |= static_cast(somfy_flags_t::Sun); + this->emitState(); + break; case somfy_commands::Up: if(this->tiltType == tilt_types::tiltmotor) { // Wait another half second just in case we are potentially processing a tilt. diff --git a/Somfy.h b/Somfy.h index e64a565..793f0e2 100644 --- a/Somfy.h +++ b/Somfy.h @@ -28,7 +28,7 @@ enum class somfy_commands : byte { StepDown = 0xB, UnknownC = 0xC, UnknownD = 0xD, - Status = 0xE, + UnknownE = 0xE, RTWProto = 0xF, // RTW Protocol // Command extensions for 80 bit frames StepUp = 0x8B @@ -95,10 +95,9 @@ typedef struct somfy_tx_queue_t { bool push(uint32_t await, somfy_commands cmd, uint8_t repeats); }; -typedef enum { - no_sun = 0, - sun = 2 -} somfy_status_t; +enum class somfy_flags_t : byte { + Sun = 1 +}; typedef struct somfy_frame_t { bool valid = false; bool processed = false; @@ -115,7 +114,6 @@ typedef struct somfy_frame_t { uint32_t await = 0; uint8_t bitLength = 56; uint16_t pulseCount = 0; - somfy_status_t status = no_sun; void print(); void encodeFrame(byte *frame); void decodeFrame(byte* frame); @@ -161,6 +159,7 @@ class SomfyShade : public SomfyRemote { public: shade_types shadeType = shade_types::roller; tilt_types tiltType = tilt_types::none; + uint8_t flags = 0; void load(); somfy_tx_queue_t txQueue; somfy_frame_t lastFrame; diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index e11a236..e9a83b5 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 2d5e4da..6f560e7 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/data/icons.css b/data/icons.css index 2338b90..b1b9ac7 100644 --- a/data/icons.css +++ b/data/icons.css @@ -716,3 +716,150 @@ i.icss-awning { background-image: repeating-linear-gradient(90deg, var(--shade-color, currentColor), var(--shade-color, currentColor) calc(25% - 4px), black calc(25% - 2px), var(--shade-color, currentColor) calc(25% - 2px)); background-color: rgba(71, 212, 255, 0); } +i.icss-sun-c { + width: 1em; + height: 1em; + border-radius: 50%; + background-color: transparent; + background-image: radial-gradient(ellipse at center, #fd0 1%, #fb0 39%, #fb0 39%, #d61 100%); + box-shadow: 0 0 .06em .03em rgba(255, 107, 0, 0.4), 0 0 22px 11px rgba(255, 203, 0, 0.13); + background-position: -.7em -.6em; + background-size: 165%; + margin: 0; +} + + i.icss-sun-c:before { + } + + i.icss-sun-c:after { + } + +i.icss-sun { + width: .6em; + height: .6em; + border-radius: 50%; + background-color: transparent; + border: 0.03em solid transparent; + box-shadow: inset 0 0 0 .5em; + margin: .2em; +} + + i.icss-sun:before { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } + + i.icss-sun:after { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(45deg); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } +i.icss-sun-o { + width: .6em; + height: .6em; + border-radius: 50%; + background-color: transparent; + border: 0.03em solid transparent; + box-shadow: inset 0 0 0 .065em; + margin: .2em; +} + + i.icss-sun-o:before { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } + + i.icss-sun-o:after { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(45deg); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } +i.icss-sun-c { + width: 1em; + height: 1em; + border-radius: 50%; + background-color: transparent; + background-image: radial-gradient(ellipse at center, #fd0 1%, #fb0 39%, #fb0 39%, #d61 100%); + box-shadow: 0 0 .06em .03em rgba(255, 107, 0, 0.4), 0 0 22px 11px rgba(255, 203, 0, 0.13); + background-position: -.7em -.6em; + background-size: 165%; + margin: 0; +} + + i.icss-sun-c:before { + } + + i.icss-sun-c:after { + } + +i.icss-sun-o { + width: .6em; + height: .6em; + border-radius: 50%; + background-color: transparent; + border: 0.03em solid transparent; + box-shadow: inset 0 0 0 .065em; + margin: .2em; +} + + i.icss-sun-o:before { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } + + i.icss-sun-o:after { + width: .1em; + height: .1em; + background-color: transparent; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(45deg); + box-shadow: .45em 0, .38em 0, -.45em 0, -.38em 0, 0 .45em, 0 .38em, 0 -.45em, 0 -.38em; + } + +div.button-sunflag { + position: relative; + margin-left:-30px; + cursor:pointer; +} + div.button-sunflag[data-on=false] i.icss-sun-c { + background-image: radial-gradient(ellipse at center, gainsboro 1%, #ccc 39%, silver 39%, gray 100%); + } + + + div.button-sunflag > i.icss-sun-c { + position: absolute; + font-size: 18px; + color: orange; + left: 6px; + top: 7px; + } + + div.button-sunflag > i.icss-sun-o { + position: absolute; + font-size: 32px; + color: orange; + } diff --git a/data/index.js b/data/index.js index fa66693..318071e 100644 --- a/data/index.js +++ b/data/index.js @@ -1075,9 +1075,10 @@ class Somfy { divCtl += `${shade.name}`; divCtl += `${shade.myPos > 0 ? shade.myPos + '%' : '---'}` if (shade.myTiltPos > 0) divCtl += `${shade.myTiltPos > 0 ? shade.myTiltPos + '%' : '---'}` - divCtl += '' + divCtl += ''; divCtl += `
`; + divCtl += `
`; divCtl += `
`; divCtl += `
my
`; divCtl += `
`; @@ -1101,6 +1102,12 @@ class Somfy { if (new Date().getTime() - this.btnDown > 2000) event.preventDefault(); else this.sendCommand(shadeId, cmd); } + else if (cmd === 'sunflag') { + if (makeBool(event.currentTarget.getAttribute('data-on'))) + this.sendCommand(shadeId, 'flag'); + else + this.sendCommand(shadeId, 'sunflag'); + } else this.sendCommand(shadeId, cmd); }, true); btns[i].addEventListener('mousedown', (event) => { @@ -1299,6 +1306,12 @@ class Somfy { tilts[i].setAttribute('data-tiltposition', `${state.tiltPosition}`); } } + let flags = document.querySelectorAll(`.button-sunflag[data-shadeid="${state.shadeId}"]`); + for (let i = 0; i < flags.length; i++) { + flags[i].style.display = state.type === 3 ? '' : 'none'; + flags[i].setAttribute('data-on', state.flags & 0x01 === 0x01 ? 'true' : 'false'); + + } let divs = document.querySelectorAll(`.somfyShadeCtl[data-shadeid="${state.shadeId}"]`); for (let i = 0; i < divs.length; i++) { divs[i].setAttribute('data-direction', state.direction); @@ -1412,10 +1425,19 @@ class Somfy { case 1: document.getElementById('divTiltSettings').style.display = ''; if (ico.classList.contains('icss-window-shade')) ico.classList.remove('icss-window-shade'); + if (ico.classList.contains('icss-awning')) ico.classList.remove('icss-awning'); if (!ico.classList.contains('icss-window-blind')) ico.classList.add('icss-window-blind'); break; + case 3: + document.getElementById('divTiltSettings').style.display = 'none'; + if (ico.classList.contains('icss-window-shade')) ico.classList.remove('icss-window-shade'); + if (ico.classList.contains('icss-window-blind')) ico.classList.remove('icss-window-blind'); + if (!ico.classList.contains('icss-awning')) ico.classList.add('icss-awning'); + break; + default: if (ico.classList.contains('icss-window-blind')) ico.classList.remove('icss-window-blind'); + if (ico.classList.contains('icss-awning')) ico.classList.remove('icss-awning'); if (!ico.classList.contains('icss-window-shade')) ico.classList.add('icss-window-shade'); document.getElementById('divTiltSettings').style.display = 'none'; tilt = false;