package main import ( "flag" "fmt" "log" "os" ) const defaultBaud = 921600 func usage() { fmt.Fprintf(os.Stderr, "usage: gotool [-port /dev/ttyUSB0] \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") fmt.Fprintf(os.Stderr, " clients registered ESP-NOW slaves on the master\n") fmt.Fprintf(os.Stderr, " deadzone get/set accelerometer deadzone (LSB)\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, " unicast-test send ESP-NOW unicast test to one slave\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, " 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") fmt.Fprintf(os.Stderr, " restart reboot master or slave (ESP-NOW)\n\n") flag.PrintDefaults() } func main() { portName := flag.String("port", "", "serial port (e.g. /dev/ttyUSB0)") baud := flag.Int("baud", defaultBaud, "UART baud rate") flag.Parse() if flag.NArg() < 1 { usage() os.Exit(2) } cmd := flag.Arg(0) var runErr error switch cmd { case "test", "autotest": runErr = runTest(*portName, *baud, flag.Args()[1:]) case "serve", "web", "dashboard": 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-notify", "tap_notify", "cache-status", "cache_status", "unicast-test", "unicast_test", "led-ring", "led_ring", "find-me", "find_me", "restart", "ota", "ota-progress", "ota_progress": if *portName == "" { fmt.Fprintf(os.Stderr, "command %q requires -port\n\n", cmd) usage() os.Exit(2) } sp, err := openSerial(*portName, *baud) if err != nil { log.Fatalf("open serial: %v", err) } registerShutdown(func() { _ = sp.Close() }) enableShutdownOnInterrupt() defer sp.Close() switch cmd { case "version": runErr = runVersion(sp) case "clients", "client-info": runErr = runClients(sp) case "deadzone", "accel-deadzone": runErr = runDeadzone(sp, flag.Args()[1:]) case "tap-notify", "tap_notify": runErr = runTapNotify(sp, flag.Args()[1:]) case "cache-status", "cache_status": runErr = runCacheStatus(sp) case "unicast-test", "unicast_test": runErr = runUnicastTest(sp, flag.Args()[1:]) case "led-ring", "led_ring": runErr = runLedRing(sp, flag.Args()[1:]) case "find-me", "find_me": runErr = runFindMe(sp, flag.Args()[1:]) case "restart": runErr = runRestart(sp, flag.Args()[1:]) case "ota": runErr = runOTA(sp, flag.Args()[1:]) case "ota-progress", "ota_progress": runErr = runOtaProgress(sp, flag.Args()[1:]) } default: fmt.Fprintf(os.Stderr, "unknown command %q\n\n", cmd) usage() os.Exit(2) } if runErr != nil { log.Fatal(runErr) } }