Files
powerpods/goTool/cmd_ble_stream.go
T
skrueckenandCursor 92a13d7fd0 Add BLE host transport selectable via DIP8 alongside UART.
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>
2026-10-23 16:11:10 +02:00

129 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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])
}