Add goTool clients subcommand for CLIENT_INFO query.

Refactor into version/clients subcommands with shared serial framing
to list registered slaves from the master over UART.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-18 22:26:42 +02:00
co-authored by Cursor
parent 92e146e2ed
commit 16bfbd1091
5 changed files with 188 additions and 65 deletions
+26 -63
View File
@@ -5,86 +5,49 @@ import (
"fmt"
"log"
"os"
"time"
"go.bug.st/serial"
"google.golang.org/protobuf/proto"
"powerpod/gotool/pb"
uartframe "powerpod/gotool/uart"
)
const (
defaultBaud = 921600
versionCmdID = byte(pb.MessageType_VERSION)
readTimeout = 3 * time.Second
)
const defaultBaud = 921600
func usage() {
fmt.Fprintf(os.Stderr, "usage: gotool -port /dev/ttyUSB0 <command>\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\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 *portName == "" {
fmt.Fprintln(os.Stderr, "usage: gotool -port /dev/ttyUSB0")
flag.PrintDefaults()
if *portName == "" || flag.NArg() < 1 {
usage()
os.Exit(2)
}
mode := &serial.Mode{
BaudRate: *baud,
DataBits: 8,
Parity: serial.NoParity,
StopBits: serial.OneStopBit,
}
cmd := flag.Arg(0)
port, err := serial.Open(*portName, mode)
sp, err := openSerial(*portName, *baud)
if err != nil {
log.Fatalf("open serial: %v", err)
}
defer port.Close()
defer sp.Close()
if err := port.SetReadTimeout(readTimeout); err != nil {
log.Fatalf("set read timeout: %v", err)
var runErr error
switch cmd {
case "version":
runErr = runVersion(sp)
case "clients", "client-info":
runErr = runClients(sp)
default:
fmt.Fprintf(os.Stderr, "unknown command %q\n\n", cmd)
usage()
os.Exit(2)
}
frame, err := uartframe.EncodeFrame([]byte{versionCmdID})
if err != nil {
log.Fatalf("encode frame: %v", err)
if runErr != nil {
log.Fatal(runErr)
}
log.Printf("sending VERSION command (%d bytes): % x", len(frame), frame)
if _, err := port.Write(frame); err != nil {
log.Fatalf("write: %v", err)
}
payload, err := uartframe.ReadFrame(port, nil)
if err != nil {
log.Fatalf("read response: %v", err)
}
log.Printf("response payload (%d bytes): % x", len(payload), payload)
if len(payload) == 0 {
log.Fatal("empty response payload")
}
if payload[0] != versionCmdID {
log.Fatalf("unexpected command id 0x%02x (want 0x%02x)", payload[0], versionCmdID)
}
var msg pb.UartMessage
if err := proto.Unmarshal(payload[1:], &msg); err != nil {
log.Fatalf("decode protobuf: %v", err)
}
if msg.GetType() != pb.MessageType_VERSION {
log.Fatalf("unexpected message type %v", msg.GetType())
}
ver := msg.GetVersionResponse()
if ver == nil {
log.Fatal("response missing version_response")
}
fmt.Printf("version: %d\n", ver.GetVersion())
fmt.Printf("git_hash: %s\n", ver.GetGitHash())
}