Enable host OTA over BLE and harden ESP-NOW slave distribution.
Allow the same staged firmware upload path on BLE as UART, fix distribute stack/event-group crashes, and recover sticky slave OTA sessions instead of failing with err=4. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+79
-4
@@ -26,6 +26,7 @@ type bleLink struct {
|
||||
respCh chan []byte
|
||||
streamCh chan []byte
|
||||
quiet bool
|
||||
attMTU int // negotiated ATT MTU (0 if unknown)
|
||||
}
|
||||
|
||||
func openBLE(name string, settle time.Duration) (*bleLink, error) {
|
||||
@@ -125,19 +126,23 @@ func connectBLEDevice(addr bluetooth.Address, settle time.Duration) (*bleLink, e
|
||||
}
|
||||
char := chars[0]
|
||||
|
||||
attMTU := 0
|
||||
if mtu, err := char.GetMTU(); err == nil {
|
||||
log.Printf("BLE ATT MTU=%d", mtu)
|
||||
if mtu < 203 {
|
||||
log.Printf("warning: MTU %d < 203 — large frames/stream ticks may fail", mtu)
|
||||
attMTU = int(mtu)
|
||||
log.Printf("BLE ATT MTU=%d", attMTU)
|
||||
if attMTU < otaMinATTMTU {
|
||||
log.Printf("warning: MTU %d < %d — OTA (200 B chunks) and large stream ticks may fail",
|
||||
attMTU, otaMinATTMTU)
|
||||
}
|
||||
}
|
||||
|
||||
link := &bleLink{
|
||||
device: device,
|
||||
device: device,
|
||||
char: char,
|
||||
parser: uartframe.NewParser(),
|
||||
respCh: make(chan []byte, 8),
|
||||
streamCh: make(chan []byte, 64),
|
||||
attMTU: attMTU,
|
||||
}
|
||||
|
||||
if err := char.EnableNotifications(func(buf []byte) {
|
||||
@@ -277,3 +282,73 @@ func (b *bleLink) NextStreamTick(timeout time.Duration) ([]byte, error) {
|
||||
return nil, fmt.Errorf("BLE stream tick timeout")
|
||||
}
|
||||
}
|
||||
|
||||
// ensureOTAMTU rejects OTA when the negotiated ATT MTU cannot carry 200 B chunks.
|
||||
func (b *bleLink) ensureOTAMTU() error {
|
||||
mtu := b.attMTU
|
||||
if mtu <= 0 {
|
||||
if v, err := b.char.GetMTU(); err == nil {
|
||||
mtu = int(v)
|
||||
b.attMTU = mtu
|
||||
}
|
||||
}
|
||||
if mtu < otaMinATTMTU {
|
||||
return fmt.Errorf("BLE ATT MTU %d < %d required for OTA (negotiate larger MTU)", mtu, otaMinATTMTU)
|
||||
}
|
||||
maxWrite := mtu - 3
|
||||
if maxWrite < 216 {
|
||||
return fmt.Errorf("BLE max write %d < 216 needed for 200 B OTA frames", maxWrite)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bleLink) writeMsg(msg *pb.UartMessage) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
frame, err := encodeUartMessage(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b.attMTU > 0 && len(frame) > b.attMTU-3 {
|
||||
return fmt.Errorf("OTA frame %d exceeds BLE max write %d (MTU=%d)",
|
||||
len(frame), b.attMTU-3, b.attMTU)
|
||||
}
|
||||
if _, err := b.char.WriteWithoutResponse(frame); err != nil {
|
||||
return fmt.Errorf("BLE write: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bleLink) readFrame(timeout time.Duration) ([]byte, error) {
|
||||
if timeout <= 0 {
|
||||
timeout = readTimeout
|
||||
}
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case p := <-b.respCh:
|
||||
return p, nil
|
||||
case <-timer.C:
|
||||
return nil, fmt.Errorf("BLE read timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bleLink) flushInput() {
|
||||
for {
|
||||
select {
|
||||
case <-b.respCh:
|
||||
default:
|
||||
goto streams
|
||||
}
|
||||
}
|
||||
streams:
|
||||
for {
|
||||
select {
|
||||
case <-b.streamCh:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bleLink) chunkPace() time.Duration { return otaHostChunkPaceBLE }
|
||||
|
||||
Reference in New Issue
Block a user