simon 0299ba44fd Add bench UART ports and cold-start reset to goTool autotest.
Bench configs define command and console serial paths; scenarios can
reset nodes via esptool before tests. Smoke resets all nodes then waits
for ESP-NOW join.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 23:44:58 +02:00

153 lines
3.9 KiB
Go
Raw Permalink 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 autotest
import (
"encoding/json"
"fmt"
"os"
"strings"
)
// UARTConfig holds serial paths for gotool commands and per-node reset (USB console).
type UARTConfig struct {
// Baud for uart.master (default 921600).
Baud uint `json:"baud,omitempty"`
// Master command UART (external adapter on GPIO2/3), e.g. /dev/ttyUSB0.
Master string `json:"master"`
// Master USB console / JTAG serial for esptool reset, e.g. /dev/ttyACM0.
MasterConsole string `json:"master_console,omitempty"`
}
// Config describes the bench: ESP-NOW network, master MAC, and known slaves.
type Config struct {
ID string `json:"id"`
Description string `json:"description,omitempty"`
Network uint `json:"network"`
MasterMAC string `json:"master_mac"`
UART UARTConfig `json:"uart"`
Slaves []SlaveNode `json:"slaves"`
}
type SlaveNode struct {
ID string `json:"id"`
MAC string `json:"mac"`
ClientID *uint `json:"client_id,omitempty"`
// USB console port for esptool reset (optional), e.g. /dev/ttyACM1.
Console string `json:"console,omitempty"`
}
type Bench struct {
Config
slaveByID map[string]*SlaveNode
}
func LoadConfig(path string) (*Bench, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
return NewBench(cfg)
}
func NewBench(cfg Config) (*Bench, error) {
cfg.MasterMAC = normalizeMAC(cfg.MasterMAC)
if cfg.ID == "" {
return nil, fmt.Errorf("config: id is required")
}
if cfg.MasterMAC == "" {
return nil, fmt.Errorf("config %q: master_mac is required", cfg.ID)
}
if cfg.Network < 1 || cfg.Network > 8 {
return nil, fmt.Errorf("config %q: network must be 18 (DIP / IO expander)", cfg.ID)
}
if cfg.UART.Master == "" {
return nil, fmt.Errorf("config %q: uart.master is required (gotool command port)", cfg.ID)
}
if cfg.UART.Baud == 0 {
cfg.UART.Baud = 921600
}
b := &Bench{Config: cfg, slaveByID: make(map[string]*SlaveNode)}
for i := range cfg.Slaves {
s := &cfg.Slaves[i]
if s.ID == "" {
return nil, fmt.Errorf("config %q: slave missing id", cfg.ID)
}
if _, dup := b.slaveByID[s.ID]; dup {
return nil, fmt.Errorf("config %q: duplicate slave id %q", cfg.ID, s.ID)
}
mac, err := parseMAC(s.MAC)
if err != nil {
return nil, fmt.Errorf("config %q slave %q: %w", cfg.ID, s.ID, err)
}
s.MAC = mac
if s.ClientID == nil {
parts := strings.Split(mac, ":")
var last byte
fmt.Sscanf(parts[5], "%02x", &last)
id := uint(last)
s.ClientID = &id
}
b.slaveByID[s.ID] = s
}
return b, nil
}
func (b *Bench) Slave(id string) (*SlaveNode, error) {
s, ok := b.slaveByID[id]
if !ok {
return nil, fmt.Errorf("unknown slave %q (config %q)", id, b.ID)
}
return s, nil
}
func (b *Bench) ResolveClientID(slaveID string) (uint32, error) {
if slaveID == "" || slaveID == "master" || slaveID == "local" {
return 0, nil
}
s, err := b.Slave(slaveID)
if err != nil {
return 0, err
}
return uint32(*s.ClientID), nil
}
func normalizeMAC(s string) string {
s = strings.TrimSpace(strings.ToLower(s))
s = strings.ReplaceAll(s, "-", ":")
return s
}
func parseMAC(s string) (string, error) {
s = normalizeMAC(s)
parts := strings.Split(s, ":")
if len(parts) != 6 {
return "", fmt.Errorf("invalid mac %q", s)
}
out := make([]byte, 6)
for i, p := range parts {
var b byte
if _, err := fmt.Sscanf(p, "%02x", &b); err != nil {
return "", fmt.Errorf("invalid mac byte %q", p)
}
out[i] = b
}
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
out[0], out[1], out[2], out[3], out[4], out[5]), nil
}
func MACEqual(a, b string) bool {
return normalizeMAC(a) == normalizeMAC(b)
}
func MACFromProto(mac []byte) string {
if len(mac) != 6 {
return ""
}
return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
}