diff --git a/goTool/ota_upload.go b/goTool/ota_upload.go index b1f3ba1..a130990 100644 --- a/goTool/ota_upload.go +++ b/goTool/ota_upload.go @@ -20,6 +20,9 @@ const ( otaDistQueryInterval = 500 * time.Millisecond otaDistQueryTimeout = 2 * time.Second otaDistEmitMinInterval = 150 * time.Millisecond + // Pace host chunks so the master UART RX ring is not overrun (~20 frames/block). + otaHostChunkPace = 3 * time.Millisecond + otaBlockMaxRetries = 3 ) const ( @@ -189,52 +192,83 @@ func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgres var seq uint32 for offset := 0; offset < imageSize; { - bytesInBlock := 0 - for bytesInBlock < otaFlashBlockSize && offset < imageSize { - n := otaHostChunkSize - room := otaFlashBlockSize - bytesInBlock - if n > room { - n = room - } - if offset+n > imageSize { - n = imageSize - offset - } - chunk := firmware[offset : offset+n] + blockStart := offset + blockStartSeq := seq - if err := writeUartMessage(sp, &pb.UartMessage{ - Type: pb.MessageType_OTA_PAYLOAD, - Payload: &pb.UartMessage_OtaPayload{ - OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk}, - }, - }); err != nil { - notify("error", "", 0, err.Error()) - return err - } - seq++ - offset += n - bytesInBlock += n + sendBlock := func() (fullBlock bool, err error) { + bytesInBlock := 0 + for bytesInBlock < otaFlashBlockSize && offset < imageSize { + n := otaHostChunkSize + room := otaFlashBlockSize - bytesInBlock + if n > room { + n = room + } + if offset+n > imageSize { + n = imageSize - offset + } + chunk := firmware[offset : offset+n] - pct := offset * 100 / imageSize - if pct > 99 { - pct = 99 + if err := writeUartMessage(sp, &pb.UartMessage{ + Type: pb.MessageType_OTA_PAYLOAD, + Payload: &pb.UartMessage_OtaPayload{ + OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk}, + }, + }); err != nil { + return false, err + } + time.Sleep(otaHostChunkPace) + seq++ + offset += n + bytesInBlock += n + + pct := offset * 100 / imageSize + if pct > 99 { + pct = 99 + } + notify("uploading", otaStepMaster, pct, + fmt.Sprintf("Master: %d / %d bytes", offset, imageSize)) } - notify("uploading", otaStepMaster, pct, fmt.Sprintf("Master: %d / %d bytes", offset, imageSize)) + return bytesInBlock == otaFlashBlockSize, nil } - if bytesInBlock == otaFlashBlockSize { - st, err := waitOtaStatus(sp, otaStBlockAck, otaDefaultTimeout, nil) - if err != nil { - notify("error", "", 0, err.Error()) - return err - } - pct := offset * 100 / imageSize - if pct > 99 { - pct = 99 - } - notify("uploading", otaStepMaster, pct, - fmt.Sprintf("Master: Block geschrieben (%d bytes)", st.GetBytesWritten()), - OTAProgress{Bytes: st.GetBytesWritten()}) + fullBlock, err := sendBlock() + if err != nil { + notify("error", "", 0, err.Error()) + return err } + + if !fullBlock { + continue + } + + var st *pb.OtaStatusPayload + var ackErr error + for attempt := 0; attempt < otaBlockMaxRetries; attempt++ { + if attempt > 0 { + offset = blockStart + seq = blockStartSeq + if _, err := sendBlock(); err != nil { + notify("error", "", 0, err.Error()) + return err + } + } + st, ackErr = waitOtaStatus(sp, otaStBlockAck, otaDefaultTimeout, nil) + if ackErr == nil { + break + } + } + if ackErr != nil { + notify("error", "", 0, ackErr.Error()) + return ackErr + } + + pct := offset * 100 / imageSize + if pct > 99 { + pct = 99 + } + notify("uploading", otaStepMaster, pct, + fmt.Sprintf("Master: Block geschrieben (%d bytes)", st.GetBytesWritten()), + OTAProgress{Bytes: st.GetBytesWritten()}) } masterPct = 100 diff --git a/main/cmd/cmd_ota.c b/main/cmd/cmd_ota.c index eae4a3d..5146081 100644 --- a/main/cmd/cmd_ota.c +++ b/main/cmd/cmd_ota.c @@ -175,8 +175,18 @@ static void handle_ota_payload(const uint8_t *data, size_t len) { return; } - ota_feed_result_t r = - ota_uart_feed(req_ptr->data.bytes, req_ptr->data.size); + ota_feed_result_t r = ota_uart_feed_chunk(req_ptr->seq, req_ptr->data.bytes, + req_ptr->data.size); + if (r == OTA_FEED_SEQ_GAP) { + send_ota_failed(16); + return; + } + if (r == OTA_FEED_SEQ_DUP) { + if (ota_uart_block_ready_for_reack()) { + send_ota_status(OTA_UART_ST_BLOCK_ACK, 0); + } + return; + } if (r == OTA_FEED_ERROR) { send_ota_failed( 13); return; @@ -189,15 +199,6 @@ static void handle_ota_payload(const uint8_t *data, size_t len) { led_ring_show_ota_progress(done, total, OTA_LED_UART_R, OTA_LED_UART_G, OTA_LED_UART_B); send_ota_status(OTA_UART_ST_BLOCK_ACK, 0); - return; - } - - if (r == OTA_FEED_OK) { - uint32_t total = ota_uart_total_size(); - if (total > 0) { - led_ring_show_ota_progress(ota_uart_bytes_received(), total, - OTA_LED_UART_R, OTA_LED_UART_G, OTA_LED_UART_B); - } } } diff --git a/main/esp_now_core.h b/main/esp_now_core.h index f459ecc..f5a00c6 100644 --- a/main/esp_now_core.h +++ b/main/esp_now_core.h @@ -23,8 +23,14 @@ esp_err_t esp_now_core_ensure_broadcast_peer(void); esp_err_t esp_now_core_send(const uint8_t *dest_mac, const alox_EspNowMessage *msg); +/** Like send but does not wait for esp_now send-done (lower latency). */ +esp_err_t esp_now_core_send_fast(const uint8_t *dest_mac, + const alox_EspNowMessage *msg); esp_err_t esp_now_core_send_wait(const uint8_t *dest_mac, const alox_EspNowMessage *msg); +/** OTA payloads: wait for send-done with extra NO_MEM backoff (queue drain). */ +esp_err_t esp_now_core_send_reliable(const uint8_t *dest_mac, + const alox_EspNowMessage *msg); esp_err_t esp_now_core_init_radio(uint8_t channel); void esp_now_core_init_send_done(void);