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>
This commit is contained in:
+178
-19
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultBaud = 921600
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: gotool [-port /dev/ttyUSB0] <command>\n")
|
||||
fmt.Fprintf(os.Stderr, "usage: gotool [-port /dev/ttyUSB0 | -ble] <command>\n")
|
||||
fmt.Fprintf(os.Stderr, " DIP8 on master: 0=UART (-port), 1=BLE (-ble); reboot after DIP change\n")
|
||||
fmt.Fprintf(os.Stderr, " test uses uart.master from bench config when -port is omitted\n\n")
|
||||
fmt.Fprintf(os.Stderr, "commands:\n")
|
||||
fmt.Fprintf(os.Stderr, " version firmware version and git hash\n")
|
||||
@@ -18,12 +20,15 @@ func usage() {
|
||||
fmt.Fprintf(os.Stderr, " deadzone get/set accelerometer deadzone (LSB)\n")
|
||||
fmt.Fprintf(os.Stderr, " tap-config get/set BMA456 tap detection parameters\n")
|
||||
fmt.Fprintf(os.Stderr, " tap-notify get/set which tap kinds notify via ESP-NOW\n")
|
||||
fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one UART round-trip)\n")
|
||||
fmt.Fprintf(os.Stderr, " cache-status subscribed accel + tap cache (one round-trip)\n")
|
||||
fmt.Fprintf(os.Stderr, " accel-stream enable/disable ESP-NOW accel stream (+ BLE period)\n")
|
||||
fmt.Fprintf(os.Stderr, " unicast-test send ESP-NOW unicast test to one slave\n")
|
||||
fmt.Fprintf(os.Stderr, " echo-ping ESP-NOW timestamp echo round-trip to one slave\n")
|
||||
fmt.Fprintf(os.Stderr, " ble-rtt Host↔Master RTT via VERSION (N samples)\n")
|
||||
fmt.Fprintf(os.Stderr, " ble-stream enable stream + measure BleStreamTick window\n")
|
||||
fmt.Fprintf(os.Stderr, " test run automated scenario (see testdata/)\n")
|
||||
fmt.Fprintf(os.Stderr, " serve web dashboard (Bootstrap + WebSocket)\n")
|
||||
fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions)\n")
|
||||
fmt.Fprintf(os.Stderr, " serve web dashboard (UART only)\n")
|
||||
fmt.Fprintf(os.Stderr, " ota UART OTA upload (A/B partitions; UART host only)\n")
|
||||
fmt.Fprintf(os.Stderr, " ota-progress query per-slave ESP-NOW OTA progress on master\n")
|
||||
fmt.Fprintf(os.Stderr, " led-ring set LED ring progress bar (0–100%%, rgb, intensity)\n")
|
||||
fmt.Fprintf(os.Stderr, " find-me blink LED ring red/green/blue (3× each, full brightness)\n")
|
||||
@@ -33,8 +38,11 @@ func usage() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0)")
|
||||
portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0); XOR with -ble")
|
||||
baud := flag.Int("baud", defaultBaud, "UART baud rate")
|
||||
useBLE := flag.Bool("ble", false, "use BLE host transport (DIP8=1); XOR with -port")
|
||||
bleName := flag.String("ble-name", bleDefaultName, "BLE advertised name")
|
||||
bleSettle := flag.Float64("ble-settle", 1.0, "seconds to settle after BLE connect")
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
@@ -47,55 +55,133 @@ func main() {
|
||||
var runErr error
|
||||
switch cmd {
|
||||
case "test", "autotest":
|
||||
if *useBLE {
|
||||
runErr = fmt.Errorf("autotest requires -port (UART)")
|
||||
break
|
||||
}
|
||||
runErr = runTest(*portName, *baud, flag.Args()[1:])
|
||||
case "serve", "web", "dashboard":
|
||||
if *useBLE {
|
||||
fmt.Fprintf(os.Stderr, "serve requires -port (UART only in this release)\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
if *portName == "" {
|
||||
fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd)
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
runErr = runServe(*portName, *baud, flag.Args()[1:])
|
||||
case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-config", "tap_config", "tap-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
|
||||
if *portName == "" {
|
||||
fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd)
|
||||
case "version", "clients", "client-info", "deadzone", "accel-deadzone", "tap-config", "tap_config", "tap-notify", "tap_notify", "cache-status", "cache_status", "accel-stream", "accel_stream", "unicast-test", "unicast_test", "echo-ping", "echo_ping", "ble-rtt", "ble_rtt", "ble-stream", "ble_stream", "led-ring", "led_ring", "find-me", "find_me", "restart", "log-level", "log_level", "ota", "ota-progress", "ota_progress":
|
||||
if *useBLE && *portName != "" {
|
||||
fmt.Fprintf(os.Stderr, "use either -port or -ble, not both\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
if !*useBLE && *portName == "" {
|
||||
fmt.Fprintf(os.Stderr, "command %q requires -port or -ble\n\n", cmd)
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
sp, err := openSerial(*portName, *baud)
|
||||
if err != nil {
|
||||
log.Fatalf("open serial: %v", err)
|
||||
if cmd == "ota" || cmd == "ota-progress" || cmd == "ota_progress" {
|
||||
if *useBLE {
|
||||
fmt.Fprintf(os.Stderr, "OTA requires UART host (DIP8=0, -port)\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
registerShutdown(func() { _ = sp.Close() })
|
||||
|
||||
var h hostConn
|
||||
if *useBLE {
|
||||
bl, err := openBLE(*bleName, time.Duration(*bleSettle*float64(time.Second)))
|
||||
if err != nil {
|
||||
log.Fatalf("open BLE: %v", err)
|
||||
}
|
||||
h = bl
|
||||
} else {
|
||||
sp, err := openSerial(*portName, *baud)
|
||||
if err != nil {
|
||||
log.Fatalf("open serial: %v", err)
|
||||
}
|
||||
h = sp
|
||||
}
|
||||
registerShutdown(func() { _ = h.Close() })
|
||||
enableShutdownOnInterrupt()
|
||||
defer sp.Close()
|
||||
defer h.Close()
|
||||
|
||||
switch cmd {
|
||||
case "version":
|
||||
runErr = runVersion(sp)
|
||||
runErr = runVersionHost(h)
|
||||
case "clients", "client-info":
|
||||
runErr = runClients(sp)
|
||||
runErr = runClientsHost(h)
|
||||
case "cache-status", "cache_status":
|
||||
runErr = runCacheStatusHost(h)
|
||||
case "accel-stream", "accel_stream":
|
||||
runErr = runAccelStreamCmd(h, flag.Args()[1:])
|
||||
case "echo-ping", "echo_ping":
|
||||
runErr = runEchoPingHost(h, flag.Args()[1:])
|
||||
case "ble-rtt", "ble_rtt":
|
||||
runErr = runBleRtt(h, flag.Args()[1:])
|
||||
case "ble-stream", "ble_stream":
|
||||
runErr = runBleStream(h, flag.Args()[1:])
|
||||
case "deadzone", "accel-deadzone":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runDeadzone(sp, flag.Args()[1:])
|
||||
case "tap-config", "tap_config":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runTapConfig(sp, flag.Args()[1:])
|
||||
case "tap-notify", "tap_notify":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runTapNotify(sp, flag.Args()[1:])
|
||||
case "cache-status", "cache_status":
|
||||
runErr = runCacheStatus(sp)
|
||||
case "unicast-test", "unicast_test":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runUnicastTest(sp, flag.Args()[1:])
|
||||
case "echo-ping", "echo_ping":
|
||||
runErr = runEchoPing(sp, flag.Args()[1:])
|
||||
case "led-ring", "led_ring":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runLedRing(sp, flag.Args()[1:])
|
||||
case "find-me", "find_me":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runFindMe(sp, flag.Args()[1:])
|
||||
case "restart":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runRestart(sp, flag.Args()[1:])
|
||||
case "log-level", "log_level":
|
||||
sp, ok := h.(*serialPort)
|
||||
if !ok {
|
||||
runErr = fmt.Errorf("%s over BLE not wired yet; use -port", cmd)
|
||||
break
|
||||
}
|
||||
runErr = runLogLevel(sp, flag.Args()[1:])
|
||||
case "ota":
|
||||
sp := h.(*serialPort)
|
||||
runErr = runOTA(sp, flag.Args()[1:])
|
||||
case "ota-progress", "ota_progress":
|
||||
sp := h.(*serialPort)
|
||||
runErr = runOtaProgress(sp, flag.Args()[1:])
|
||||
}
|
||||
default:
|
||||
@@ -108,3 +194,76 @@ func main() {
|
||||
log.Fatal(runErr)
|
||||
}
|
||||
}
|
||||
|
||||
func runVersionHost(h hostConn) error {
|
||||
ver, err := hostGetVersion(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("version: %d\n", ver.GetVersion())
|
||||
fmt.Printf("git_hash: %s\n", ver.GetGitHash())
|
||||
return nil
|
||||
}
|
||||
|
||||
func runClientsHost(h hostConn) error {
|
||||
clients, err := hostListClients(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(clients) == 0 {
|
||||
fmt.Println("no clients registered")
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("clients (%d):\n", len(clients))
|
||||
for i, c := range clients {
|
||||
fmt.Printf(" [%d] id=%d ver=%d available=%v used=%v\n",
|
||||
i, c.GetId(), c.GetVersion(), c.GetAvailable(), c.GetUsed())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCacheStatusHost(h hostConn) error {
|
||||
r, err := hostReadCacheStatus(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clients := r.GetClients()
|
||||
if len(clients) == 0 {
|
||||
fmt.Println("(no slaves with accel stream or tap notify enabled)")
|
||||
return nil
|
||||
}
|
||||
for _, c := range clients {
|
||||
id := c.GetClientId()
|
||||
if a := c.GetAccel(); a != nil {
|
||||
if !a.GetValid() {
|
||||
fmt.Printf("client %d accel: no sample yet\n", id)
|
||||
} else {
|
||||
fmt.Printf("client %d accel: x=%d y=%d z=%d (age %d ms)\n",
|
||||
id, a.GetX(), a.GetY(), a.GetZ(), a.GetAgeMs())
|
||||
}
|
||||
}
|
||||
if t := c.GetTap(); t != nil {
|
||||
fmt.Printf("client %d tap: %s (age %d ms)\n",
|
||||
id, tapKindLabel(t.GetKind()), t.GetAgeMs())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runEchoPingHost(h hostConn, args []string) error {
|
||||
fs := flag.NewFlagSet("echo-ping", flag.ExitOnError)
|
||||
clientID := fs.Uint("client", 0, "slave client id from `clients`")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *clientID == 0 {
|
||||
return fmt.Errorf("client id required (see `gotool clients`)")
|
||||
}
|
||||
r, err := hostEchoPing(h, uint32(*clientID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("echo ping: success=%v client_id=%d rtt_ms=%.3f (host↔master↔slave) esp_rtt_us=%d (ESP-NOW only)\n",
|
||||
r.Success, r.ClientID, r.RttMs, r.EspRttUs)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user