Add led_ring, find_me, restart, and ota_progress steps plus uart_cmds scenario. Co-authored-by: Cursor <cursoragent@cursor.com>
525 lines
13 KiB
Go
525 lines
13 KiB
Go
package autotest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"powerpod/gotool/pb"
|
|
)
|
|
|
|
// MasterClient talks to the powerpod master over UART (implemented by main.serialPort).
|
|
type MasterClient interface {
|
|
GetVersion() (*pb.VersionResponse, error)
|
|
ListClients() ([]*pb.ClientInfo, error)
|
|
AccelDeadzone(req *pb.AccelDeadzoneRequest) (*pb.AccelDeadzoneResponse, error)
|
|
EspnowUnicastTest(clientID, seq uint32) (*pb.EspNowUnicastTestResponse, error)
|
|
LedRing(req *pb.LedRingProgressRequest) (*pb.LedRingProgressResponse, error)
|
|
FindMe(clientID uint32) (*pb.EspNowFindMeResponse, error)
|
|
Restart(clientID uint32) (*pb.RestartResponse, error)
|
|
OtaSlaveProgress(clientID uint32) (*pb.OtaSlaveProgressResponse, error)
|
|
}
|
|
|
|
type StepResult struct {
|
|
Index int
|
|
Name string
|
|
Command string
|
|
Pass bool
|
|
Detail string
|
|
}
|
|
|
|
type RunResult struct {
|
|
ConfigID string
|
|
ScenarioID string
|
|
Steps []StepResult
|
|
Passed int
|
|
Failed int
|
|
}
|
|
|
|
func Run(bench *Bench, sc *Scenario, client MasterClient) (*RunResult, error) {
|
|
if sc.ConfigID != bench.ID {
|
|
return nil, fmt.Errorf("scenario %q expects config %q, got %q", sc.ID, sc.ConfigID, bench.ID)
|
|
}
|
|
|
|
res := &RunResult{ConfigID: bench.ID, ScenarioID: sc.ID}
|
|
for i, step := range sc.Steps {
|
|
sr := StepResult{Index: i + 1, Name: step.Name, Command: step.Command}
|
|
if step.Name == "" {
|
|
sr.Name = fmt.Sprintf("step %d", i+1)
|
|
}
|
|
|
|
if step.DelayMS > 0 && step.Command == "" {
|
|
time.Sleep(time.Duration(step.DelayMS) * time.Millisecond)
|
|
sr.Pass = true
|
|
sr.Detail = fmt.Sprintf("delay %d ms", step.DelayMS)
|
|
res.Steps = append(res.Steps, sr)
|
|
res.Passed++
|
|
continue
|
|
}
|
|
|
|
if step.Command == "" {
|
|
sr.Pass = false
|
|
sr.Detail = "step needs command or delay_ms"
|
|
res.Steps = append(res.Steps, sr)
|
|
res.Failed++
|
|
continue
|
|
}
|
|
|
|
if step.DelayMS > 0 {
|
|
time.Sleep(time.Duration(step.DelayMS) * time.Millisecond)
|
|
}
|
|
|
|
err := runStep(bench, step, client)
|
|
if err != nil {
|
|
sr.Pass = false
|
|
sr.Detail = err.Error()
|
|
res.Failed++
|
|
} else {
|
|
sr.Pass = true
|
|
sr.Detail = "ok"
|
|
res.Passed++
|
|
}
|
|
res.Steps = append(res.Steps, sr)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func runStep(bench *Bench, step Step, client MasterClient) error {
|
|
cmd := strings.ToLower(strings.ReplaceAll(step.Command, "-", "_"))
|
|
switch cmd {
|
|
case "version":
|
|
return checkVersion(step, client)
|
|
case "clients", "client_info":
|
|
return checkClients(bench, step, client)
|
|
case "deadzone", "accel_deadzone":
|
|
return checkDeadzone(bench, step, client)
|
|
case "unicast_test", "unicast":
|
|
return checkUnicastTest(bench, step, client)
|
|
case "led_ring", "ledring":
|
|
return checkLedRing(step, client)
|
|
case "find_me", "findme":
|
|
return checkFindMe(bench, step, client)
|
|
case "restart":
|
|
return checkRestartCmd(bench, step, client)
|
|
case "ota_progress", "ota_slave_progress":
|
|
return checkOtaProgress(step, client)
|
|
case "reset", "reboot":
|
|
return checkReset(bench, step)
|
|
default:
|
|
return fmt.Errorf("unknown command %q", step.Command)
|
|
}
|
|
}
|
|
|
|
type resetInput struct {
|
|
Target string `json:"target"`
|
|
Slave string `json:"slave"`
|
|
All bool `json:"all"`
|
|
WaitMS int `json:"wait_ms"`
|
|
}
|
|
|
|
func checkReset(bench *Bench, step Step) error {
|
|
var in resetInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
|
|
if in.All {
|
|
return bench.ResetAll(in.WaitMS)
|
|
}
|
|
|
|
target := in.Target
|
|
if in.Slave != "" {
|
|
target = in.Slave
|
|
}
|
|
if target == "" {
|
|
target = "master"
|
|
}
|
|
return bench.ResetTargets([]string{target}, in.WaitMS)
|
|
}
|
|
|
|
func checkVersion(step Step, client MasterClient) error {
|
|
ver, err := client.GetVersion()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e := step.Expect
|
|
if e.Version != nil && ver.GetVersion() != *e.Version {
|
|
return fmt.Errorf("version=%d want %d", ver.GetVersion(), *e.Version)
|
|
}
|
|
if e.VersionMin != nil && ver.GetVersion() < *e.VersionMin {
|
|
return fmt.Errorf("version=%d want >= %d", ver.GetVersion(), *e.VersionMin)
|
|
}
|
|
if e.GitHash != "" && ver.GetGitHash() != e.GitHash {
|
|
return fmt.Errorf("git_hash=%q want %q", ver.GetGitHash(), e.GitHash)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checkClients(bench *Bench, step Step, client MasterClient) error {
|
|
clients, err := client.ListClients()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e := step.Expect
|
|
n := len(clients)
|
|
|
|
if e.ClientCount != nil && n != *e.ClientCount {
|
|
return fmt.Errorf("client_count=%d want %d", n, *e.ClientCount)
|
|
}
|
|
if e.MinClients != nil && n < *e.MinClients {
|
|
return fmt.Errorf("client_count=%d want >= %d", n, *e.MinClients)
|
|
}
|
|
if e.MaxClients != nil && n > *e.MaxClients {
|
|
return fmt.Errorf("client_count=%d want <= %d", n, *e.MaxClients)
|
|
}
|
|
|
|
slaveNames := e.Slaves
|
|
if e.Slave != "" {
|
|
slaveNames = append(slaveNames, e.Slave)
|
|
}
|
|
for _, name := range slaveNames {
|
|
want, err := bench.Slave(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var found *pb.ClientInfo
|
|
for _, c := range clients {
|
|
if MACEqual(MACFromProto(c.GetMac()), want.MAC) {
|
|
found = c
|
|
break
|
|
}
|
|
}
|
|
if found == nil {
|
|
return fmt.Errorf("slave %q mac %s not in client list", name, want.MAC)
|
|
}
|
|
if uint32(*want.ClientID) != found.GetId() {
|
|
return fmt.Errorf("slave %q id=%d want client_id=%d", name, found.GetId(), *want.ClientID)
|
|
}
|
|
if e.Available != nil && found.GetAvailable() != *e.Available {
|
|
return fmt.Errorf("slave %q available=%v want %v", name, found.GetAvailable(), *e.Available)
|
|
}
|
|
if e.MAC != "" && !MACEqual(MACFromProto(found.GetMac()), e.MAC) {
|
|
return fmt.Errorf("slave %q mac=%s want %s", name, MACFromProto(found.GetMac()), e.MAC)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type deadzoneInput struct {
|
|
Write bool `json:"write"`
|
|
Value uint `json:"value"`
|
|
Deadzone uint `json:"deadzone"`
|
|
ClientID *uint `json:"client_id"`
|
|
Client uint `json:"client"`
|
|
Slave string `json:"slave"`
|
|
AllClients bool `json:"all_clients"`
|
|
}
|
|
|
|
func checkDeadzone(bench *Bench, step Step, client MasterClient) error {
|
|
var in deadzoneInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
dz := in.Value
|
|
if dz == 0 {
|
|
dz = in.Deadzone
|
|
}
|
|
|
|
var clientID uint32
|
|
switch {
|
|
case in.AllClients:
|
|
// client_id 0 with all_clients set
|
|
case in.Slave != "":
|
|
id, err := bench.ResolveClientID(in.Slave)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
clientID = id
|
|
case in.ClientID != nil:
|
|
clientID = uint32(*in.ClientID)
|
|
default:
|
|
clientID = uint32(in.Client)
|
|
}
|
|
|
|
req := &pb.AccelDeadzoneRequest{
|
|
Write: in.Write,
|
|
Deadzone: uint32(dz),
|
|
ClientId: clientID,
|
|
AllClients: in.AllClients,
|
|
}
|
|
resp, err := client.AccelDeadzone(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Deadzone != nil && resp.GetDeadzone() != *e.Deadzone {
|
|
return fmt.Errorf("deadzone=%d want %d", resp.GetDeadzone(), *e.Deadzone)
|
|
}
|
|
if e.Success != nil && resp.GetSuccess() != *e.Success {
|
|
return fmt.Errorf("success=%v want %v", resp.GetSuccess(), *e.Success)
|
|
}
|
|
if e.SlavesUpdated != nil && resp.GetSlavesUpdated() != *e.SlavesUpdated {
|
|
return fmt.Errorf("slaves_updated=%d want %d", resp.GetSlavesUpdated(), *e.SlavesUpdated)
|
|
}
|
|
if e.SlavesUpdatedMin != nil && resp.GetSlavesUpdated() < *e.SlavesUpdatedMin {
|
|
return fmt.Errorf("slaves_updated=%d want >= %d", resp.GetSlavesUpdated(), *e.SlavesUpdatedMin)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type unicastInput struct {
|
|
Seq uint `json:"seq"`
|
|
ClientID *uint `json:"client_id"`
|
|
Client uint `json:"client"`
|
|
Slave string `json:"slave"`
|
|
}
|
|
|
|
func checkUnicastTest(bench *Bench, step Step, client MasterClient) error {
|
|
var in unicastInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
if in.Seq == 0 {
|
|
in.Seq = 1
|
|
}
|
|
|
|
var clientID uint32
|
|
switch {
|
|
case in.Slave != "":
|
|
id, err := bench.ResolveClientID(in.Slave)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
clientID = id
|
|
case in.ClientID != nil:
|
|
clientID = uint32(*in.ClientID)
|
|
default:
|
|
clientID = uint32(in.Client)
|
|
}
|
|
if clientID == 0 {
|
|
return fmt.Errorf("unicast_test: client_id or slave required")
|
|
}
|
|
|
|
resp, err := client.EspnowUnicastTest(clientID, uint32(in.Seq))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Success != nil && resp.GetSuccess() != *e.Success {
|
|
return fmt.Errorf("success=%v want %v", resp.GetSuccess(), *e.Success)
|
|
}
|
|
if e.Seq != nil && resp.GetSeq() != *e.Seq {
|
|
return fmt.Errorf("seq=%d want %d", resp.GetSeq(), *e.Seq)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ledRingInput struct {
|
|
Mode string `json:"mode"`
|
|
Progress uint `json:"progress"`
|
|
Digit uint `json:"digit"`
|
|
R uint `json:"r"`
|
|
G uint `json:"g"`
|
|
B uint `json:"b"`
|
|
Intensity uint `json:"intensity"`
|
|
BlinkMs uint `json:"blink_ms"`
|
|
BlinkCount uint `json:"blink_count"`
|
|
}
|
|
|
|
func ledRingModeValue(mode string) (uint32, error) {
|
|
switch strings.ToLower(strings.ReplaceAll(mode, "-", "_")) {
|
|
case "clear", "":
|
|
return 0, nil
|
|
case "progress":
|
|
return 1, nil
|
|
case "digit":
|
|
return 2, nil
|
|
case "blink":
|
|
return 3, nil
|
|
case "find_me", "findme":
|
|
return 4, nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown led_ring mode %q", mode)
|
|
}
|
|
}
|
|
|
|
func checkLedRing(step Step, client MasterClient) error {
|
|
var in ledRingInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
mode, err := ledRingModeValue(in.Mode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if in.Mode == "" && step.Expect.Mode != nil {
|
|
mode = *step.Expect.Mode
|
|
}
|
|
|
|
req := &pb.LedRingProgressRequest{
|
|
Mode: mode,
|
|
Progress: uint32(in.Progress),
|
|
Digit: uint32(in.Digit),
|
|
R: uint32(in.R),
|
|
G: uint32(in.G),
|
|
B: uint32(in.B),
|
|
Intensity: uint32(in.Intensity),
|
|
BlinkMs: uint32(in.BlinkMs),
|
|
BlinkCount: uint32(in.BlinkCount),
|
|
}
|
|
if mode == 1 && req.GetG() == 0 && req.GetR() == 0 && req.GetB() == 0 {
|
|
req.G = 255
|
|
}
|
|
|
|
resp, err := client.LedRing(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Success != nil && resp.GetSuccess() != *e.Success {
|
|
return fmt.Errorf("success=%v want %v", resp.GetSuccess(), *e.Success)
|
|
}
|
|
if e.Mode != nil && resp.GetMode() != *e.Mode {
|
|
return fmt.Errorf("mode=%d want %d", resp.GetMode(), *e.Mode)
|
|
}
|
|
if e.Progress != nil && resp.GetProgress() != *e.Progress {
|
|
return fmt.Errorf("progress=%d want %d", resp.GetProgress(), *e.Progress)
|
|
}
|
|
if e.Digit != nil && resp.GetDigit() != *e.Digit {
|
|
return fmt.Errorf("digit=%d want %d", resp.GetDigit(), *e.Digit)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type findMeInput struct {
|
|
ClientID *uint `json:"client_id"`
|
|
Client uint `json:"client"`
|
|
Slave string `json:"slave"`
|
|
}
|
|
|
|
func checkFindMe(bench *Bench, step Step, client MasterClient) error {
|
|
var in findMeInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
|
|
clientID, err := resolveClientID(bench, in.Slave, in.ClientID, in.Client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := client.FindMe(clientID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Success != nil && resp.GetSuccess() != *e.Success {
|
|
return fmt.Errorf("success=%v want %v", resp.GetSuccess(), *e.Success)
|
|
}
|
|
if e.Slave != "" || in.Slave != "" {
|
|
wantSlave := e.Slave
|
|
if wantSlave == "" {
|
|
wantSlave = in.Slave
|
|
}
|
|
wantID, err := bench.ResolveClientID(wantSlave)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.GetClientId() != wantID {
|
|
return fmt.Errorf("client_id=%d want %d", resp.GetClientId(), wantID)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type restartCmdInput struct {
|
|
ClientID *uint `json:"client_id"`
|
|
Client uint `json:"client"`
|
|
Slave string `json:"slave"`
|
|
}
|
|
|
|
func checkRestartCmd(bench *Bench, step Step, client MasterClient) error {
|
|
var in restartCmdInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
|
|
clientID, err := resolveClientID(bench, in.Slave, in.ClientID, in.Client)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp, err := client.Restart(clientID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Success != nil && resp.GetSuccess() != *e.Success {
|
|
return fmt.Errorf("success=%v want %v", resp.GetSuccess(), *e.Success)
|
|
}
|
|
if resp.GetClientId() != clientID {
|
|
return fmt.Errorf("client_id=%d want %d", resp.GetClientId(), clientID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type otaProgressInput struct {
|
|
ClientID *uint `json:"client_id"`
|
|
Client uint `json:"client"`
|
|
Slave string `json:"slave"`
|
|
}
|
|
|
|
func checkOtaProgress(step Step, client MasterClient) error {
|
|
var in otaProgressInput
|
|
if len(step.Input) > 0 {
|
|
if err := json.Unmarshal(step.Input, &in); err != nil {
|
|
return fmt.Errorf("input: %w", err)
|
|
}
|
|
}
|
|
|
|
clientID := uint32(in.Client)
|
|
if in.ClientID != nil {
|
|
clientID = uint32(*in.ClientID)
|
|
}
|
|
|
|
resp, err := client.OtaSlaveProgress(clientID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
e := step.Expect
|
|
if e.Active != nil && resp.GetActive() != *e.Active {
|
|
return fmt.Errorf("active=%v want %v", resp.GetActive(), *e.Active)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveClientID(bench *Bench, slave string, clientID *uint, client uint) (uint32, error) {
|
|
switch {
|
|
case slave != "":
|
|
return bench.ResolveClientID(slave)
|
|
case clientID != nil:
|
|
return uint32(*clientID), nil
|
|
default:
|
|
return uint32(client), nil
|
|
}
|
|
}
|