diff --git a/src/Somfy.cpp b/src/Somfy.cpp index 9a18c25..e6bde09 100644 --- a/src/Somfy.cpp +++ b/src/Somfy.cpp @@ -4553,6 +4553,10 @@ int currRSSI = -100; float markFreq = 433.0f; int markRSSI = -100; uint32_t lastScan = 0; +static uint32_t scanStart = 0; +// A scan nobody ended (closed browser tab) stops on its own after this long so +// the radio doesn't stay unable to transmit. +#define SCAN_MAX_MS 300000 void Transceiver::beginFrequencyScan() { // The scan retunes the radio so nothing may transmit until it ends. This // also aborts a transmission in flight so the lock below is never held long. @@ -4561,6 +4565,7 @@ void Transceiver::beginFrequencyScan() { if(this->config.enabled) { this->disableReceive(); rxmode = 3; + scanStart = millis(); pinMode(this->config.RXPin, INPUT); interruptPin = digitalPinToInterrupt(this->config.RXPin); ELECHOUSE_cc1101.setRxBW(this->config.rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. @@ -4608,9 +4613,10 @@ void Transceiver::endFrequencyScan() { if(interruptPin > 0) detachInterrupt(interruptPin); interruptPin = 0; this->config.apply(); - this->resumeTx(TX_SUSPEND_SCAN); this->emitFrequencyScan(); } + // Even if something else already ended the scan, nothing may keep TX suspended. + this->resumeTx(TX_SUSPEND_SCAN); } void Transceiver::emitFrequencyScan(uint8_t num) { JsonSockEvent *json = sockEmit.beginEmit("frequencyScan"); @@ -4770,7 +4776,8 @@ bool Transceiver::save() { } bool Transceiver::end() { // Nothing may transmit (and re-enable receive) after this. It is not - // resumed since the only caller is OTA which always reboots. + // resumed since the only caller is OTA, which reboots when the upload + // completes, fails or is abandoned. this->suspendTx(TX_SUSPEND_OTA); RadioLock lock(portMAX_DELAY, true); this->disableReceive(); @@ -5011,7 +5018,13 @@ void transceiver_config_t::apply() { // be reconfigured. Queued frames are already encoded and go out with the // new settings. RadioLock lock(portMAX_DELAY, true); + // Reconfiguring the radio ends a frequency scan so TX may resume. + const bool wasScanning = rxmode == 3; somfy.transceiver.disableReceive(); + if(wasScanning) { + somfy.transceiver.resumeTx(TX_SUSPEND_SCAN); + somfy.transceiver.emitFrequencyScan(); + } bit_length = this->type; if(this->enabled) { bool radioInit = true; @@ -5136,6 +5149,10 @@ void Transceiver::loop() { noiseCount = 0; } } + if(rxmode == 3 && millis() - scanStart > SCAN_MAX_MS) { + ESP_LOGW(TAG, "Frequency scan timed out"); + this->endFrequencyScan(); + } if(rxmode == 3) { if(this->receive(&rx)) this->processFrequencyScan(true); diff --git a/src/Web.cpp b/src/Web.cpp index c780449..6211d5e 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -64,10 +64,24 @@ static bool queueUnpairShade(SomfyShade *shade) { shade->paired = false; return true; } +// The radio and MQTT are shut down once an upload starts and only a reboot +// brings them back. The completion handler schedules it but never runs if the +// client goes away mid-upload, so make sure the device reboots then too. +static void onUploadDisconnect() { + if(webServer.uploadSuccess || rebootDelay.reboot) return; + ESP_LOGW(TAG, "Upload abandoned, rebooting"); + if(Update.isRunning()) Update.abort(); + rebootDelay.reboot = true; + rebootDelay.rebootTime = millis() + 500; +} // Sent when a command can't be handed to the main loop. static void sendQueueFull(AsyncWebServerRequest *request) { request->send(503, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Command queue full\"}")); } +// Sent when the radio can't take a frame (suspended or queue full). +static void sendRadioUnavailable(AsyncWebServerRequest *request) { + request->send(503, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"Radio TX unavailable\"}")); +} AsyncWebServer asyncServer(80); AsyncWebServer asyncApiServer(8081); @@ -2136,6 +2150,7 @@ void Web::begin() { else { somfy.transceiver.end(); mqtt.end(); + request->onDisconnect(onUploadDisconnect); } } if(len > 0) { @@ -2201,6 +2216,7 @@ void Web::begin() { else { somfy.transceiver.end(); mqtt.end(); + request->onDisconnect(onUploadDisconnect); } } if(len > 0) { @@ -2331,8 +2347,10 @@ void Web::begin() { frame.cmd = translateSomfyCommand(scmd.c_str()); } if(frame.remoteAddress > 0 && frame.rollingCode > 0) { - somfy.sendFrame(frame, repeats); - request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); + if(somfy.sendFrame(frame, repeats)) + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); + else + sendRadioUnavailable(request); } else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}")); @@ -2348,8 +2366,10 @@ void Web::begin() { if(asyncHasParam(request, "repeats")) repeats = atoi(asyncParam(request, "repeats").c_str()); } if(frame.remoteAddress > 0 && frame.rollingCode > 0) { - somfy.sendFrame(frame, repeats); - request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); + if(somfy.sendFrame(frame, repeats)) + request->send(200, _encoding_json, F("{\"status\":\"SUCCESS\",\"desc\":\"Command Sent\"}")); + else + sendRadioUnavailable(request); } else request->send(500, _encoding_json, F("{\"status\":\"ERROR\",\"desc\":\"No address or rolling code provided\"}"));