Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
.DS_Store
.vscode/
.idea/
/cmd/benchpod-cli/benchpod-cli
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,17 @@ one that is already done:
2. **Network**: a pod with no address gets one. Choose Ethernet (setup waits up to
two minutes for its DHCP lease) or Wi-Fi (SSID and password, like `set-wifi`).
3. **Board I/O voltage**: 1.8 V or 3.3 V, the DUT's logic level (like `la voltage`).
One already set is kept unless `--la-voltage` is given.
One already set is kept unless `--la-voltage` is given. The pod forgets it on a
restart, so when this machine is signed in and the pod is registered to that
account, setup also saves it (`la_mv`) to the pod's wiring profile on
embeddedci.com, keeping every other field. The server applies it on every connect
and the web app's setup checklist ticks "I/O voltage". A voltage already on the pod
but missing from the profile is saved too. If the save fails, setup warns and goes on.
4. **embeddedci.com**: an unregistered pod is registered to your account, signing in
first (`login`) when there is no session. A pod registered to your account is
named; one registered to another account is explained (sign in with the address
your activation email was sent to, `benchpod login --force`) and left alone.
first (`login`) when there is no session, and its new wiring profile gets the I/O
voltage. A pod registered to your account is named; one registered to another
account is explained (sign in with the address your activation email was sent to,
`benchpod login --force`) and left alone.
5. **Save the connection**: the pod's address becomes the default (like `discover --save`).

It ends with the next steps: SWD wiring (SWCLK on LA11, SWDIO on LA12, GND), a
Expand All @@ -510,7 +516,7 @@ $ benchpod setup --la-voltage 3.3V
Done: the pod is on the network at 192.168.1.221:8080.

3/5 Board I/O voltage
Set to 3.3 V.
Set to 3.3 V on the pod and saved to its wiring profile on embeddedci.com.

4/5 embeddedci.com
Done: registered to your account (you@example.com) as benchpod-baea06.
Expand Down Expand Up @@ -652,6 +658,13 @@ LA capture, UART, SWD flash, pull-ups and I2C-sensor emulation until it is set;
1.8 V needs a v3 pod, and the pod refuses a change while an LA pin is in use. Works
over the network and over USB. No flags.

The pod forgets the voltage on a restart. When this machine is signed in
(`benchpod login`) and the pod is registered to that account, setting it over the
network or an `embeddedci:` connection also saves it to the pod's wiring profile on
embeddedci.com (only `la_mv` changes), which the server applies on every connect.
A failed save is a warning; the pod setting still stands. Over USB it is set on the
pod only.

### `benchpod la pullup PIN STATE`

