Fix web OTA upload and isolate OTA sessions across firmware and goTool.
Split ESP-NOW into core/master/slave modules, block non-OTA UART traffic during updates, and hold the host serial port exclusively so dashboard polling cannot interleave with firmware uploads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+77
-25
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -69,16 +68,45 @@ const (
|
||||
)
|
||||
|
||||
func runOTAUpload(m *managedSerial, firmware []byte, onProgress otaProgressFn) error {
|
||||
push := func(phase, msg string) {
|
||||
if onProgress == nil {
|
||||
return
|
||||
}
|
||||
onProgress(OTAProgress{
|
||||
Type: "ota_progress", Phase: phase, Step: otaStepMaster,
|
||||
Percent: 0, Message: msg, MasterMessage: msg,
|
||||
})
|
||||
}
|
||||
push("preparing", "UART wird vorbereitet…")
|
||||
|
||||
// Block until the UART is free, then hold m.mu for the entire upload so
|
||||
// dashboard/API polling cannot interleave on the serial port.
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
err := runOTAOnPortUnlocked(m, firmware, onProgress)
|
||||
if m.otaActive {
|
||||
m.mu.Unlock()
|
||||
return errOTAInProgress
|
||||
}
|
||||
m.otaActive = true
|
||||
if m.sp == nil {
|
||||
if err := m.openLocked(); err != nil {
|
||||
m.otaActive = false
|
||||
m.mu.Unlock()
|
||||
push("error", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
sp := m.sp
|
||||
|
||||
err := runOTAOnPortUnlocked(sp, firmware, onProgress)
|
||||
if err != nil {
|
||||
m.invalidateLocked(err)
|
||||
}
|
||||
m.otaActive = false
|
||||
m.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgressFn) error {
|
||||
func runOTAOnPortUnlocked(sp *serialPort, firmware []byte, onProgress otaProgressFn) error {
|
||||
if len(firmware) == 0 {
|
||||
return fmt.Errorf("empty firmware")
|
||||
}
|
||||
@@ -120,32 +148,31 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
||||
onProgress(p)
|
||||
}
|
||||
|
||||
if m.sp == nil {
|
||||
if err := m.openLocked(); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sp := m.sp
|
||||
if err := sp.port.SetReadTimeout(otaPrepareTimeout); err != nil {
|
||||
if err := sp.port.SetReadTimeout(readTimeout); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
defer sp.port.SetReadTimeout(readTimeout)
|
||||
|
||||
notify("preparing", otaStepMaster, 0, fmt.Sprintf("Master: OTA start (%d bytes)…", imageSize))
|
||||
|
||||
flushSerialInput(sp)
|
||||
|
||||
if err := writeUartMessage(sp, &pb.UartMessage{
|
||||
Type: pb.MessageType_OTA_START,
|
||||
Payload: &pb.UartMessage_OtaStart{
|
||||
OtaStart: &pb.OtaStartPayload{TotalSize: uint32(imageSize)},
|
||||
},
|
||||
}, false); err != nil {
|
||||
}); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sp.port.SetReadTimeout(otaPrepareTimeout); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
defer func() { _ = sp.port.SetReadTimeout(readTimeout) }()
|
||||
|
||||
ready, err := waitOtaStatus(sp, otaStReady, otaPrepareTimeout, func(msg string) {
|
||||
notify("preparing", otaStepMaster, 2, msg)
|
||||
})
|
||||
@@ -179,7 +206,7 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
||||
Payload: &pb.UartMessage_OtaPayload{
|
||||
OtaPayload: &pb.OtaPayload{Seq: seq, Data: chunk},
|
||||
},
|
||||
}, false); err != nil {
|
||||
}); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
@@ -219,7 +246,7 @@ func runOTAOnPortUnlocked(m *managedSerial, firmware []byte, onProgress otaProgr
|
||||
Payload: &pb.UartMessage_OtaEnd{
|
||||
OtaEnd: &pb.OtaEndPayload{},
|
||||
},
|
||||
}, false); err != nil {
|
||||
}); err != nil {
|
||||
notify("error", "", 0, err.Error())
|
||||
return err
|
||||
}
|
||||
@@ -333,7 +360,7 @@ func queryOtaSlaveProgressLocked(sp *serialPort, clientID uint32,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := writeUartMessage(sp, req, false); err != nil {
|
||||
if err := writeUartMessage(sp, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if queryTimeout <= 0 {
|
||||
@@ -487,14 +514,11 @@ func waitOtaComplete(sp *serialPort, timeout time.Duration,
|
||||
}
|
||||
}
|
||||
|
||||
func writeUartMessage(sp *serialPort, msg *pb.UartMessage, logFrame bool) error {
|
||||
func writeUartMessage(sp *serialPort, msg *pb.UartMessage) error {
|
||||
frame, err := encodeUartMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if logFrame {
|
||||
log.Printf("sending %s (%d frame bytes)", msg.Type, len(frame))
|
||||
}
|
||||
_, err = sp.port.Write(frame)
|
||||
return err
|
||||
}
|
||||
@@ -505,12 +529,24 @@ func waitOtaStatus(sp *serialPort, want uint32, timeout time.Duration, onPrepari
|
||||
if time.Now().After(deadline) {
|
||||
return nil, fmt.Errorf("timeout waiting for OTA status %d", want)
|
||||
}
|
||||
if err := sp.port.SetReadTimeout(time.Until(deadline)); err != nil {
|
||||
readWait := time.Until(deadline)
|
||||
if readWait > otaStatusPollTimeout {
|
||||
readWait = otaStatusPollTimeout
|
||||
}
|
||||
if err := sp.port.SetReadTimeout(readWait); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := readOtaStatus(sp)
|
||||
payload, err := uartframe.ReadFrame(sp.port, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
continue
|
||||
}
|
||||
msg, err := decodeUartPayload(payload)
|
||||
if err != nil || msg.GetType() != pb.MessageType_OTA_STATUS {
|
||||
continue
|
||||
}
|
||||
st := msg.GetOtaStatus()
|
||||
if st == nil {
|
||||
continue
|
||||
}
|
||||
switch st.GetStatus() {
|
||||
case want:
|
||||
@@ -553,6 +589,22 @@ func encodeUartMessage(msg *pb.UartMessage) ([]byte, error) {
|
||||
return uartframe.EncodeFrame(payload)
|
||||
}
|
||||
|
||||
// flushSerialInput drops stale RX bytes (not full frames — avoids ReadFrame blocking).
|
||||
func flushSerialInput(sp *serialPort) {
|
||||
if sp == nil {
|
||||
return
|
||||
}
|
||||
_ = sp.port.SetReadTimeout(10 * time.Millisecond)
|
||||
buf := make([]byte, 256)
|
||||
deadline := time.Now().Add(50 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
n, err := sp.port.Read(buf)
|
||||
if n == 0 || err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUartPayload(payload []byte) (*pb.UartMessage, error) {
|
||||
if len(payload) == 0 {
|
||||
return nil, fmt.Errorf("empty response")
|
||||
|
||||
Reference in New Issue
Block a user