Master can speak the same framed protobuf protocol over NimBLE or UART; goTool gains -ble, ble-rtt, and ble-stream timing checks for ESP-NOW coexistence. Co-authored-by: Cursor <cursoragent@cursor.com>
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"math"
|
||
"sort"
|
||
"time"
|
||
|
||
pb "powerpod/gotool/pb"
|
||
"google.golang.org/protobuf/proto"
|
||
)
|
||
|
||
func runBleStream(h hostConn, args []string) error {
|
||
fs := flag.NewFlagSet("ble-stream", flag.ExitOnError)
|
||
clientID := fs.Uint("client", 0, "slave client id (0 = all_clients)")
|
||
periodMs := fs.Uint("period", 16, "BLE push period_ms (8–16)")
|
||
count := fs.Int("count", 50, "number of BleStreamTick samples")
|
||
tolMs := fs.Float64("tol", 2.0, "tolerance ms around period for window_ok")
|
||
duration := fs.Float64("timeout", 15.0, "max seconds waiting for ticks")
|
||
if err := fs.Parse(args); err != nil {
|
||
return err
|
||
}
|
||
if *periodMs < 8 || *periodMs > 16 {
|
||
return fmt.Errorf("--period must be 8..16")
|
||
}
|
||
if *count < 2 {
|
||
return fmt.Errorf("--count must be >= 2")
|
||
}
|
||
|
||
sc, ok := h.(hostStreamConn)
|
||
if !ok {
|
||
return fmt.Errorf("ble-stream requires -ble (UART has no push ticks)")
|
||
}
|
||
|
||
req := &pb.AccelStreamRequest{
|
||
Write: true,
|
||
Enable: true,
|
||
PeriodMs: uint32(*periodMs),
|
||
}
|
||
if *clientID == 0 {
|
||
req.AllClients = true
|
||
} else {
|
||
req.ClientId = uint32(*clientID)
|
||
}
|
||
resp, err := hostAccelStream(h, req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if !resp.GetSuccess() {
|
||
return fmt.Errorf("accel stream enable failed")
|
||
}
|
||
fmt.Printf("accel stream on; period_ms=%d; collecting %d ticks…\n", *periodMs, *count)
|
||
|
||
type sample struct {
|
||
espUs uint64
|
||
pcT float64
|
||
}
|
||
samples := make([]sample, 0, *count)
|
||
deadline := time.Now().Add(time.Duration(*duration * float64(time.Second)))
|
||
|
||
for len(samples) < *count {
|
||
remain := time.Until(deadline)
|
||
if remain <= 0 {
|
||
break
|
||
}
|
||
payload, err := sc.NextStreamTick(remain)
|
||
if err != nil {
|
||
break
|
||
}
|
||
pcT := time.Now().UnixMicro()
|
||
var msg pb.UartMessage
|
||
if err := proto.Unmarshal(payload[1:], &msg); err != nil {
|
||
continue
|
||
}
|
||
tick := msg.GetBleStreamTick()
|
||
if tick == nil {
|
||
continue
|
||
}
|
||
samples = append(samples, sample{espUs: tick.GetEspTimestampUs(), pcT: float64(pcT)})
|
||
}
|
||
|
||
// Best-effort disable
|
||
off := &pb.AccelStreamRequest{Write: true, Enable: false, AllClients: req.AllClients, ClientId: req.ClientId}
|
||
_, _ = hostAccelStream(h, off)
|
||
|
||
if len(samples) < 2 {
|
||
return fmt.Errorf("need >=2 ticks, got %d (is stream running / DIP8=BLE?)", len(samples))
|
||
}
|
||
|
||
var dT, dR, extra []float64
|
||
esp0 := samples[0].espUs
|
||
pc0 := samples[0].pcT
|
||
for i := 1; i < len(samples); i++ {
|
||
dT = append(dT, float64(samples[i].espUs-samples[i-1].espUs)/1000.0)
|
||
dR = append(dR, (samples[i].pcT-samples[i-1].pcT)/1000.0)
|
||
relEsp := float64(samples[i].espUs-esp0) / 1000.0
|
||
relPc := (samples[i].pcT - pc0) / 1000.0
|
||
extra = append(extra, relPc-relEsp)
|
||
}
|
||
|
||
period := float64(*periodMs)
|
||
outCount := 0
|
||
for _, dt := range dT {
|
||
if math.Abs(dt-period) > *tolMs {
|
||
outCount++
|
||
}
|
||
}
|
||
windowOK := outCount*2 <= len(dT) // majority inside tolerance
|
||
|
||
fmt.Printf("received=%d/%d period_ms=%d tol=±%.1f ms\n", len(samples), *count, *periodMs, *tolMs)
|
||
printStat("dT ESP inter-depart", dT)
|
||
printStat("dR PC inter-arrive", dR)
|
||
printStat("extra vs first", extra)
|
||
fmt.Printf("outside_tol=%d/%d window_ok=%s\n", outCount, len(dT), map[bool]string{true: "yes", false: "no"}[windowOK])
|
||
return nil
|
||
}
|
||
|
||
func printStat(name string, v []float64) {
|
||
if len(v) == 0 {
|
||
fmt.Printf("%s: (none)\n", name)
|
||
return
|
||
}
|
||
s := append([]float64(nil), v...)
|
||
sort.Float64s(s)
|
||
fmt.Printf("%s: n=%d min=%.3f median=%.3f mean=%.3f max=%.3f ms\n",
|
||
name, len(s), s[0], percentile(s, 0.5), mean(s), s[len(s)-1])
|
||
}
|