diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index 7e3813f..5643cb4 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -8,7 +8,6 @@ Preferences pref; bool BaseSettings::load() { return true; } -bool BaseSettings::save() { return true; } bool BaseSettings::loadFile(const char *filename) { size_t filesize = 10; String data = ""; diff --git a/ConfigSettings.h b/ConfigSettings.h index 4d28234..3ea217a 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -38,7 +38,6 @@ class NTPSettings: BaseSettings { class WifiSettings: BaseSettings { public: WifiSettings(); -// char hostname[32] = "ESPSomfyRTS"; char ssid[64] = ""; char passphrase[64] = ""; //bool ssdpBroadcast = true; diff --git a/Somfy.cpp b/Somfy.cpp index bcc41d8..85f3319 100644 --- a/Somfy.cpp +++ b/Somfy.cpp @@ -1455,6 +1455,7 @@ void SomfyRemote::sendCommand(somfy_commands cmd, uint8_t repeat) { frame.cmd = cmd; frame.repeats = repeat; frame.bitLength = this->bitLength; + // Match the encKey to the rolling code. These keys range from 160 to 175. frame.encKey = 0xA0 | static_cast(frame.rollingCode & 0x000F); if(frame.bitLength == 0) frame.bitLength = bit_length; this->lastRollingCode = frame.rollingCode; @@ -1684,11 +1685,16 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync, uint8_t bitLength) { delayMicroseconds(SYMBOL); } } + // End with a 0 no matter what. This accommodates the 56-bit protocol by telling the + // motor that there are no more follow on bits. + REG_WRITE(GPIO_OUT_W1TS_REG, pin); + //delayMicroseconds(SYMBOL/2); + // Inter-frame silence for 56-bit protocols are around 34ms. However, an 80 bit protocol should // reduce this by the transmission of SYMBOL * 24 or 15,360us REG_WRITE(GPIO_OUT_W1TC_REG, pin); // Below are the original calculations for inter-frame silence. However, when actually inspecting this from - // the remote it appears to be closer to 27500us. The delayMicoseconds call is also cannot be called with + // the remote it appears to be closer to 27500us. The delayMicoseconds call cannot be called with // values larger than 16383. /* if(bitLength == 80) @@ -1696,9 +1702,10 @@ void Transceiver::sendFrame(byte *frame, uint8_t sync, uint8_t bitLength) { else delayMicroseconds(30415); */ - delayMicroseconds(13750); - if(bitLength != 80) // Part of the inter-frame silence is used to transport data. + if(bitLength != 80) { delayMicroseconds(13750); + delayMicroseconds(13750); + } } void RECEIVE_ATTR Transceiver::handleReceive() { @@ -1749,7 +1756,7 @@ void RECEIVE_ATTR Transceiver::handleReceive() { somfy_rx.cpt_bits = 0; somfy_rx.bit_length = 56; } - else if((somfy_rx.pulseCount > 10 && somfy_rx.cpt_synchro_hw == 0) || duration > 150000) { + else if((somfy_rx.pulseCount > 20 && somfy_rx.cpt_synchro_hw == 0) || duration > 250000) { somfy_rx.pulseCount = 0; } } diff --git a/SomfyController.ino.esp32.bin b/SomfyController.ino.esp32.bin index 60b8e74..a6775d0 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 fea26b8..b79868b 100644 Binary files a/SomfyController.littlefs.bin and b/SomfyController.littlefs.bin differ diff --git a/Utils.h b/Utils.h index ab7d715..3ac30b9 100644 --- a/Utils.h +++ b/Utils.h @@ -51,7 +51,13 @@ typedef struct rebootDelay_t { int rebootTime = 0; bool closed = false; }; - +static bool toBoolean(const char *str, bool def) { + if(!str) return def; + if(strlen(str) == 0) return def; + else if(str[0] == 't' || str[0] == 'T' || str[0] == '1') return true; + else if(str[0] == 'f' || str[0] == 'F' || str[0] == '0') return false; + return def; +} class Timestamp { char _timeBuffer[44]; diff --git a/Web.cpp b/Web.cpp index 7bcc714..59a7f96 100644 --- a/Web.cpp +++ b/Web.cpp @@ -170,7 +170,7 @@ void Web::begin() { shade->moveToTarget(target); else shade->sendCommand(command, repeat); - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -234,7 +234,7 @@ void Web::begin() { shade->moveToTiltTarget(target); else shade->sendTiltCommand(command); - DynamicJsonDocument sdoc(256); + DynamicJsonDocument sdoc(512); JsonObject sobj = sdoc.to(); shade->toJSON(sobj); serializeJson(sdoc, g_content); @@ -870,6 +870,52 @@ void Web::begin() { } } }); + server.on("/setPaired", []() { + webServer.sendCORSHeaders(); + uint8_t shadeId = 255; + bool paired = false; + if(server.hasArg("plain")) { + DynamicJsonDocument doc(512); + DeserializationError err = deserializeJson(doc, server.arg("plain")); + if(err) { + switch(err.code()) { + case DeserializationError::InvalidInput: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Invalid JSON payload\"}")); + break; + case DeserializationError::NoMemory: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Out of memory parsing JSON\"}")); + break; + default: + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"General JSON Deserialization failed\"}")); + break; + } + } + else { + JsonObject obj = doc.as(); + if (obj.containsKey("shadeId")) shadeId = obj["shadeId"]; + if(obj.containsKey("paired")) paired = obj["paired"]; + } + } + else if (server.hasArg("shadeId")) + shadeId = atoi(server.arg("shadeId").c_str()); + if(server.hasArg("paired")) + paired = toBoolean(server.arg("paired").c_str(), false); + SomfyShade* shade = nullptr; + if (shadeId != 255) shade = somfy.getShadeById(shadeId); + if (!shade) { + server.send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Shade not found to pair\"}")); + } + else { + shade->paired = paired; + shade->save(); + DynamicJsonDocument doc(512); + JsonObject obj = doc.to(); + shade->toJSON(obj); + serializeJson(doc, g_content); + server.send(200, _encoding_json, g_content); + } + }); + /* server.on("/pairShade", []() { webServer.sendCORSHeaders(); HTTPMethod method = server.method(); @@ -919,6 +965,7 @@ void Web::begin() { } } }); + */ server.on("/unpairShade", []() { webServer.sendCORSHeaders(); HTTPMethod method = server.method(); diff --git a/data/index.js b/data/index.js index da37f9d..44f5b07 100644 --- a/data/index.js +++ b/data/index.js @@ -1696,6 +1696,42 @@ class Somfy { } }); } + setPaired(shadeId, paired) { + let obj = { shadeId: shadeId, paired: paired || false }; + let div = document.getElementById('divPairing'); + let overlay = typeof div === 'undefined' ? undefined : waitMessage(div); + putJSON('/setPaired', obj, (err, shade) => { + if (overlay) overlay.remove(); + if (err) { + console.log(err); + errorMessage(div, err.message); + } + else if (div) { + console.log(shade); + document.getElementById('somfyMain').style.display = 'none'; + document.getElementById('somfyShade').style.display = ''; + document.getElementById('btnSaveShade').style.display = 'inline-block'; + document.getElementById('btnLinkRemote').style.display = ''; + document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress; + document.getElementsByName('shadeName')[0].value = shade.name; + document.getElementsByName('shadeUpTime')[0].value = shade.upTime; + document.getElementsByName('shadeDownTime')[0].value = shade.downTime; + let ico = document.getElementById('icoShade'); + ico.style.setProperty('--shade-position', `${shade.position}%`); + ico.setAttribute('data-shadeid', shade.shadeId); + if (shade.paired) { + document.getElementById('btnUnpairShade').style.display = 'inline-block'; + document.getElementById('btnPairShade').style.display = 'none'; + } + else { + document.getElementById('btnPairShade').style.display = 'inline-block'; + document.getElementById('btnUnpairShade').style.display = 'none'; + } + this.setLinkedRemotesList(shade); + div.remove(); + } + }); + } pairShade(shadeId) { let div = document.createElement('div'); let html = `
`; @@ -1706,10 +1742,12 @@ class Somfy { html += '
  • Press the prog button on the back of the remote until the shade jogs
  • '; html += '
  • After the shade jogs press the Prog button below
  • '; html += '
  • The shade should jog again indicating that the shade is paired
  • '; - html += '
  • You may now press the close button
  • ' + html += '
  • If the shade jogs, you can press the shade paired button.
  • ' + html += '
  • If the shade does not jog, press the prog button again.
  • ' html += '' html += `
    ` - html += `` + html += `` + html += `` html += `` html += `
    `; div.innerHTML = html; @@ -1726,24 +1764,27 @@ class Somfy { html += '
  • Press the prog button on the back of the remote until the shade jogs
  • '; html += '
  • After the shade jogs press the Prog button below
  • '; html += '
  • The shade should jog again indicating that the shade is unpaired
  • '; - html += '
  • You may now press the close button
  • ' + html += '
  • If the shade jogs, you can press the shade unpaired button.
  • ' + html += '
  • If the shade does not jog, press the prog button again until the shade jogs.
  • ' html += '' html += `
    ` - html += `` + html += `` + html += `` html += `` html += `
    `; div.innerHTML = html; document.getElementById('somfyShade').appendChild(div); return div; }; - sendCommand(shadeId, command) { + sendCommand(shadeId, command, repeat) { console.log(`Sending Shade command ${shadeId}-${command}`); - if (isNaN(parseInt(command, 10))) - putJSON('/shadeCommand', { shadeId: shadeId, command: command }, (err, shade) => { - }); - else - putJSON('/shadeCommand', { shadeId: shadeId, target: parseInt(command, 10) }, (err, shade) => { - }); + let obj = { shadeId: shadeId }; + if (isNaN(parseInt(command, 10))) obj.command = command; + else obj.target = parseInt(command, 10); + if (typeof repeat === 'number') obj.repeat = parseInt(repeat); + putJSON('/shadeCommand', obj, (err, shade) => { + + }); }; sendTiltCommand(shadeId, command) { console.log(`Sending Tilt command ${shadeId}-${command}`);