Switch an LA pin's pull-up: `PIN` is 1-8 (or `la1`), `STATE` is `on` or `off`. No flags.
Expand Down
Binary file removed cmd/benchpod-cli/benchpod-cli
Binary file not shown.
69 changes: 68 additions & 1 deletion cmd/benchpod-cli/lavoltage.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/embeddedci-com/benchpod-cli/internal/serialconsole"
"github.com/embeddedci-com/benchpod-cli/internal/serverapi"
"github.com/embeddedci-com/benchpod-cli/internal/tcpclient"
"github.com/spf13/cobra"
)
Expand All @@ -32,7 +33,12 @@ func newLAVoltageCmd(g *globalFlags) *cobra.Command {
"I2C-sensor emulation until it is set. 1.8V needs a v3 pod, and the pod refuses a\n" +
"change while any LA pin is in use.\n\n" +
"VOLTAGE is 1.8V or 3.3V (also 1800mV, 3.3, or 3300). Omit it to show the\n" +
"current setting. Works over the network and over USB (--connection usb).",
"current setting. Works over the network and over USB (--connection usb).\n\n" +
"The pod forgets the voltage on a restart. When this machine is signed in\n" +
"(`benchpod login`) and the pod is registered to that account, setting it over\n" +
"the network or embeddedci.com also saves it to the pod's wiring profile on\n" +
"embeddedci.com, which the server applies on every connect. Over USB it is set\n" +
"on the pod only.",
Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
mv := 0
Expand Down Expand Up @@ -91,9 +97,70 @@ func runLAVoltage(g *globalFlags, mv int, out, warn io.Writer) error {
if rep.ReadbackMV != 0 && rep.ReadbackMV != rep.MV {
fmt.Fprintf(warn, "Warning: the bank's power mux reports %s, not %s\n", formatMV(rep.ReadbackMV), formatMV(rep.MV))
}
if mv != 0 {
if id := accountDeviceForConnection(g); id != "" {
changed, err := saveLaMVToProfile(id, rep.MV)
switch {
case err != nil:
fmt.Fprintf(warn, "Warning: could not save it to the pod's wiring profile on embeddedci.com: %v\n", err)
case changed:
fmt.Fprintln(out, "Saved to the pod's wiring profile on embeddedci.com (applied on every connect).")
}
}
}
return nil
}

// accountDeviceForConnection returns the cloud device id of the pod behind g's network or
// embeddedci.com connection when this machine is signed in and the pod is registered to that
// account, so its wiring profile can be written; "" otherwise (also over USB, and whenever it
// cannot tell). It never prompts or fails. A variable so tests can replace it.
var accountDeviceForConnection = func(g *globalFlags) string {
if who, _ := currentSession("", ""); who == "" {
return ""
}
spec, err := g.resolveConnection()
if err != nil || spec.IsSerial() {
return ""
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
raw, err := (&tcpclient.Client{Addr: spec.Addr}).Command(ctx, map[string]any{"cmd": "cloud_status"})
if err != nil {
return ""
}
var c cloudState
if json.Unmarshal(raw, &c) != nil || !c.Configured {
return ""
}
id := strings.TrimSpace(c.DeviceID)
if id == "" {
return ""
}
if _, ok := accountDevices()[id]; !ok {
return ""
}
return id
}

// saveLaMVToProfile stores mv as la_mv in the wiring profile of the pod with this cloud device id
// on embeddedci.com (read-modify-write: every other field is kept). changed is false when the
// profile already had it. A variable so tests can replace it.
var saveLaMVToProfile = func(deviceID string, mv int) (changed bool, err error) {
tokenPath, err := resolveTokenPath("")
if err != nil {
return false, err
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
api := serverapi.New(defaultServerURL)
tokens, err := ensureTokens(ctx, api, tokenPath)
if err != nil {
return false, fmt.Errorf("sign in: %w", err)
}
return api.SetDeviceWiringLaMV(ctx, tokens.AccessToken, deviceID, mv)
}

// laVoltageDo shows (mv 0) or sets the LA voltage over the effective connection, network or
// USB, and returns a label for the pod plus its reply. Shared by la voltage, status and setup.
func laVoltageDo(g *globalFlags, mv int) (string, laVoltageReply, error) {
Expand Down
50 changes: 50 additions & 0 deletions cmd/benchpod-cli/lavoltage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"time"
Expand All @@ -11,7 +12,55 @@ import (
"github.com/embeddedci-com/benchpod-cli/internal/serialconsole"
)

// stubLAVoltageProfile replaces the embeddedci.com side of `la voltage` for one test: id is the
// device the connection resolves to on the signed-in account ("" = not on it), and the returned
// slice records each save.
func stubLAVoltageProfile(t *testing.T, id string, changed bool, err error) *[]int {
t.Helper()
savedAcct, savedSave := accountDeviceForConnection, saveLaMVToProfile
t.Cleanup(func() { accountDeviceForConnection, saveLaMVToProfile = savedAcct, savedSave })
var saves []int
accountDeviceForConnection = func(*globalFlags) string { return id }
saveLaMVToProfile = func(dev string, mv int) (bool, error) {
if dev != id {
t.Errorf("saved to %q, want %q", dev, id)
}
saves = append(saves, mv)
return changed, err
}
return &saves
}

func TestLAVoltageSavesToTheProfile(t *testing.T) {
addr, _ := fakeLANPod(t, `{"status":"ok","data":{"mv":3300,"st":1,"readback_mv":3300}}`)
saves := stubLAVoltageProfile(t, "dev-1", true, nil)
var out, warn bytes.Buffer
if err := runLAVoltage(&globalFlags{connection: addr}, 3300, &out, &warn); err != nil {
t.Fatal(err)
}
if len(*saves) != 1 || (*saves)[0] != 3300 || !strings.Contains(out.String(), "Saved to the pod's wiring profile on embeddedci.com") {
t.Fatalf("saves %v out %q", *saves, out.String())
}
// A failed save warns; the command still succeeds.
addr, _ = fakeLANPod(t, `{"status":"ok","data":{"mv":3300,"st":1,"readback_mv":3300}}`)
stubLAVoltageProfile(t, "dev-1", false, errors.New("403 forbidden"))
out.Reset()
if err := runLAVoltage(&globalFlags{connection: addr}, 3300, &out, &warn); err != nil {
t.Fatal(err)
}
if !strings.Contains(warn.String(), "could not save it to the pod's wiring profile on embeddedci.com: 403 forbidden") {
t.Fatalf("warn %q", warn.String())
}
// Showing the voltage never writes the profile.
addr, _ = fakeLANPod(t, `{"status":"ok","data":{"mv":3300,"st":1,"readback_mv":3300}}`)
saves = stubLAVoltageProfile(t, "dev-1", true, nil)
if err := runLAVoltage(&globalFlags{connection: addr}, 0, &bytes.Buffer{}, &bytes.Buffer{}); err != nil || len(*saves) != 0 {
t.Fatalf("err %v saves %v", err, *saves)
}
}

func TestLAVoltageLAN(t *testing.T) {
stubLAVoltageProfile(t, "", false, nil)
addr, got := fakeLANPod(t, `{"status":"ok","data":{"mv":3300,"st":1,"readback_mv":3300}}`)
g := &globalFlags{connection: addr}
var out, warn bytes.Buffer
Expand Down Expand Up @@ -83,6 +132,7 @@ func (f *fakeLAVoltageConsole) LAVoltage(_ context.Context, mv int) (int, error)
func (f *fakeLAVoltageConsole) Close() error { return nil }

func TestLAVoltageUSB(t *testing.T) {
stubLAVoltageProfile(t, "", false, nil)
fc := &fakeLAVoltageConsole{}
saved := openLAVoltageConsole
t.Cleanup(func() { openLAVoltageConsole = saved })
Expand Down
Loading
Loading