Compare commits
38 Commits
dhcp-wip
...
e2a5d97dae
| Author | SHA1 | Date | |
|---|---|---|---|
| e2a5d97dae | |||
| 94895fd2fe | |||
| f2d98ef4f1 | |||
| 8edf8c2d4f | |||
| c961499239 | |||
| 0d41f63533 | |||
| 394628b8da | |||
| bee0fa3aef | |||
| ff9f9a5c1f | |||
| af308b5aac | |||
| 712110aace | |||
| 0c11cbb1d1 | |||
| 3d20bf4c33 | |||
| f96ed20aa0 | |||
| e301c672a9 | |||
| 7034391d4d | |||
| 9989d8c66a | |||
| 3d749add7d | |||
| f161dda60a | |||
| b0294fada3 | |||
| 31b2c16b07 | |||
| d215ddc6f2 | |||
| ffcbaf0665 | |||
| a7381ca435 | |||
| a9193d51e4 | |||
| 46db2fd966 | |||
| 642e2ff318 | |||
| 3c320cf466 | |||
| 843a286631 | |||
| e60479bad8 | |||
| b8c0e6be66 | |||
| f837937cb7 | |||
| 49bbe1b29c | |||
| 00b960d81d | |||
| b197f0bfa7 | |||
| 00ab432a72 | |||
| 1fa1b2076c | |||
| ee8563ab69 |
@@ -4,8 +4,10 @@ description: Build firmware, load it onto the Pico, and reboot. Use when the use
|
|||||||
user-invocable: true
|
user-invocable: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Run `go run ./cmd/load/` from the project root. This builds both firmware targets, loads picomap onto the first device and picomap_test onto the second, and reboots both.
|
Run `go run ./cmd/picomap/ load` from the project root. This builds both firmware targets, loads picomap onto the first device and picomap_test onto the second, and reboots both.
|
||||||
|
|
||||||
After loading, run `go run ./cmd/info/` to verify both devices are responding.
|
To load a single target: `go run ./cmd/picomap/ load picomap` or `go run ./cmd/picomap/ load picomap_test`.
|
||||||
|
|
||||||
After modifying the load command itself (cmd/load/, lib/wire/, lib/picoserial/), run it twice: once to load the firmware, once to verify the load process still works end-to-end.
|
After loading, run `go run ./cmd/picomap/ info` to verify devices are responding.
|
||||||
|
|
||||||
|
After modifying the CLI itself (cmd/picomap/, lib/), run load twice: once to load the firmware, once to verify the load process still works end-to-end.
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/theater/picomap/lib/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type deviceResult struct {
|
|
||||||
dev string
|
|
||||||
info *client.ResponseInfo
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := run(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() error {
|
|
||||||
devs, err := client.ListSerial()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(devs) == 0 {
|
|
||||||
return fmt.Errorf("no devices found")
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make([]deviceResult, len(devs))
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i, dev := range devs {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
results[i].dev = dev
|
|
||||||
c, err := client.NewSerial(dev, 2*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
results[i].err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info, err := c.Info()
|
|
||||||
c.Close()
|
|
||||||
results[i].info = info
|
|
||||||
results[i].err = err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
for _, r := range results {
|
|
||||||
fmt.Printf("Device: %s\n", r.dev)
|
|
||||||
if r.err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, " error: %v\n", r.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf(" Board ID: %02X%02X%02X%02X%02X%02X%02X%02X\n",
|
|
||||||
r.info.BoardID[0], r.info.BoardID[1], r.info.BoardID[2], r.info.BoardID[3],
|
|
||||||
r.info.BoardID[4], r.info.BoardID[5], r.info.BoardID[6], r.info.BoardID[7])
|
|
||||||
fmt.Printf(" MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
|
|
||||||
r.info.MAC[0], r.info.MAC[1], r.info.MAC[2],
|
|
||||||
r.info.MAC[3], r.info.MAC[4], r.info.MAC[5])
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/theater/picomap/lib/client"
|
|
||||||
"github.com/theater/picomap/lib/picotool"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
buildDir := filepath.Join(wd, "firmware", "build")
|
|
||||||
|
|
||||||
if err := run(buildDir); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func build(buildDir string) error {
|
|
||||||
fmt.Println("Configuring...")
|
|
||||||
cmake := exec.Command("cmake", "-S", filepath.Join(filepath.Dir(buildDir)), "-B", buildDir)
|
|
||||||
cmake.Stdout = os.Stdout
|
|
||||||
cmake.Stderr = os.Stderr
|
|
||||||
if err := cmake.Run(); err != nil {
|
|
||||||
return fmt.Errorf("cmake failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Building...")
|
|
||||||
cmd := exec.Command("make", "-C", buildDir)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("build failed: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func boardSerial(id [8]byte) string {
|
|
||||||
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X",
|
|
||||||
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7])
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(buildDir string) error {
|
|
||||||
if err := build(buildDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
devs, err := client.ListSerial()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(devs) < 2 {
|
|
||||||
return fmt.Errorf("expected 2 devices, found %d", len(devs))
|
|
||||||
}
|
|
||||||
|
|
||||||
serials := make([]string, 2)
|
|
||||||
errs := make([]error, 2)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := range 2 {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
c, err := client.NewSerial(devs[i], 2*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
errs[i] = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info, err := c.Info()
|
|
||||||
c.Close()
|
|
||||||
if err != nil {
|
|
||||||
errs[i] = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serials[i] = boardSerial(info.BoardID)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
for i, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("info %s: %w", devs[i], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Sending PICOBOOT requests...")
|
|
||||||
for i := range 2 {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
c, err := client.NewSerial(devs[i], 2*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
errs[i] = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = c.PICOBOOT()
|
|
||||||
c.Close()
|
|
||||||
if err != nil {
|
|
||||||
errs[i] = fmt.Errorf("PICOBOOT %s: %w", devs[i], err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
for _, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
|
|
||||||
uf2s := []string{
|
|
||||||
filepath.Join(buildDir, "picomap.uf2"),
|
|
||||||
filepath.Join(buildDir, "picomap_test.uf2"),
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Loading firmware...")
|
|
||||||
for i := range 2 {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
errs[i] = picotool.Load(uf2s[i], serials[i])
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
for i, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("load %s: %w", serials[i], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Rebooting...")
|
|
||||||
for i := range 2 {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
errs[i] = picotool.Reboot(serials[i])
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
for i, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reboot %s: %w", serials[i], err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Done.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,392 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/theater/picomap/lib/client"
|
||||||
|
"github.com/theater/picomap/lib/picotool"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
slog.Error("usage: picomap <info|load|test> [args...]")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cmd := os.Args[1]
|
||||||
|
args := os.Args[2:]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch cmd {
|
||||||
|
case "info":
|
||||||
|
err = cmdInfo(args)
|
||||||
|
case "load":
|
||||||
|
err = cmdLoad(args)
|
||||||
|
case "log":
|
||||||
|
err = cmdLog(args)
|
||||||
|
case "test":
|
||||||
|
err = cmdTest(args)
|
||||||
|
default:
|
||||||
|
slog.Error("usage: picomap <info|load|log|test> [args...]")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("fatal", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type deviceResult struct {
|
||||||
|
dev string
|
||||||
|
info *client.ResponseInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func printInfo(via string, from string, info *client.ResponseInfo) {
|
||||||
|
slog.Info("device",
|
||||||
|
"via", via,
|
||||||
|
"from", from,
|
||||||
|
"board_id", hex.EncodeToString(info.BoardID[:]),
|
||||||
|
"mac", net.HardwareAddr(info.MAC[:]).String(),
|
||||||
|
"ip", net.IP(info.IP[:]).String(),
|
||||||
|
"firmware", info.FirmwareName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdInfo(args []string) error {
|
||||||
|
fs := flag.NewFlagSet("info", flag.ExitOnError)
|
||||||
|
udpAddr := fs.String("udp", "", "connect via UDP to this IP address")
|
||||||
|
iface := fs.String("iface", "", "bind to this network interface (for broadcast)")
|
||||||
|
fs.Parse(args)
|
||||||
|
|
||||||
|
if *udpAddr == "" && *iface != "" {
|
||||||
|
bcast, err := client.InterfaceBroadcast(*iface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*udpAddr = bcast
|
||||||
|
}
|
||||||
|
|
||||||
|
if *udpAddr != "" {
|
||||||
|
c, err := client.NewUDP(*udpAddr, *iface, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
infos, err := c.InfoAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(infos) == 0 {
|
||||||
|
return fmt.Errorf("no devices responded")
|
||||||
|
}
|
||||||
|
for _, r := range infos {
|
||||||
|
printInfo(*udpAddr, r.From, r.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
devs, err := client.ListSerial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(devs) == 0 {
|
||||||
|
return fmt.Errorf("no devices found")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]deviceResult, len(devs))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, dev := range devs {
|
||||||
|
results[i].dev = dev
|
||||||
|
wg.Go(func() {
|
||||||
|
c, err := client.NewSerial(dev, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
results[i].err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, err := c.Info()
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
results[i].err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
results[i].info = info
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
for _, r := range results {
|
||||||
|
if r.err != nil {
|
||||||
|
slog.Error("device error", "dev", r.dev, "err", r.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
printInfo(r.dev, r.dev, r.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func boardSerial(id [8]byte) string {
|
||||||
|
return fmt.Sprintf("%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||||
|
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7])
|
||||||
|
}
|
||||||
|
|
||||||
|
type deviceInfo struct {
|
||||||
|
dev string
|
||||||
|
serial string
|
||||||
|
uf2 string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFirmware(buildDir string) error {
|
||||||
|
slog.Info("configuring")
|
||||||
|
cmake := exec.Command("cmake", "-S", filepath.Join(filepath.Dir(buildDir)), "-B", buildDir)
|
||||||
|
cmake.Stdout = os.Stdout
|
||||||
|
cmake.Stderr = os.Stderr
|
||||||
|
if err := cmake.Run(); err != nil {
|
||||||
|
return fmt.Errorf("cmake failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("building")
|
||||||
|
cmd := exec.Command("make", "-C", buildDir)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("build failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdLog(_ []string) error {
|
||||||
|
devs, err := client.ListSerial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(devs) == 0 {
|
||||||
|
return fmt.Errorf("no devices found")
|
||||||
|
}
|
||||||
|
for _, dev := range devs {
|
||||||
|
log := slog.With("dev", dev)
|
||||||
|
c, err := client.NewSerial(dev, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("connect error", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp, err := c.Log()
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("log error", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(resp.Entries) == 0 {
|
||||||
|
log.Info("no debug messages")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, e := range resp.Entries {
|
||||||
|
log.Info("dlog", "t_us", e.TimestampUS, "msg", e.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdLoad(args []string) error {
|
||||||
|
target := "all"
|
||||||
|
if len(args) > 0 {
|
||||||
|
target = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildDir := filepath.Join(wd, "firmware", "build")
|
||||||
|
|
||||||
|
if err := buildFirmware(buildDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
devs, err := client.ListSerial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
allTargets := []struct {
|
||||||
|
name string
|
||||||
|
uf2 string
|
||||||
|
}{
|
||||||
|
{"picomap", filepath.Join(buildDir, "picomap.uf2")},
|
||||||
|
{"picomap_test", filepath.Join(buildDir, "picomap_test.uf2")},
|
||||||
|
}
|
||||||
|
|
||||||
|
var targets []struct {
|
||||||
|
name string
|
||||||
|
uf2 string
|
||||||
|
}
|
||||||
|
switch target {
|
||||||
|
case "all":
|
||||||
|
targets = allTargets
|
||||||
|
case "picomap":
|
||||||
|
targets = allTargets[:1]
|
||||||
|
case "picomap_test":
|
||||||
|
targets = allTargets[1:]
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown target %q", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(devs) < len(targets) {
|
||||||
|
return fmt.Errorf("need %d device(s), found %d", len(targets), len(devs))
|
||||||
|
}
|
||||||
|
|
||||||
|
devices := make([]deviceInfo, len(targets))
|
||||||
|
errs := make([]error, len(targets))
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := range targets {
|
||||||
|
log := slog.With("dev", devs[i])
|
||||||
|
wg.Go(func() {
|
||||||
|
c, err := client.NewSerial(devs[i], 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, err := c.Info()
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
devices[i] = deviceInfo{
|
||||||
|
dev: devs[i],
|
||||||
|
serial: boardSerial(info.BoardID),
|
||||||
|
uf2: targets[i].uf2,
|
||||||
|
name: targets[i].name,
|
||||||
|
}
|
||||||
|
log.Info("got info", "serial", devices[i].serial, "firmware", info.FirmwareName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for i, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] info: %w", devs[i], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range devices {
|
||||||
|
log := slog.With("serial", devices[i].serial)
|
||||||
|
wg.Go(func() {
|
||||||
|
log.Info("sending PICOBOOT")
|
||||||
|
c, err := client.NewSerial(devices[i].dev, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.PICOBOOT()
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs[i] = fmt.Errorf("PICOBOOT %s: %w", devices[i].serial, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("PICOBOOT sent")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for i, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] %w", devices[i].serial, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf2s := make([]string, len(targets))
|
||||||
|
for i := range targets {
|
||||||
|
uf2s[i] = targets[i].uf2
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range devices {
|
||||||
|
log := slog.With("serial", devices[i].serial)
|
||||||
|
wg.Go(func() {
|
||||||
|
log.Info("loading", "uf2", devices[i].name)
|
||||||
|
errs[i] = picotool.Load(devices[i].uf2, devices[i].serial, 10*time.Second)
|
||||||
|
if errs[i] == nil {
|
||||||
|
log.Info("loaded", "uf2", devices[i].name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for i, err := range errs {
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[%s] load: %w", devices[i].serial, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdTest(args []string) error {
|
||||||
|
if len(args) < 1 {
|
||||||
|
return fmt.Errorf("usage: picomap test <name>")
|
||||||
|
}
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
devs, err := client.ListSerial()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var testDev string
|
||||||
|
for _, dev := range devs {
|
||||||
|
log := slog.With("dev", dev)
|
||||||
|
c, err := client.NewSerial(dev, 500*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("connect error", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := c.Info()
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("info error", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info("got info", "firmware", info.FirmwareName)
|
||||||
|
if info.FirmwareName == "picomap_test" {
|
||||||
|
testDev = dev
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if testDev == "" {
|
||||||
|
return fmt.Errorf("no picomap_test device found")
|
||||||
|
}
|
||||||
|
|
||||||
|
log := slog.With("dev", testDev)
|
||||||
|
log.Info("running test", "name", name)
|
||||||
|
|
||||||
|
c, err := client.NewSerial(testDev, 10*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
result, err := c.Test(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("remote: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range result.Messages {
|
||||||
|
log.Info("remote", "msg", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Pass {
|
||||||
|
log.Info("PASS")
|
||||||
|
} else {
|
||||||
|
log.Error("FAIL")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -10,13 +10,14 @@ set(CMAKE_CXX_STANDARD 23)
|
|||||||
pico_sdk_init()
|
pico_sdk_init()
|
||||||
|
|
||||||
set(LIB_SOURCES
|
set(LIB_SOURCES
|
||||||
lib/dhcp.cpp
|
lib/dispatch.cpp
|
||||||
|
lib/handlers.cpp
|
||||||
lib/net.cpp
|
lib/net.cpp
|
||||||
lib/tusb_config.cpp
|
lib/tusb_config.cpp
|
||||||
w6300/w6300.cpp
|
w6300/w6300.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB_DEPS pico_stdlib pico_rand tinyusb_device tinyusb_board hardware_pio hardware_spi hardware_dma hardware_clocks)
|
set(LIB_DEPS pico_stdlib tinyusb_device tinyusb_board hardware_pio hardware_spi hardware_dma hardware_clocks)
|
||||||
|
|
||||||
add_executable(picomap firmware.cpp ${LIB_SOURCES})
|
add_executable(picomap firmware.cpp ${LIB_SOURCES})
|
||||||
target_include_directories(picomap PRIVATE include w6300)
|
target_include_directories(picomap PRIVATE include w6300)
|
||||||
@@ -24,6 +25,7 @@ target_compile_options(picomap PRIVATE -Wall -Wextra -Wno-unused-parameter)
|
|||||||
pico_generate_pio_header(picomap ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
|
pico_generate_pio_header(picomap ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
|
||||||
pico_enable_stdio_usb(picomap 0)
|
pico_enable_stdio_usb(picomap 0)
|
||||||
pico_enable_stdio_uart(picomap 0)
|
pico_enable_stdio_uart(picomap 0)
|
||||||
|
pico_set_binary_type(picomap copy_to_ram)
|
||||||
pico_add_extra_outputs(picomap)
|
pico_add_extra_outputs(picomap)
|
||||||
target_link_libraries(picomap ${LIB_DEPS})
|
target_link_libraries(picomap ${LIB_DEPS})
|
||||||
|
|
||||||
@@ -33,5 +35,6 @@ target_compile_options(picomap_test PRIVATE -Wall -Wextra -Wno-unused-parameter)
|
|||||||
pico_generate_pio_header(picomap_test ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
|
pico_generate_pio_header(picomap_test ${CMAKE_CURRENT_LIST_DIR}/w6300/qspi.pio)
|
||||||
pico_enable_stdio_usb(picomap_test 0)
|
pico_enable_stdio_usb(picomap_test 0)
|
||||||
pico_enable_stdio_uart(picomap_test 0)
|
pico_enable_stdio_uart(picomap_test 0)
|
||||||
|
pico_set_binary_type(picomap_test copy_to_ram)
|
||||||
pico_add_extra_outputs(picomap_test)
|
pico_add_extra_outputs(picomap_test)
|
||||||
target_link_libraries(picomap_test ${LIB_DEPS})
|
target_link_libraries(picomap_test ${LIB_DEPS})
|
||||||
|
|||||||
+11
-62
@@ -1,66 +1,15 @@
|
|||||||
#include "pico/stdlib.h"
|
#include "dispatch.h"
|
||||||
#include "pico/bootrom.h"
|
#include "handlers.h"
|
||||||
#include "pico/unique_id.h"
|
|
||||||
#include "tusb.h"
|
|
||||||
#include "wire.h"
|
|
||||||
#include "usb_cdc.h"
|
|
||||||
#include "timer_queue.h"
|
|
||||||
#include "dhcp.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include "w6300.h"
|
|
||||||
|
|
||||||
static usb_cdc usb;
|
std::string_view firmware_name = "picomap";
|
||||||
static timer_queue timers;
|
|
||||||
|
static constexpr handler_entry handlers[] = {
|
||||||
|
{RequestPICOBOOT::ext_id, handle_picoboot},
|
||||||
|
{RequestInfo::ext_id, handle_info},
|
||||||
|
{RequestLog::ext_id, handle_log},
|
||||||
|
};
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
tusb_init();
|
dispatch_init();
|
||||||
|
dispatch_run(handlers);
|
||||||
net_init();
|
|
||||||
|
|
||||||
auto ninfo = w6300::get_net_info();
|
|
||||||
dhcp_start(timers, ninfo.mac);
|
|
||||||
|
|
||||||
static static_vector<uint8_t, 256> rx_buf;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
tud_task();
|
|
||||||
|
|
||||||
usb.drain();
|
|
||||||
timers.run();
|
|
||||||
|
|
||||||
while (tud_cdc_available()) {
|
|
||||||
uint8_t byte;
|
|
||||||
if (tud_cdc_read(&byte, 1) != 1) break;
|
|
||||||
|
|
||||||
rx_buf.push_back(byte);
|
|
||||||
|
|
||||||
auto msg = try_decode(rx_buf);
|
|
||||||
if (!msg) {
|
|
||||||
if (rx_buf.full()) rx_buf.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rx_buf.clear();
|
|
||||||
|
|
||||||
switch (msg->type_id) {
|
|
||||||
case RequestPICOBOOT::ext_id:
|
|
||||||
usb.send(encode_response(msg->message_id, ResponsePICOBOOT{}));
|
|
||||||
sleep_ms(100);
|
|
||||||
reset_usb_boot(0, 1);
|
|
||||||
break;
|
|
||||||
case RequestInfo::ext_id: {
|
|
||||||
ResponseInfo resp;
|
|
||||||
pico_unique_board_id_t uid;
|
|
||||||
pico_get_unique_board_id(&uid);
|
|
||||||
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
|
|
||||||
auto ninfo = w6300::get_net_info();
|
|
||||||
resp.mac = ninfo.mac;
|
|
||||||
usb.send(encode_response(msg->message_id, resp));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__wfi();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
#include "pico/time.h"
|
||||||
|
#include "ring_buffer.h"
|
||||||
|
|
||||||
|
struct log_entry {
|
||||||
|
uint32_t timestamp_us;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ring_buffer<log_entry, 32> g_debug_log;
|
||||||
|
|
||||||
|
inline void dlog(std::string_view msg) {
|
||||||
|
g_debug_log.push_overwrite(log_entry{static_cast<uint32_t>(time_us_32()), std::string(msg)});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void dlog_if_slow(std::string_view label, uint32_t threshold_us, std::function<void()> fn) {
|
||||||
|
uint32_t t0 = time_us_32();
|
||||||
|
fn();
|
||||||
|
uint32_t elapsed = time_us_32() - t0;
|
||||||
|
if (elapsed > threshold_us)
|
||||||
|
dlog(std::string(label) + " " + std::to_string(elapsed) + "us");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<log_entry> dlog_drain() {
|
||||||
|
std::vector<log_entry> result;
|
||||||
|
uint16_t n = g_debug_log.used();
|
||||||
|
result.reserve(n);
|
||||||
|
for (uint16_t i = 0; i < n; i++) {
|
||||||
|
log_entry e;
|
||||||
|
g_debug_log.peek(std::span{&e, 1});
|
||||||
|
result.push_back(std::move(e));
|
||||||
|
g_debug_log.consume(1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <array>
|
|
||||||
#include "timer_queue.h"
|
|
||||||
|
|
||||||
void dhcp_start(timer_queue& timers, const std::array<uint8_t, 6>& mac);
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
#include "wire.h"
|
||||||
|
|
||||||
|
using handler_fn = size_t (*)(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
|
||||||
|
|
||||||
|
struct handler_entry {
|
||||||
|
int8_t type_id;
|
||||||
|
handler_fn handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Req, auto Fn>
|
||||||
|
size_t typed_handler(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out) {
|
||||||
|
msgpack::parser p(payload.data(), static_cast<int>(payload.size()));
|
||||||
|
Req req;
|
||||||
|
auto tup = req.as_tuple();
|
||||||
|
auto r = msgpack::unpack(p, tup);
|
||||||
|
if (!r) {
|
||||||
|
return encode_response_into(out, message_id, DeviceError{1, "decode request ext_id=" +
|
||||||
|
std::to_string(Req::ext_id) + ": msgpack error " + std::to_string(static_cast<int>(r.error()))});
|
||||||
|
}
|
||||||
|
return Fn(message_id, req, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch_init();
|
||||||
|
void dispatch_schedule_ms(uint32_t ms, std::function<void()> fn);
|
||||||
|
[[noreturn]] void dispatch_run(std::span<const handler_entry> handlers);
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <string_view>
|
||||||
|
#include "wire.h"
|
||||||
|
|
||||||
|
extern std::string_view firmware_name;
|
||||||
|
|
||||||
|
size_t handle_picoboot(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
|
||||||
|
size_t handle_info(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
|
||||||
|
size_t handle_log(uint32_t message_id, std::span<const uint8_t> payload, span_writer &out);
|
||||||
+78
-53
@@ -6,11 +6,11 @@
|
|||||||
#include <expected>
|
#include <expected>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "span_writer.h"
|
||||||
|
|
||||||
namespace msgpack {
|
namespace msgpack {
|
||||||
|
|
||||||
@@ -163,26 +163,23 @@ inline result<body_info> get_body_info(const uint8_t *p, int size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class packer {
|
class packer {
|
||||||
public:
|
|
||||||
using buffer = std::vector<std::uint8_t>;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<buffer> m_buffer;
|
span_writer m_buf;
|
||||||
|
|
||||||
template <typename T> void push_big_endian(T n) {
|
template <typename T> void push_big_endian(T n) {
|
||||||
auto p = reinterpret_cast<std::uint8_t *>(&n) + (sizeof(T) - 1);
|
auto p = reinterpret_cast<std::uint8_t *>(&n) + (sizeof(T) - 1);
|
||||||
for (size_t i = 0; i < sizeof(T); ++i, --p) {
|
for (size_t i = 0; i < sizeof(T); ++i, --p) {
|
||||||
m_buffer->push_back(*p);
|
m_buf.push_back(*p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Range> void push(const Range &r) {
|
template <class Range> void push(const Range &r) {
|
||||||
m_buffer->insert(m_buffer->end(), std::begin(r), std::end(r));
|
m_buf.insert(m_buf.end(), std::begin(r), std::end(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
packer() : m_buffer(std::make_shared<buffer>()) {}
|
packer(uint8_t *data, size_t capacity) : m_buf(data, capacity) {}
|
||||||
packer(const std::shared_ptr<buffer> &buf) : m_buffer(buf) {}
|
packer(span_writer buf) : m_buf(buf) {}
|
||||||
|
|
||||||
packer(const packer &) = delete;
|
packer(const packer &) = delete;
|
||||||
packer &operator=(const packer &) = delete;
|
packer &operator=(const packer &) = delete;
|
||||||
@@ -190,12 +187,12 @@ public:
|
|||||||
using pack_result = result<std::reference_wrapper<packer>>;
|
using pack_result = result<std::reference_wrapper<packer>>;
|
||||||
|
|
||||||
pack_result pack_nil() {
|
pack_result pack_nil() {
|
||||||
m_buffer->push_back(format::NIL);
|
m_buf.push_back(format::NIL);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
pack_result pack_bool(bool v) {
|
pack_result pack_bool(bool v) {
|
||||||
m_buffer->push_back(v ? format::TRUE : format::FALSE);
|
m_buf.push_back(v ? format::TRUE : format::FALSE);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,36 +200,36 @@ public:
|
|||||||
pack_result pack_integer(T n) {
|
pack_result pack_integer(T n) {
|
||||||
if constexpr (std::is_signed_v<T>) {
|
if constexpr (std::is_signed_v<T>) {
|
||||||
if (n >= 0 && n <= 0x7F) {
|
if (n >= 0 && n <= 0x7F) {
|
||||||
m_buffer->push_back(static_cast<uint8_t>(n));
|
m_buf.push_back(static_cast<uint8_t>(n));
|
||||||
} else if (n >= -32 && n < 0) {
|
} else if (n >= -32 && n < 0) {
|
||||||
m_buffer->push_back(static_cast<uint8_t>(n)); // negative fixint
|
m_buf.push_back(static_cast<uint8_t>(n)); // negative fixint
|
||||||
} else if (n >= std::numeric_limits<int8_t>::min() && n <= std::numeric_limits<int8_t>::max()) {
|
} else if (n >= std::numeric_limits<int8_t>::min() && n <= std::numeric_limits<int8_t>::max()) {
|
||||||
m_buffer->push_back(format::INT8);
|
m_buf.push_back(format::INT8);
|
||||||
m_buffer->push_back(static_cast<uint8_t>(n));
|
m_buf.push_back(static_cast<uint8_t>(n));
|
||||||
} else if (n >= std::numeric_limits<int16_t>::min() && n <= std::numeric_limits<int16_t>::max()) {
|
} else if (n >= std::numeric_limits<int16_t>::min() && n <= std::numeric_limits<int16_t>::max()) {
|
||||||
m_buffer->push_back(format::INT16);
|
m_buf.push_back(format::INT16);
|
||||||
push_big_endian(static_cast<int16_t>(n));
|
push_big_endian(static_cast<int16_t>(n));
|
||||||
} else if (n >= std::numeric_limits<int32_t>::min() && n <= std::numeric_limits<int32_t>::max()) {
|
} else if (n >= std::numeric_limits<int32_t>::min() && n <= std::numeric_limits<int32_t>::max()) {
|
||||||
m_buffer->push_back(format::INT32);
|
m_buf.push_back(format::INT32);
|
||||||
push_big_endian(static_cast<int32_t>(n));
|
push_big_endian(static_cast<int32_t>(n));
|
||||||
} else {
|
} else {
|
||||||
m_buffer->push_back(format::INT64);
|
m_buf.push_back(format::INT64);
|
||||||
push_big_endian(static_cast<int64_t>(n));
|
push_big_endian(static_cast<int64_t>(n));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (n <= 0x7F) {
|
if (n <= 0x7F) {
|
||||||
m_buffer->push_back(static_cast<uint8_t>(n));
|
m_buf.push_back(static_cast<uint8_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint8_t>::max()) {
|
} else if (n <= std::numeric_limits<uint8_t>::max()) {
|
||||||
m_buffer->push_back(format::UINT8);
|
m_buf.push_back(format::UINT8);
|
||||||
m_buffer->push_back(static_cast<uint8_t>(n));
|
m_buf.push_back(static_cast<uint8_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::UINT16);
|
m_buf.push_back(format::UINT16);
|
||||||
push_big_endian(static_cast<uint16_t>(n));
|
push_big_endian(static_cast<uint16_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::UINT32);
|
m_buf.push_back(format::UINT32);
|
||||||
push_big_endian(static_cast<uint32_t>(n));
|
push_big_endian(static_cast<uint32_t>(n));
|
||||||
} else {
|
} else {
|
||||||
m_buffer->push_back(format::UINT64);
|
m_buf.push_back(format::UINT64);
|
||||||
push_big_endian(static_cast<uint64_t>(n));
|
push_big_endian(static_cast<uint64_t>(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,13 +237,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
pack_result pack_float(float n) {
|
pack_result pack_float(float n) {
|
||||||
m_buffer->push_back(format::FLOAT32);
|
m_buf.push_back(format::FLOAT32);
|
||||||
push_big_endian(n);
|
push_big_endian(n);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
pack_result pack_double(double n) {
|
pack_result pack_double(double n) {
|
||||||
m_buffer->push_back(format::FLOAT64);
|
m_buf.push_back(format::FLOAT64);
|
||||||
push_big_endian(n);
|
push_big_endian(n);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -255,15 +252,15 @@ public:
|
|||||||
pack_result pack_str(const Range &r) {
|
pack_result pack_str(const Range &r) {
|
||||||
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
||||||
if (sz < 32) {
|
if (sz < 32) {
|
||||||
m_buffer->push_back(format::FIXSTR_MIN | static_cast<uint8_t>(sz));
|
m_buf.push_back(format::FIXSTR_MIN | static_cast<uint8_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint8_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint8_t>::max()) {
|
||||||
m_buffer->push_back(format::STR8);
|
m_buf.push_back(format::STR8);
|
||||||
m_buffer->push_back(static_cast<uint8_t>(sz));
|
m_buf.push_back(static_cast<uint8_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::STR16);
|
m_buf.push_back(format::STR16);
|
||||||
push_big_endian(static_cast<uint16_t>(sz));
|
push_big_endian(static_cast<uint16_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::STR32);
|
m_buf.push_back(format::STR32);
|
||||||
push_big_endian(static_cast<uint32_t>(sz));
|
push_big_endian(static_cast<uint32_t>(sz));
|
||||||
} else {
|
} else {
|
||||||
return std::unexpected(error_code::overflow);
|
return std::unexpected(error_code::overflow);
|
||||||
@@ -280,13 +277,13 @@ public:
|
|||||||
pack_result pack_bin(const Range &r) {
|
pack_result pack_bin(const Range &r) {
|
||||||
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
||||||
if (sz <= std::numeric_limits<uint8_t>::max()) {
|
if (sz <= std::numeric_limits<uint8_t>::max()) {
|
||||||
m_buffer->push_back(format::BIN8);
|
m_buf.push_back(format::BIN8);
|
||||||
m_buffer->push_back(static_cast<uint8_t>(sz));
|
m_buf.push_back(static_cast<uint8_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::BIN16);
|
m_buf.push_back(format::BIN16);
|
||||||
push_big_endian(static_cast<uint16_t>(sz));
|
push_big_endian(static_cast<uint16_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::BIN32);
|
m_buf.push_back(format::BIN32);
|
||||||
push_big_endian(static_cast<uint32_t>(sz));
|
push_big_endian(static_cast<uint32_t>(sz));
|
||||||
} else {
|
} else {
|
||||||
return std::unexpected(error_code::overflow);
|
return std::unexpected(error_code::overflow);
|
||||||
@@ -297,12 +294,12 @@ public:
|
|||||||
|
|
||||||
pack_result pack_array(size_t n) {
|
pack_result pack_array(size_t n) {
|
||||||
if (n <= 15) {
|
if (n <= 15) {
|
||||||
m_buffer->push_back(format::FIXARRAY_MIN | static_cast<uint8_t>(n));
|
m_buf.push_back(format::FIXARRAY_MIN | static_cast<uint8_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::ARRAY16);
|
m_buf.push_back(format::ARRAY16);
|
||||||
push_big_endian(static_cast<uint16_t>(n));
|
push_big_endian(static_cast<uint16_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::ARRAY32);
|
m_buf.push_back(format::ARRAY32);
|
||||||
push_big_endian(static_cast<uint32_t>(n));
|
push_big_endian(static_cast<uint32_t>(n));
|
||||||
} else {
|
} else {
|
||||||
return std::unexpected(error_code::overflow);
|
return std::unexpected(error_code::overflow);
|
||||||
@@ -312,12 +309,12 @@ public:
|
|||||||
|
|
||||||
pack_result pack_map(size_t n) {
|
pack_result pack_map(size_t n) {
|
||||||
if (n <= 15) {
|
if (n <= 15) {
|
||||||
m_buffer->push_back(format::FIXMAP_MIN | static_cast<uint8_t>(n));
|
m_buf.push_back(format::FIXMAP_MIN | static_cast<uint8_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
} else if (n <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::MAP16);
|
m_buf.push_back(format::MAP16);
|
||||||
push_big_endian(static_cast<uint16_t>(n));
|
push_big_endian(static_cast<uint16_t>(n));
|
||||||
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
} else if (n <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::MAP32);
|
m_buf.push_back(format::MAP32);
|
||||||
push_big_endian(static_cast<uint32_t>(n));
|
push_big_endian(static_cast<uint32_t>(n));
|
||||||
} else {
|
} else {
|
||||||
return std::unexpected(error_code::overflow);
|
return std::unexpected(error_code::overflow);
|
||||||
@@ -330,26 +327,26 @@ public:
|
|||||||
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
auto sz = static_cast<size_t>(std::distance(std::begin(r), std::end(r)));
|
||||||
|
|
||||||
switch (sz) {
|
switch (sz) {
|
||||||
case 1: m_buffer->push_back(format::FIXEXT1); break;
|
case 1: m_buf.push_back(format::FIXEXT1); break;
|
||||||
case 2: m_buffer->push_back(format::FIXEXT2); break;
|
case 2: m_buf.push_back(format::FIXEXT2); break;
|
||||||
case 4: m_buffer->push_back(format::FIXEXT4); break;
|
case 4: m_buf.push_back(format::FIXEXT4); break;
|
||||||
case 8: m_buffer->push_back(format::FIXEXT8); break;
|
case 8: m_buf.push_back(format::FIXEXT8); break;
|
||||||
case 16: m_buffer->push_back(format::FIXEXT16); break;
|
case 16: m_buf.push_back(format::FIXEXT16); break;
|
||||||
default:
|
default:
|
||||||
if (sz <= std::numeric_limits<uint8_t>::max()) {
|
if (sz <= std::numeric_limits<uint8_t>::max()) {
|
||||||
m_buffer->push_back(format::EXT8);
|
m_buf.push_back(format::EXT8);
|
||||||
m_buffer->push_back(static_cast<uint8_t>(sz));
|
m_buf.push_back(static_cast<uint8_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint16_t>::max()) {
|
||||||
m_buffer->push_back(format::EXT16);
|
m_buf.push_back(format::EXT16);
|
||||||
push_big_endian(static_cast<uint16_t>(sz));
|
push_big_endian(static_cast<uint16_t>(sz));
|
||||||
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
} else if (sz <= std::numeric_limits<uint32_t>::max()) {
|
||||||
m_buffer->push_back(format::EXT32);
|
m_buf.push_back(format::EXT32);
|
||||||
push_big_endian(static_cast<uint32_t>(sz));
|
push_big_endian(static_cast<uint32_t>(sz));
|
||||||
} else {
|
} else {
|
||||||
return std::unexpected(error_code::overflow);
|
return std::unexpected(error_code::overflow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_buffer->push_back(static_cast<uint8_t>(type));
|
m_buf.push_back(static_cast<uint8_t>(type));
|
||||||
push(r);
|
push(r);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -367,6 +364,18 @@ public:
|
|||||||
|
|
||||||
pack_result pack(const std::vector<uint8_t> &v) { return pack_bin(v); }
|
pack_result pack(const std::vector<uint8_t> &v) { return pack_bin(v); }
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires (!std::is_same_v<T, uint8_t>)
|
||||||
|
pack_result pack(const std::vector<T> &v) {
|
||||||
|
auto r = pack_array(v.size());
|
||||||
|
if (!r) return r;
|
||||||
|
for (auto& elem : v) {
|
||||||
|
r = r->get().pack(elem);
|
||||||
|
if (!r) return r;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
pack_result pack(const std::array<uint8_t, N> &v) { return pack_bin(v); }
|
pack_result pack(const std::array<uint8_t, N> &v) { return pack_bin(v); }
|
||||||
|
|
||||||
@@ -380,7 +389,8 @@ public:
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
requires requires(const T &v) { { T::ext_id } -> std::convertible_to<int8_t>; v.as_tuple(); }
|
requires requires(const T &v) { { T::ext_id } -> std::convertible_to<int8_t>; v.as_tuple(); }
|
||||||
pack_result pack(const T &v) {
|
pack_result pack(const T &v) {
|
||||||
packer inner;
|
uint8_t ext_buf[256];
|
||||||
|
packer inner(ext_buf, sizeof(ext_buf));
|
||||||
auto r = inner.pack(v.as_tuple());
|
auto r = inner.pack(v.as_tuple());
|
||||||
if (!r) return r;
|
if (!r) return r;
|
||||||
return pack_ext(T::ext_id, inner.get_payload());
|
return pack_ext(T::ext_id, inner.get_payload());
|
||||||
@@ -401,7 +411,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
const buffer &get_payload() const { return *m_buffer; }
|
const span_writer &get_payload() const { return m_buf; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class parser {
|
class parser {
|
||||||
@@ -744,6 +754,21 @@ inline result<parser> unpack(const parser &p, std::vector<uint8_t> &out) {
|
|||||||
return p.next();
|
return p.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires (!std::is_same_v<T, uint8_t>)
|
||||||
|
result<parser> unpack(const parser &p, std::vector<T> &out) {
|
||||||
|
auto cnt = p.count();
|
||||||
|
if (!cnt) return std::unexpected(cnt.error());
|
||||||
|
out.resize(*cnt);
|
||||||
|
result<parser> cur = p.first_item();
|
||||||
|
for (size_t i = 0; i < *cnt; i++) {
|
||||||
|
if (!cur) return cur;
|
||||||
|
cur = unpack(*cur, out[i]);
|
||||||
|
}
|
||||||
|
if (!cur) return cur;
|
||||||
|
return p.next();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename... Ts, size_t... Is>
|
template <typename... Ts, size_t... Is>
|
||||||
result<parser> unpack_tuple_elements(const parser &p, std::tuple<Ts...> &t, std::index_sequence<Is...>) {
|
result<parser> unpack_tuple_elements(const parser &p, std::tuple<Ts...> &t, std::index_sequence<Is...>) {
|
||||||
result<parser> cur = p.first_item();
|
result<parser> cur = p.first_item();
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <span>
|
||||||
|
#include "span_writer.h"
|
||||||
|
|
||||||
|
struct net_state {
|
||||||
|
std::array<uint8_t, 6> mac;
|
||||||
|
std::array<uint8_t, 4> ip;
|
||||||
|
};
|
||||||
|
|
||||||
|
using net_handler = std::function<size_t(std::span<const uint8_t> payload, span_writer &out)>;
|
||||||
|
|
||||||
bool net_init();
|
bool net_init();
|
||||||
|
const net_state& net_get_state();
|
||||||
|
void net_set_handler(net_handler handler);
|
||||||
|
void net_poll();
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
template <uint16_t N>
|
template <typename T, uint16_t N>
|
||||||
struct ring_buffer {
|
struct ring_buffer {
|
||||||
std::array<uint8_t, N> data = {};
|
std::array<T, N> data = {};
|
||||||
uint16_t head = 0;
|
uint16_t head = 0;
|
||||||
uint16_t tail = 0;
|
uint16_t tail = 0;
|
||||||
|
|
||||||
@@ -13,13 +13,23 @@ struct ring_buffer {
|
|||||||
uint16_t free() const { return N - used(); }
|
uint16_t free() const { return N - used(); }
|
||||||
bool empty() const { return head == tail; }
|
bool empty() const { return head == tail; }
|
||||||
|
|
||||||
void push(std::span<const uint8_t> src) {
|
void push(std::span<const T> src) {
|
||||||
if (src.size() > free()) return;
|
if (src.size() > free()) return;
|
||||||
for (auto b : src)
|
for (auto& v : src)
|
||||||
data[(tail++) % N] = b;
|
data[(tail++) % N] = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t peek(std::span<uint8_t> dst) const {
|
void push(const T& v) {
|
||||||
|
if (free() == 0) return;
|
||||||
|
data[(tail++) % N] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_overwrite(const T& v) {
|
||||||
|
if (free() == 0) head++;
|
||||||
|
data[(tail++) % N] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t peek(std::span<T> dst) const {
|
||||||
uint16_t len = dst.size() < used() ? dst.size() : used();
|
uint16_t len = dst.size() < used() ? dst.size() : used();
|
||||||
for (uint16_t i = 0; i < len; i++)
|
for (uint16_t i = 0; i < len; i++)
|
||||||
dst[i] = data[(head + i) % N];
|
dst[i] = data[(head + i) % N];
|
||||||
@@ -34,7 +44,7 @@ struct ring_buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::span<const uint8_t> read_contiguous() const {
|
std::span<const T> read_contiguous() const {
|
||||||
uint16_t offset = head % N;
|
uint16_t offset = head % N;
|
||||||
uint16_t contig = N - offset;
|
uint16_t contig = N - offset;
|
||||||
uint16_t pending = used();
|
uint16_t pending = used();
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
class span_writer {
|
||||||
|
uint8_t *m_data;
|
||||||
|
size_t m_capacity;
|
||||||
|
size_t m_size = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
span_writer(uint8_t *data, size_t capacity) : m_data(data), m_capacity(capacity) {}
|
||||||
|
|
||||||
|
void push_back(uint8_t v) {
|
||||||
|
if (m_size < m_capacity) m_data[m_size++] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class It>
|
||||||
|
void insert(uint8_t *, It first, It last) {
|
||||||
|
while (first != last && m_size < m_capacity)
|
||||||
|
m_data[m_size++] = *first++;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const { return m_size; }
|
||||||
|
size_t capacity() const { return m_capacity; }
|
||||||
|
bool full() const { return m_size >= m_capacity; }
|
||||||
|
|
||||||
|
uint8_t *data() { return m_data; }
|
||||||
|
const uint8_t *data() const { return m_data; }
|
||||||
|
|
||||||
|
uint8_t *begin() { return m_data; }
|
||||||
|
uint8_t *end() { return m_data + m_size; }
|
||||||
|
const uint8_t *begin() const { return m_data; }
|
||||||
|
const uint8_t *end() const { return m_data + m_size; }
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ inline bool operator<(const timer_entry& a, const timer_entry& b) {
|
|||||||
struct timer_queue {
|
struct timer_queue {
|
||||||
sorted_list<timer_entry, 16> queue;
|
sorted_list<timer_entry, 16> queue;
|
||||||
alarm_id_t alarm = -1;
|
alarm_id_t alarm = -1;
|
||||||
|
volatile bool irq_pending = false;
|
||||||
|
|
||||||
void schedule(absolute_time_t when, std::function<void()> fn) {
|
void schedule(absolute_time_t when, std::function<void()> fn) {
|
||||||
queue.insert({when, std::move(fn)});
|
queue.insert({when, std::move(fn)});
|
||||||
@@ -26,6 +27,8 @@ struct timer_queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void run() {
|
void run() {
|
||||||
|
if (!irq_pending) return;
|
||||||
|
irq_pending = false;
|
||||||
while (!queue.empty()) {
|
while (!queue.empty()) {
|
||||||
auto& front = queue.front();
|
auto& front = queue.front();
|
||||||
if (absolute_time_diff_us(get_absolute_time(), front.when) > 0) break;
|
if (absolute_time_diff_us(get_absolute_time(), front.when) > 0) break;
|
||||||
@@ -39,12 +42,15 @@ struct timer_queue {
|
|||||||
bool empty() const { return queue.empty(); }
|
bool empty() const { return queue.empty(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int64_t alarm_cb(alarm_id_t, void*) { return 0; }
|
static int64_t alarm_cb(alarm_id_t, void* user_data) {
|
||||||
|
static_cast<timer_queue*>(user_data)->irq_pending = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void arm() {
|
void arm() {
|
||||||
if (alarm >= 0) cancel_alarm(alarm);
|
if (alarm >= 0) cancel_alarm(alarm);
|
||||||
alarm = -1;
|
alarm = -1;
|
||||||
if (!queue.empty())
|
if (!queue.empty())
|
||||||
alarm = add_alarm_at(queue.front().when, alarm_cb, nullptr, false);
|
alarm = add_alarm_at(queue.front().when, alarm_cb, this, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "ring_buffer.h"
|
#include "ring_buffer.h"
|
||||||
|
|
||||||
struct usb_cdc {
|
struct usb_cdc {
|
||||||
ring_buffer<512> tx;
|
ring_buffer<uint8_t, 8192> tx;
|
||||||
|
|
||||||
void send(std::span<const uint8_t> data) {
|
void send(std::span<const uint8_t> data) {
|
||||||
tx.push(data);
|
tx.push(data);
|
||||||
|
|||||||
+84
-11
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -47,8 +48,45 @@ struct ResponseInfo {
|
|||||||
static constexpr int8_t ext_id = 5;
|
static constexpr int8_t ext_id = 5;
|
||||||
std::array<uint8_t, 8> board_id;
|
std::array<uint8_t, 8> board_id;
|
||||||
std::array<uint8_t, 6> mac;
|
std::array<uint8_t, 6> mac;
|
||||||
auto as_tuple() const { return std::tie(board_id, mac); }
|
std::array<uint8_t, 4> ip;
|
||||||
auto as_tuple() { return std::tie(board_id, mac); }
|
std::string firmware_name;
|
||||||
|
auto as_tuple() const { return std::tie(board_id, mac, ip, firmware_name); }
|
||||||
|
auto as_tuple() { return std::tie(board_id, mac, ip, firmware_name); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequestLog {
|
||||||
|
static constexpr int8_t ext_id = 6;
|
||||||
|
auto as_tuple() const { return std::tie(); }
|
||||||
|
auto as_tuple() { return std::tie(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LogEntry {
|
||||||
|
uint32_t timestamp_us;
|
||||||
|
std::string message;
|
||||||
|
auto as_tuple() const { return std::tie(timestamp_us, message); }
|
||||||
|
auto as_tuple() { return std::tie(timestamp_us, message); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResponseLog {
|
||||||
|
static constexpr int8_t ext_id = 7;
|
||||||
|
std::vector<LogEntry> entries;
|
||||||
|
auto as_tuple() const { return std::tie(entries); }
|
||||||
|
auto as_tuple() { return std::tie(entries); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequestTest {
|
||||||
|
static constexpr int8_t ext_id = 127;
|
||||||
|
std::string name;
|
||||||
|
auto as_tuple() const { return std::tie(name); }
|
||||||
|
auto as_tuple() { return std::tie(name); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResponseTest {
|
||||||
|
static constexpr int8_t ext_id = 126;
|
||||||
|
bool pass;
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
auto as_tuple() const { return std::tie(pass, messages); }
|
||||||
|
auto as_tuple() { return std::tie(pass, messages); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr uint8_t hash_key[8] = {};
|
static constexpr uint8_t hash_key[8] = {};
|
||||||
@@ -56,20 +94,30 @@ static constexpr uint8_t hash_key[8] = {};
|
|||||||
struct DecodedMessage {
|
struct DecodedMessage {
|
||||||
uint32_t message_id;
|
uint32_t message_id;
|
||||||
int8_t type_id;
|
int8_t type_id;
|
||||||
|
std::vector<uint8_t> payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::vector<uint8_t> pack_envelope(uint32_t message_id, const std::vector<uint8_t> &payload) {
|
inline size_t pack_envelope_into(span_writer &out, uint32_t message_id, const uint8_t *payload, size_t payload_len) {
|
||||||
uint32_t checksum = halfsiphash::hash32(payload.data(), payload.size(), hash_key);
|
uint32_t checksum = halfsiphash::hash32(payload, payload_len, hash_key);
|
||||||
msgpack::packer p;
|
uint8_t env_buf[512];
|
||||||
p.pack(Envelope{message_id, checksum, payload});
|
span_writer env_body(env_buf, sizeof(env_buf));
|
||||||
return p.get_payload();
|
msgpack::packer env_p(env_body);
|
||||||
|
env_p.pack_array(3);
|
||||||
|
env_p.pack(message_id);
|
||||||
|
env_p.pack(checksum);
|
||||||
|
env_p.pack_bin(std::span<const uint8_t>{payload, payload_len});
|
||||||
|
msgpack::packer outer(out);
|
||||||
|
outer.pack_ext(Envelope::ext_id, env_body);
|
||||||
|
return out.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline std::vector<uint8_t> encode_response(uint32_t message_id, const T &msg) {
|
inline size_t encode_response_into(span_writer &out, uint32_t message_id, const T &msg) {
|
||||||
msgpack::packer inner;
|
uint8_t inner_buf[256];
|
||||||
|
msgpack::packer inner(inner_buf, sizeof(inner_buf));
|
||||||
inner.pack(msg);
|
inner.pack(msg);
|
||||||
return pack_envelope(message_id, inner.get_payload());
|
auto &pl = inner.get_payload();
|
||||||
|
return pack_envelope_into(out, message_id, pl.data(), pl.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline msgpack::result<DecodedMessage> try_decode(const uint8_t *data, size_t len) {
|
inline msgpack::result<DecodedMessage> try_decode(const uint8_t *data, size_t len) {
|
||||||
@@ -87,10 +135,35 @@ inline msgpack::result<DecodedMessage> try_decode(const uint8_t *data, size_t le
|
|||||||
auto ext = inner.get_ext();
|
auto ext = inner.get_ext();
|
||||||
if (!ext) return std::unexpected(ext.error());
|
if (!ext) return std::unexpected(ext.error());
|
||||||
|
|
||||||
return DecodedMessage{env.message_id, std::get<0>(*ext)};
|
auto& [type_id, ext_data] = *ext;
|
||||||
|
return DecodedMessage{env.message_id, type_id,
|
||||||
|
std::vector<uint8_t>(reinterpret_cast<const uint8_t*>(ext_data.data()),
|
||||||
|
reinterpret_cast<const uint8_t*>(ext_data.data()) + ext_data.size())};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
inline msgpack::result<DecodedMessage> try_decode(const static_vector<uint8_t, N> &buf) {
|
inline msgpack::result<DecodedMessage> try_decode(const static_vector<uint8_t, N> &buf) {
|
||||||
return try_decode(buf.data(), buf.size());
|
return try_decode(buf.data(), buf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline msgpack::result<T> decode_response(const uint8_t *data, size_t len) {
|
||||||
|
msgpack::parser p(data, static_cast<int>(len));
|
||||||
|
|
||||||
|
Envelope env;
|
||||||
|
auto r = msgpack::unpack(p, env);
|
||||||
|
if (!r) return std::unexpected(r.error());
|
||||||
|
|
||||||
|
uint32_t expected = halfsiphash::hash32(env.payload.data(), env.payload.size(), hash_key);
|
||||||
|
if (env.checksum != expected) return std::unexpected(msgpack::error_code::invalid);
|
||||||
|
|
||||||
|
msgpack::parser inner(env.payload.data(), static_cast<int>(env.payload.size()));
|
||||||
|
T out;
|
||||||
|
auto r2 = msgpack::unpack(inner, out);
|
||||||
|
if (!r2) return std::unexpected(r2.error());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t encode_request_into(span_writer &out, uint32_t message_id, const auto &msg) {
|
||||||
|
return encode_response_into(out, message_id, msg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
#include "dhcp.h"
|
|
||||||
#include <span>
|
|
||||||
#include "pico/rand.h"
|
|
||||||
#include "w6300.h"
|
|
||||||
|
|
||||||
namespace dhcp_opt {
|
|
||||||
constexpr uint8_t message_type = 53;
|
|
||||||
constexpr uint8_t param_request = 55;
|
|
||||||
constexpr uint8_t end = 255;
|
|
||||||
constexpr uint8_t discover = 1;
|
|
||||||
constexpr uint8_t subnet_mask = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct __attribute__((packed)) dhcp_discover {
|
|
||||||
uint8_t op = 1;
|
|
||||||
uint8_t htype = 1;
|
|
||||||
uint8_t hlen = 6;
|
|
||||||
uint8_t hops = 0;
|
|
||||||
uint32_t xid = __builtin_bswap32(static_cast<uint32_t>(get_rand_64()));
|
|
||||||
uint16_t secs = 0;
|
|
||||||
uint16_t flags = __builtin_bswap16(0x8000);
|
|
||||||
std::array<uint8_t, 4> ciaddr = {};
|
|
||||||
std::array<uint8_t, 4> yiaddr = {};
|
|
||||||
std::array<uint8_t, 4> siaddr = {};
|
|
||||||
std::array<uint8_t, 4> giaddr = {};
|
|
||||||
std::array<uint8_t, 16> chaddr = {};
|
|
||||||
std::array<uint8_t, 64> sname = {};
|
|
||||||
std::array<uint8_t, 128> file = {};
|
|
||||||
std::array<uint8_t, 4> magic = {99, 130, 83, 99};
|
|
||||||
uint8_t opt_msg_type[3] = {dhcp_opt::message_type, 1, dhcp_opt::discover};
|
|
||||||
uint8_t opt_params[3] = {dhcp_opt::param_request, 1, dhcp_opt::subnet_mask};
|
|
||||||
uint8_t opt_end = dhcp_opt::end;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(dhcp_discover) == 247);
|
|
||||||
|
|
||||||
static void send_discover(timer_queue& timers, const std::array<uint8_t, 6>& mac) {
|
|
||||||
auto sn = w6300::socket_id{0};
|
|
||||||
w6300::open_socket(sn, w6300::protocol::udp, w6300::port_num{68}, w6300::sock_flag::none);
|
|
||||||
|
|
||||||
dhcp_discover pkt;
|
|
||||||
std::copy(mac.begin(), mac.end(), pkt.chaddr.begin());
|
|
||||||
|
|
||||||
w6300::ip_address broadcast = {};
|
|
||||||
broadcast.ip = {255, 255, 255, 255};
|
|
||||||
broadcast.len = 4;
|
|
||||||
|
|
||||||
w6300::sendto(sn, std::span{reinterpret_cast<uint8_t*>(&pkt), sizeof(pkt)}, broadcast, w6300::port_num{67});
|
|
||||||
|
|
||||||
timers.schedule_ms(5000, [&timers, mac]() { send_discover(timers, mac); });
|
|
||||||
}
|
|
||||||
|
|
||||||
void dhcp_start(timer_queue& timers, const std::array<uint8_t, 6>& mac) {
|
|
||||||
send_discover(timers, mac);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#include "dispatch.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "wire.h"
|
||||||
|
#include "usb_cdc.h"
|
||||||
|
#include "timer_queue.h"
|
||||||
|
#include "net.h"
|
||||||
|
#include "debug_log.h"
|
||||||
|
#include "hardware/sync.h"
|
||||||
|
|
||||||
|
static timer_queue timers;
|
||||||
|
|
||||||
|
void dispatch_init() {
|
||||||
|
tusb_init();
|
||||||
|
net_init();
|
||||||
|
dlog("dispatch_init complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch_schedule_ms(uint32_t ms, std::function<void()> fn) {
|
||||||
|
timers.schedule_ms(ms, std::move(fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void dispatch_run(std::span<const handler_entry> handlers) {
|
||||||
|
std::unordered_map<int8_t, handler_fn> handler_map;
|
||||||
|
for (auto& entry : handlers) {
|
||||||
|
handler_map[entry.type_id] = entry.handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
static usb_cdc usb;
|
||||||
|
static static_vector<uint8_t, 256> usb_rx_buf;
|
||||||
|
static uint8_t tx_buf[1514];
|
||||||
|
|
||||||
|
net_set_handler([&](std::span<const uint8_t> payload, span_writer &out) -> size_t {
|
||||||
|
auto msg = try_decode(payload.data(), payload.size());
|
||||||
|
if (!msg) return 0;
|
||||||
|
auto it = handler_map.find(msg->type_id);
|
||||||
|
if (it == handler_map.end()) return 0;
|
||||||
|
return it->second(msg->message_id, msg->payload, out);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
uint32_t save = save_and_disable_interrupts();
|
||||||
|
|
||||||
|
dlog_if_slow("tud_task", 1000, [&]{ tud_task(); });
|
||||||
|
dlog_if_slow("drain", 1000, [&]{ usb.drain(); });
|
||||||
|
dlog_if_slow("timers", 1000, [&]{ timers.run(); });
|
||||||
|
dlog_if_slow("net_poll", 1000, [&]{ net_poll(); });
|
||||||
|
|
||||||
|
while (tud_cdc_available()) {
|
||||||
|
uint8_t byte;
|
||||||
|
if (tud_cdc_read(&byte, 1) != 1) break;
|
||||||
|
|
||||||
|
usb_rx_buf.push_back(byte);
|
||||||
|
|
||||||
|
auto msg = try_decode(usb_rx_buf);
|
||||||
|
if (!msg) {
|
||||||
|
if (usb_rx_buf.full()) usb_rx_buf.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_rx_buf.clear();
|
||||||
|
|
||||||
|
auto it = handler_map.find(msg->type_id);
|
||||||
|
if (it != handler_map.end()) {
|
||||||
|
span_writer out(tx_buf, sizeof(tx_buf));
|
||||||
|
size_t resp_len = it->second(msg->message_id, msg->payload, out);
|
||||||
|
if (resp_len > 0) {
|
||||||
|
if (resp_len > usb.tx.free()) {
|
||||||
|
span_writer err_out(tx_buf, sizeof(tx_buf));
|
||||||
|
size_t err_len = encode_response_into(err_out, msg->message_id,
|
||||||
|
DeviceError{2, "response too large: " + std::to_string(resp_len)});
|
||||||
|
usb.send(std::span<const uint8_t>{tx_buf, err_len});
|
||||||
|
} else {
|
||||||
|
usb.send(std::span<const uint8_t>{tx_buf, resp_len});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__wfi();
|
||||||
|
restore_interrupts(save);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#include "handlers.h"
|
||||||
|
#include "pico/unique_id.h"
|
||||||
|
#include "pico/bootrom.h"
|
||||||
|
#include "dispatch.h"
|
||||||
|
#include "net.h"
|
||||||
|
#include "debug_log.h"
|
||||||
|
|
||||||
|
size_t handle_picoboot(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
|
||||||
|
dispatch_schedule_ms(100, []{ reset_usb_boot(0, 1); });
|
||||||
|
return encode_response_into(out, message_id, ResponsePICOBOOT{});
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t handle_info(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
|
||||||
|
ResponseInfo resp;
|
||||||
|
pico_unique_board_id_t uid;
|
||||||
|
pico_get_unique_board_id(&uid);
|
||||||
|
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
|
||||||
|
auto& ns = net_get_state();
|
||||||
|
resp.mac = ns.mac;
|
||||||
|
resp.ip = ns.ip;
|
||||||
|
resp.firmware_name = firmware_name;
|
||||||
|
return encode_response_into(out, message_id, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t handle_log(uint32_t message_id, std::span<const uint8_t>, span_writer &out) {
|
||||||
|
ResponseLog resp;
|
||||||
|
for (auto& e : dlog_drain())
|
||||||
|
resp.entries.push_back(LogEntry{e.timestamp_us, std::move(e.message)});
|
||||||
|
return encode_response_into(out, message_id, resp);
|
||||||
|
}
|
||||||
+286
-9
@@ -1,24 +1,301 @@
|
|||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
#include <cstring>
|
||||||
#include "pico/unique_id.h"
|
#include "pico/unique_id.h"
|
||||||
|
#include "pico/time.h"
|
||||||
#include "w6300.h"
|
#include "w6300.h"
|
||||||
|
#include "debug_log.h"
|
||||||
|
|
||||||
|
using mac_addr = std::array<uint8_t, 6>;
|
||||||
|
using ip4_addr = std::array<uint8_t, 4>;
|
||||||
|
|
||||||
|
struct __attribute__((packed)) eth_header {
|
||||||
|
mac_addr dst;
|
||||||
|
mac_addr src;
|
||||||
|
uint16_t ethertype;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(eth_header) == 14);
|
||||||
|
|
||||||
|
struct __attribute__((packed)) arp_packet {
|
||||||
|
eth_header eth;
|
||||||
|
uint16_t htype;
|
||||||
|
uint16_t ptype;
|
||||||
|
uint8_t hlen;
|
||||||
|
uint8_t plen;
|
||||||
|
uint16_t oper;
|
||||||
|
mac_addr sha;
|
||||||
|
ip4_addr spa;
|
||||||
|
mac_addr tha;
|
||||||
|
ip4_addr tpa;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(arp_packet) == 42);
|
||||||
|
|
||||||
|
struct __attribute__((packed)) ipv4_header {
|
||||||
|
eth_header eth;
|
||||||
|
uint8_t ver_ihl;
|
||||||
|
uint8_t dscp_ecn;
|
||||||
|
uint16_t total_len;
|
||||||
|
uint16_t identification;
|
||||||
|
uint16_t flags_frag;
|
||||||
|
uint8_t ttl;
|
||||||
|
uint8_t protocol;
|
||||||
|
uint16_t checksum;
|
||||||
|
ip4_addr src;
|
||||||
|
ip4_addr dst;
|
||||||
|
|
||||||
|
size_t ip_header_len() const { return (ver_ihl & 0x0F) * 4; }
|
||||||
|
size_t ip_total_len() const { return __builtin_bswap16(total_len); }
|
||||||
|
const uint8_t* ip_start() const { return reinterpret_cast<const uint8_t*>(&ver_ihl); }
|
||||||
|
uint8_t* ip_start() { return reinterpret_cast<uint8_t*>(&ver_ihl); }
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ipv4_header) == 34);
|
||||||
|
|
||||||
|
struct __attribute__((packed)) udp_header {
|
||||||
|
ipv4_header ip;
|
||||||
|
uint16_t src_port;
|
||||||
|
uint16_t dst_port;
|
||||||
|
uint16_t length;
|
||||||
|
uint16_t checksum;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(udp_header) == 42);
|
||||||
|
|
||||||
|
struct __attribute__((packed)) icmp_echo {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t code;
|
||||||
|
uint16_t checksum;
|
||||||
|
uint16_t id;
|
||||||
|
uint16_t seq;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(icmp_echo) == 8);
|
||||||
|
|
||||||
|
static constexpr uint16_t ETH_ARP = __builtin_bswap16(0x0806);
|
||||||
|
static constexpr uint16_t ETH_IPV4 = __builtin_bswap16(0x0800);
|
||||||
|
static constexpr uint16_t ARP_HTYPE_ETH = __builtin_bswap16(1);
|
||||||
|
static constexpr uint16_t ARP_PTYPE_IPV4 = __builtin_bswap16(0x0800);
|
||||||
|
static constexpr uint16_t ARP_OP_REQUEST = __builtin_bswap16(1);
|
||||||
|
static constexpr uint16_t ARP_OP_REPLY = __builtin_bswap16(2);
|
||||||
|
static constexpr uint16_t PICOMAP_PORT = __builtin_bswap16(28781);
|
||||||
|
static constexpr mac_addr MAC_BROADCAST = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
static constexpr ip4_addr IP_BROADCAST_ALL = {255, 255, 255, 255};
|
||||||
|
static constexpr ip4_addr IP_BROADCAST_SUBNET = {169, 254, 255, 255};
|
||||||
|
|
||||||
|
static net_state state;
|
||||||
|
static w6300::socket_id raw_socket{0};
|
||||||
|
static net_handler msg_handler;
|
||||||
|
|
||||||
|
static uint16_t ip_checksum(const void* data, size_t len) {
|
||||||
|
auto p = static_cast<const uint8_t*>(data);
|
||||||
|
uint32_t sum = 0;
|
||||||
|
for (size_t i = 0; i < len - 1; i += 2)
|
||||||
|
sum += (p[i] << 8) | p[i + 1];
|
||||||
|
if (len & 1)
|
||||||
|
sum += p[len - 1] << 8;
|
||||||
|
while (sum >> 16)
|
||||||
|
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||||
|
return __builtin_bswap16(~sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mac_match(const mac_addr& dst) {
|
||||||
|
return dst == state.mac || dst == MAC_BROADCAST;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ip_match(const ip4_addr& dst) {
|
||||||
|
return dst == state.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ip_match_or_broadcast(const ip4_addr& dst) {
|
||||||
|
return ip_match(dst) || dst == IP_BROADCAST_ALL || dst == IP_BROADCAST_SUBNET;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_raw(const void* data, size_t len) {
|
||||||
|
dlog_if_slow("send_raw", 1000, [&]{
|
||||||
|
w6300::send(raw_socket, std::span<const uint8_t>{static_cast<const uint8_t*>(data), len});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_arp(const uint8_t* frame, size_t len) {
|
||||||
|
if (len < sizeof(arp_packet)) return;
|
||||||
|
auto& pkt = *reinterpret_cast<const arp_packet*>(frame);
|
||||||
|
|
||||||
|
if (pkt.htype != ARP_HTYPE_ETH) return;
|
||||||
|
if (pkt.ptype != ARP_PTYPE_IPV4) return;
|
||||||
|
if (pkt.hlen != 6 || pkt.plen != 4) return;
|
||||||
|
if (pkt.oper != ARP_OP_REQUEST) return;
|
||||||
|
if (!ip_match(pkt.tpa)) return;
|
||||||
|
|
||||||
|
arp_packet reply = {};
|
||||||
|
reply.eth.dst = pkt.eth.src;
|
||||||
|
reply.eth.src = state.mac;
|
||||||
|
reply.eth.ethertype = ETH_ARP;
|
||||||
|
reply.htype = ARP_HTYPE_ETH;
|
||||||
|
reply.ptype = ARP_PTYPE_IPV4;
|
||||||
|
reply.hlen = 6;
|
||||||
|
reply.plen = 4;
|
||||||
|
reply.oper = ARP_OP_REPLY;
|
||||||
|
reply.sha = state.mac;
|
||||||
|
reply.spa = state.ip;
|
||||||
|
reply.tha = pkt.sha;
|
||||||
|
reply.tpa = pkt.spa;
|
||||||
|
|
||||||
|
send_raw(&reply, sizeof(reply));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t tx_buf[1514];
|
||||||
|
|
||||||
|
static void handle_udp(const uint8_t* frame, size_t len) {
|
||||||
|
if (len < sizeof(udp_header)) return;
|
||||||
|
auto& pkt = *reinterpret_cast<const udp_header*>(frame);
|
||||||
|
|
||||||
|
if (pkt.dst_port != PICOMAP_PORT) return;
|
||||||
|
if (!ip_match_or_broadcast(pkt.ip.dst)) return;
|
||||||
|
if (!msg_handler) return;
|
||||||
|
|
||||||
|
size_t udp_len = __builtin_bswap16(pkt.length);
|
||||||
|
if (udp_len < 8) return;
|
||||||
|
if (sizeof(eth_header) + pkt.ip.ip_total_len() < sizeof(udp_header) + udp_len - 8) return;
|
||||||
|
|
||||||
|
auto* payload = frame + sizeof(udp_header);
|
||||||
|
size_t payload_len = udp_len - 8;
|
||||||
|
|
||||||
|
span_writer resp(tx_buf + sizeof(udp_header), sizeof(tx_buf) - sizeof(udp_header));
|
||||||
|
size_t resp_len = msg_handler(std::span<const uint8_t>{payload, payload_len}, resp);
|
||||||
|
if (resp_len == 0) return;
|
||||||
|
|
||||||
|
size_t ip_total = 20 + 8 + resp_len;
|
||||||
|
size_t reply_len = sizeof(eth_header) + ip_total;
|
||||||
|
|
||||||
|
auto& rip = *reinterpret_cast<ipv4_header*>(tx_buf);
|
||||||
|
rip.eth.dst = pkt.ip.eth.src;
|
||||||
|
rip.eth.src = state.mac;
|
||||||
|
rip.eth.ethertype = ETH_IPV4;
|
||||||
|
rip.ver_ihl = 0x45;
|
||||||
|
rip.dscp_ecn = 0;
|
||||||
|
rip.total_len = __builtin_bswap16(ip_total);
|
||||||
|
rip.identification = 0;
|
||||||
|
rip.flags_frag = 0;
|
||||||
|
rip.ttl = 64;
|
||||||
|
rip.protocol = 17;
|
||||||
|
rip.checksum = 0;
|
||||||
|
rip.src = state.ip;
|
||||||
|
rip.dst = pkt.ip.src;
|
||||||
|
rip.checksum = ip_checksum(rip.ip_start(), 20);
|
||||||
|
|
||||||
|
auto& rudp = *reinterpret_cast<udp_header*>(tx_buf);
|
||||||
|
rudp.src_port = PICOMAP_PORT;
|
||||||
|
rudp.dst_port = pkt.src_port;
|
||||||
|
rudp.length = __builtin_bswap16(8 + resp_len);
|
||||||
|
rudp.checksum = 0;
|
||||||
|
|
||||||
|
send_raw(tx_buf, reply_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_icmp(const uint8_t* frame, size_t len) {
|
||||||
|
auto& ip = *reinterpret_cast<const ipv4_header*>(frame);
|
||||||
|
size_t ip_hdr_len = ip.ip_header_len();
|
||||||
|
size_t ip_total = ip.ip_total_len();
|
||||||
|
|
||||||
|
if (sizeof(eth_header) + ip_total > len) return;
|
||||||
|
if (ip.protocol != 1) return;
|
||||||
|
if (!ip_match_or_broadcast(ip.dst)) return;
|
||||||
|
|
||||||
|
auto& icmp = *reinterpret_cast<const icmp_echo*>(frame + sizeof(eth_header) + ip_hdr_len);
|
||||||
|
size_t icmp_len = ip_total - ip_hdr_len;
|
||||||
|
if (icmp_len < sizeof(icmp_echo)) return;
|
||||||
|
if (icmp.type != 8) return;
|
||||||
|
|
||||||
|
uint8_t reply_buf[1514];
|
||||||
|
size_t reply_len = sizeof(eth_header) + ip_total;
|
||||||
|
if (reply_len > sizeof(reply_buf)) return;
|
||||||
|
|
||||||
|
memcpy(reply_buf, frame, reply_len);
|
||||||
|
auto& rip = *reinterpret_cast<ipv4_header*>(reply_buf);
|
||||||
|
rip.eth.dst = ip.eth.src;
|
||||||
|
rip.eth.src = state.mac;
|
||||||
|
rip.src = state.ip;
|
||||||
|
rip.dst = ip.src;
|
||||||
|
rip.ttl = 64;
|
||||||
|
rip.checksum = 0;
|
||||||
|
rip.checksum = ip_checksum(rip.ip_start(), ip_hdr_len);
|
||||||
|
|
||||||
|
auto& ricmp = *reinterpret_cast<icmp_echo*>(reply_buf + sizeof(eth_header) + ip_hdr_len);
|
||||||
|
ricmp.type = 0;
|
||||||
|
ricmp.checksum = 0;
|
||||||
|
ricmp.checksum = ip_checksum(&ricmp, icmp_len);
|
||||||
|
|
||||||
|
send_raw(reply_buf, reply_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_ipv4(const uint8_t* frame, size_t len) {
|
||||||
|
if (len < sizeof(ipv4_header)) return;
|
||||||
|
auto& ip = *reinterpret_cast<const ipv4_header*>(frame);
|
||||||
|
if ((ip.ver_ihl >> 4) != 4) return;
|
||||||
|
|
||||||
|
switch (ip.protocol) {
|
||||||
|
case 1:
|
||||||
|
handle_icmp(frame, len);
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
handle_udp(frame, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_frame(const uint8_t* frame, size_t len) {
|
||||||
|
if (len < sizeof(eth_header)) return;
|
||||||
|
auto& eth = *reinterpret_cast<const eth_header*>(frame);
|
||||||
|
|
||||||
|
if (!mac_match(eth.dst)) return;
|
||||||
|
|
||||||
|
switch (eth.ethertype) {
|
||||||
|
case ETH_ARP:
|
||||||
|
handle_arp(frame, len);
|
||||||
|
break;
|
||||||
|
case ETH_IPV4:
|
||||||
|
handle_ipv4(frame, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool net_init() {
|
bool net_init() {
|
||||||
w6300::init_spi();
|
w6300::init_spi();
|
||||||
w6300::init_critical_section();
|
|
||||||
w6300::reset();
|
w6300::reset();
|
||||||
w6300::init();
|
w6300::init();
|
||||||
if (!w6300::check()) return false;
|
if (!w6300::check()) return false;
|
||||||
|
|
||||||
pico_unique_board_id_t uid;
|
pico_unique_board_id_t uid;
|
||||||
pico_get_unique_board_id(&uid);
|
pico_get_unique_board_id(&uid);
|
||||||
w6300::net_info info = {};
|
state.mac[0] = (uid.id[0] & 0xFC) | 0x02;
|
||||||
info.mac[0] = (uid.id[0] & 0xFC) | 0x02;
|
state.mac[1] = uid.id[1];
|
||||||
info.mac[1] = uid.id[1];
|
state.mac[2] = uid.id[2];
|
||||||
info.mac[2] = uid.id[2];
|
state.mac[3] = uid.id[3];
|
||||||
info.mac[3] = uid.id[3];
|
state.mac[4] = uid.id[4];
|
||||||
info.mac[4] = uid.id[4];
|
state.mac[5] = uid.id[5];
|
||||||
info.mac[5] = uid.id[5];
|
|
||||||
w6300::init_net(info);
|
state.ip[0] = 169;
|
||||||
|
state.ip[1] = 254;
|
||||||
|
state.ip[2] = state.mac[4];
|
||||||
|
state.ip[3] = state.mac[5];
|
||||||
|
|
||||||
|
w6300::open_socket(raw_socket, w6300::protocol::macraw, w6300::sock_flag::none);
|
||||||
|
w6300::set_interrupt_mask(w6300::ik_sock_0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const net_state& net_get_state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void net_set_handler(net_handler handler) {
|
||||||
|
msg_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void net_poll() {
|
||||||
|
if (!w6300::irq_pending) return;
|
||||||
|
w6300::irq_pending = false;
|
||||||
|
w6300::clear_interrupt(w6300::ik_int_all);
|
||||||
|
if (w6300::get_socket_recv_buf(raw_socket) == 0) return;
|
||||||
|
static uint8_t rx_buf[1518];
|
||||||
|
auto result = w6300::recv(raw_socket, std::span{rx_buf});
|
||||||
|
if (!result) return;
|
||||||
|
process_frame(rx_buf, *result);
|
||||||
|
}
|
||||||
|
|||||||
+114
-50
@@ -1,58 +1,122 @@
|
|||||||
|
#include <unordered_map>
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/bootrom.h"
|
#include "pico/time.h"
|
||||||
#include "pico/unique_id.h"
|
#include "hardware/gpio.h"
|
||||||
#include "tusb.h"
|
#include "dispatch.h"
|
||||||
#include "wire.h"
|
#include "handlers.h"
|
||||||
#include "usb_cdc.h"
|
|
||||||
#include "net.h"
|
|
||||||
#include "w6300.h"
|
#include "w6300.h"
|
||||||
|
|
||||||
static usb_cdc usb;
|
static constexpr uint8_t LED_PIN = 25;
|
||||||
|
|
||||||
|
static void led_toggle() {
|
||||||
|
gpio_xor_mask(1 << LED_PIN);
|
||||||
|
dispatch_schedule_ms(1000, led_toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view firmware_name = "picomap_test";
|
||||||
|
|
||||||
|
static constexpr uint16_t PICOMAP_DISCOVERY_PORT = 28777;
|
||||||
|
|
||||||
|
static constexpr std::array<uint8_t, 4> picomap_discovery_ip = {239, 0, 112, 109};
|
||||||
|
|
||||||
|
static constexpr std::array<uint8_t, 6> picomap_discovery_mac = {
|
||||||
|
0x01, 0x00, 0x5e, 0x00, 0x70, 0x6d,
|
||||||
|
};
|
||||||
|
|
||||||
|
static w6300::socket_id test_socket{1};
|
||||||
|
|
||||||
|
static ResponseTest test_discovery() {
|
||||||
|
ResponseTest resp;
|
||||||
|
resp.pass = true;
|
||||||
|
|
||||||
|
uint8_t req_buf[256];
|
||||||
|
span_writer req_out(req_buf, sizeof(req_buf));
|
||||||
|
size_t req_len = encode_request_into(req_out, 0, RequestInfo{});
|
||||||
|
auto send_result = w6300::send(test_socket, std::span<const uint8_t>{req_buf, req_len});
|
||||||
|
if (!send_result) {
|
||||||
|
resp.pass = false;
|
||||||
|
resp.messages.push_back("send: error " + std::to_string(static_cast<int>(send_result.error())));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rx_buf[512];
|
||||||
|
|
||||||
|
auto deadline = make_timeout_time_ms(5000);
|
||||||
|
std::expected<uint16_t, w6300::sock_error> recv_result = std::unexpected(w6300::sock_error::busy);
|
||||||
|
while (get_absolute_time() < deadline) {
|
||||||
|
recv_result = w6300::recv(test_socket, std::span{rx_buf});
|
||||||
|
if (recv_result || recv_result.error() != w6300::sock_error::busy) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recv_result) {
|
||||||
|
resp.pass = false;
|
||||||
|
if (recv_result.error() == w6300::sock_error::busy) {
|
||||||
|
resp.messages.push_back("recv: timed out after 5s");
|
||||||
|
} else {
|
||||||
|
resp.messages.push_back("recv: error " + std::to_string(static_cast<int>(recv_result.error())));
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.messages.push_back("received " + std::to_string(*recv_result) + " bytes");
|
||||||
|
|
||||||
|
auto info = decode_response<ResponseInfo>(rx_buf, *recv_result);
|
||||||
|
if (!info) {
|
||||||
|
resp.pass = false;
|
||||||
|
resp.messages.push_back("decode: msgpack error " + std::to_string(static_cast<int>(info.error())));
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info->firmware_name.empty()) {
|
||||||
|
resp.pass = false;
|
||||||
|
resp.messages.push_back("firmware_name is empty");
|
||||||
|
} else {
|
||||||
|
resp.messages.push_back("firmware_name: " + info->firmware_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mac_zero = true;
|
||||||
|
for (auto b : info->mac) { if (b != 0) { mac_zero = false; break; } }
|
||||||
|
if (mac_zero) {
|
||||||
|
resp.pass = false;
|
||||||
|
resp.messages.push_back("mac is all zeros");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ip_zero = true;
|
||||||
|
for (auto b : info->ip) { if (b != 0) { ip_zero = false; break; } }
|
||||||
|
if (ip_zero) {
|
||||||
|
resp.pass = false;
|
||||||
|
resp.messages.push_back("ip is all zeros");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
using test_fn = ResponseTest (*)();
|
||||||
|
|
||||||
|
static const std::unordered_map<std::string_view, test_fn> tests = {
|
||||||
|
{"discovery", test_discovery},
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t handle_test(uint32_t message_id, const RequestTest& req, span_writer &out) {
|
||||||
|
auto it = tests.find(req.name);
|
||||||
|
if (it == tests.end())
|
||||||
|
return encode_response_into(out, message_id, ResponseTest{false, {"unknown test: " + req.name}});
|
||||||
|
return encode_response_into(out, message_id, it->second());
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr handler_entry handlers[] = {
|
||||||
|
{RequestPICOBOOT::ext_id, handle_picoboot},
|
||||||
|
{RequestInfo::ext_id, handle_info},
|
||||||
|
{RequestLog::ext_id, handle_log},
|
||||||
|
{RequestTest::ext_id, typed_handler<RequestTest, handle_test>},
|
||||||
|
};
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
tusb_init();
|
dispatch_init();
|
||||||
net_init();
|
|
||||||
|
|
||||||
static static_vector<uint8_t, 256> rx_buf;
|
gpio_init(LED_PIN);
|
||||||
|
gpio_set_dir(LED_PIN, GPIO_OUT);
|
||||||
|
dispatch_schedule_ms(1000, led_toggle);
|
||||||
|
|
||||||
while (true) {
|
dispatch_run(handlers);
|
||||||
tud_task();
|
|
||||||
|
|
||||||
usb.drain();
|
|
||||||
|
|
||||||
while (tud_cdc_available()) {
|
|
||||||
uint8_t byte;
|
|
||||||
if (tud_cdc_read(&byte, 1) != 1) break;
|
|
||||||
|
|
||||||
rx_buf.push_back(byte);
|
|
||||||
|
|
||||||
auto msg = try_decode(rx_buf);
|
|
||||||
if (!msg) {
|
|
||||||
if (rx_buf.full()) rx_buf.clear();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
rx_buf.clear();
|
|
||||||
|
|
||||||
switch (msg->type_id) {
|
|
||||||
case RequestPICOBOOT::ext_id:
|
|
||||||
usb.send(encode_response(msg->message_id, ResponsePICOBOOT{}));
|
|
||||||
sleep_ms(100);
|
|
||||||
reset_usb_boot(0, 1);
|
|
||||||
break;
|
|
||||||
case RequestInfo::ext_id: {
|
|
||||||
ResponseInfo resp;
|
|
||||||
pico_unique_board_id_t uid;
|
|
||||||
pico_get_unique_board_id(&uid);
|
|
||||||
std::copy(uid.id, uid.id + 8, resp.board_id.begin());
|
|
||||||
auto ninfo = w6300::get_net_info();
|
|
||||||
resp.mac = ninfo.mac;
|
|
||||||
usb.send(encode_response(msg->message_id, resp));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__wfi();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+133
-1302
File diff suppressed because it is too large
Load Diff
+5
-221
@@ -2,266 +2,50 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <expected>
|
#include <expected>
|
||||||
#include <optional>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
namespace w6300 {
|
namespace w6300 {
|
||||||
|
|
||||||
constexpr int SOCK_COUNT = 8;
|
|
||||||
|
|
||||||
enum class socket_id : uint8_t {};
|
enum class socket_id : uint8_t {};
|
||||||
enum class port_num : uint16_t {};
|
|
||||||
|
|
||||||
enum class sock_error : int16_t {
|
enum class sock_error : int16_t {
|
||||||
busy = 0,
|
busy = 0,
|
||||||
sock_num = -1,
|
sock_num = -1,
|
||||||
sock_opt = -2,
|
|
||||||
sock_init = -3,
|
|
||||||
sock_closed = -4,
|
sock_closed = -4,
|
||||||
sock_mode = -5,
|
sock_mode = -5,
|
||||||
sock_flag = -6,
|
|
||||||
sock_status = -7,
|
|
||||||
arg = -10,
|
arg = -10,
|
||||||
port_zero = -11,
|
|
||||||
ip_invalid = -12,
|
|
||||||
timeout = -13,
|
timeout = -13,
|
||||||
data_len = -14,
|
data_len = -14,
|
||||||
buffer = -15,
|
|
||||||
fatal_packlen = -1001,
|
fatal_packlen = -1001,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class protocol : uint8_t {
|
enum class protocol : uint8_t {
|
||||||
tcp = 0x01,
|
|
||||||
udp = 0x02,
|
|
||||||
ipraw = 0x03,
|
|
||||||
macraw = 0x07,
|
macraw = 0x07,
|
||||||
tcp6 = 0x09,
|
|
||||||
udp6 = 0x0A,
|
|
||||||
ipraw6 = 0x0B,
|
|
||||||
tcp_dual = 0x0D,
|
|
||||||
udp_dual = 0x0E,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class sock_flag : uint8_t {
|
enum class sock_flag : uint8_t {
|
||||||
none = 0,
|
none = 0,
|
||||||
multi_enable = 1 << 7,
|
|
||||||
ether_own = 1 << 7,
|
|
||||||
broad_block = 1 << 6,
|
|
||||||
tcp_fpsh = 1 << 6,
|
|
||||||
tcp_nodelay = 1 << 5,
|
|
||||||
igmp_ver2 = 1 << 5,
|
|
||||||
solicit_block = 1 << 5,
|
|
||||||
ether_multi4b = 1 << 5,
|
|
||||||
uni_block = 1 << 4,
|
|
||||||
ether_multi6b = 1 << 4,
|
|
||||||
force_arp = 1 << 0,
|
|
||||||
dha_manual = 1 << 1,
|
|
||||||
io_nonblock = 1 << 3,
|
|
||||||
};
|
|
||||||
constexpr sock_flag operator|(sock_flag a, sock_flag b) {
|
|
||||||
return static_cast<sock_flag>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
|
||||||
}
|
|
||||||
constexpr uint8_t operator&(sock_flag a, sock_flag b) {
|
|
||||||
return static_cast<uint8_t>(a) & static_cast<uint8_t>(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class pack_info : uint8_t {
|
|
||||||
none = 0x00,
|
|
||||||
first = 1 << 1,
|
|
||||||
remained = 1 << 2,
|
|
||||||
completed = 1 << 3,
|
|
||||||
ipv6_lla = (1 << 7) | (1 << 4),
|
|
||||||
ipv6_multi = (1 << 7) | (1 << 5),
|
|
||||||
ipv6_allnode = (1 << 7) | (1 << 6),
|
|
||||||
ipv6 = 1 << 7,
|
|
||||||
};
|
|
||||||
constexpr pack_info operator|(pack_info a, pack_info b) {
|
|
||||||
return static_cast<pack_info>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
|
|
||||||
}
|
|
||||||
constexpr uint8_t operator&(pack_info a, pack_info b) {
|
|
||||||
return static_cast<uint8_t>(a) & static_cast<uint8_t>(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class srcv6_prefer : uint8_t {
|
|
||||||
auto_select = 0x00,
|
|
||||||
lla = 0x02,
|
|
||||||
gua = 0x03,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class tcp_sock_info : uint8_t {
|
|
||||||
mode = 1 << 2,
|
|
||||||
op = 1 << 1,
|
|
||||||
sip = 1 << 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class sock_io_mode : uint8_t {
|
|
||||||
block = 0,
|
|
||||||
nonblock = 1,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum intr_kind : uint32_t {
|
enum intr_kind : uint32_t {
|
||||||
IK_PPPOE_TERMINATED = (1 << 0), IK_DEST_UNREACH = (1 << 1), IK_IP_CONFLICT = (1 << 2),
|
ik_sock_0 = (1 << 8),
|
||||||
IK_DEST_UNREACH6 = (1 << 4), IK_WOL = (1 << 7), IK_NET_ALL = 0x97,
|
ik_int_all = 0x00FFFF97
|
||||||
IK_SOCK_0 = (1 << 8), IK_SOCK_1 = (1 << 9), IK_SOCK_2 = (1 << 10), IK_SOCK_3 = (1 << 11),
|
|
||||||
IK_SOCK_4 = (1 << 12), IK_SOCK_5 = (1 << 13), IK_SOCK_6 = (1 << 14), IK_SOCK_7 = (1 << 15),
|
|
||||||
IK_SOCK_ALL = (0xFF << 8),
|
|
||||||
IK_SOCKL_TOUT = (1 << 16), IK_SOCKL_ARP4 = (1 << 17), IK_SOCKL_PING4 = (1 << 18),
|
|
||||||
IK_SOCKL_ARP6 = (1 << 19), IK_SOCKL_PING6 = (1 << 20), IK_SOCKL_NS = (1 << 21),
|
|
||||||
IK_SOCKL_RS = (1 << 22), IK_SOCKL_RA = (1 << 23), IK_SOCKL_ALL = (0xFF << 16),
|
|
||||||
IK_INT_ALL = 0x00FFFF97
|
|
||||||
};
|
|
||||||
|
|
||||||
struct phy_conf {
|
|
||||||
uint8_t by;
|
|
||||||
uint8_t mode;
|
|
||||||
uint8_t speed;
|
|
||||||
uint8_t duplex;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ipconf_mode : uint8_t {
|
|
||||||
NETINFO_NONE = 0x00, NETINFO_STATIC_V4 = 0x01, NETINFO_STATIC_V6 = 0x02,
|
|
||||||
NETINFO_STATIC_ALL = 0x03, NETINFO_SLAAC_V6 = 0x04,
|
|
||||||
NETINFO_DHCP_V4 = 0x10, NETINFO_DHCP_V6 = 0x20, NETINFO_DHCP_ALL = 0x30
|
|
||||||
};
|
|
||||||
|
|
||||||
enum dhcp_mode : uint8_t { NETINFO_STATIC = 1, NETINFO_DHCP };
|
|
||||||
|
|
||||||
struct net_info {
|
|
||||||
std::array<uint8_t, 6> mac;
|
|
||||||
std::array<uint8_t, 4> ip;
|
|
||||||
std::array<uint8_t, 4> sn;
|
|
||||||
std::array<uint8_t, 4> gw;
|
|
||||||
std::array<uint8_t, 16> lla;
|
|
||||||
std::array<uint8_t, 16> gua;
|
|
||||||
std::array<uint8_t, 16> sn6;
|
|
||||||
std::array<uint8_t, 16> gw6;
|
|
||||||
std::array<uint8_t, 4> dns;
|
|
||||||
std::array<uint8_t, 16> dns6;
|
|
||||||
ipconf_mode ipmode;
|
|
||||||
dhcp_mode dhcp;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum netmode_type : uint32_t {
|
|
||||||
NM_IPB_V4 = (1 << 0), NM_IPB_V6 = (1 << 1), NM_WOL = (1 << 2),
|
|
||||||
NM_PB6_MULTI = (1 << 4), NM_PB6_ALLNODE = (1 << 5), NM_MR_MASK = 0x37,
|
|
||||||
NM_PPPoE = (1 << 8), NM_DHA_SELECT = (1 << 15), NM_MR2_MASK = (0x09 << 8),
|
|
||||||
NM_PB4_ALL = (1 << 16), NM_TRSTB_V4 = (1 << 17), NM_PARP_V4 = (1 << 18),
|
|
||||||
NM_UNRB_V4 = (1 << 19), NM_NET4_MASK = (0x0F << 16),
|
|
||||||
NM_PB6_ALL = (1 << 24), NM_TRSTB_V6 = (1 << 25), NM_PARP_V6 = (1 << 26),
|
|
||||||
NM_UNRB_V6 = (1 << 27), NM_NET6_MASK = (0x0F << 24),
|
|
||||||
NM_MASK_ALL = 0x0F0F0937
|
|
||||||
};
|
|
||||||
|
|
||||||
struct net_timeout {
|
|
||||||
uint8_t s_retry_cnt;
|
|
||||||
uint16_t s_time_100us;
|
|
||||||
uint8_t sl_retry_cnt;
|
|
||||||
uint16_t sl_time_100us;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ip_address {
|
|
||||||
std::array<uint8_t, 16> ip;
|
|
||||||
uint8_t len;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct prefix {
|
|
||||||
uint8_t len;
|
|
||||||
uint8_t flag;
|
|
||||||
uint32_t valid_lifetime;
|
|
||||||
uint32_t preferred_lifetime;
|
|
||||||
std::array<uint8_t, 16> prefix;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct arp_request {
|
|
||||||
ip_address destinfo;
|
|
||||||
std::array<uint8_t, 6> dha;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ping_request {
|
|
||||||
uint16_t id;
|
|
||||||
uint16_t seq;
|
|
||||||
ip_address destinfo;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void init_spi();
|
void init_spi();
|
||||||
void init_critical_section();
|
|
||||||
void reset();
|
void reset();
|
||||||
void init();
|
void init();
|
||||||
bool check();
|
bool check();
|
||||||
void init_net(const net_info& info);
|
|
||||||
|
|
||||||
void soft_reset();
|
extern volatile bool irq_pending;
|
||||||
int8_t init_buffers(std::span<const uint8_t> txsize, std::span<const uint8_t> rxsize);
|
|
||||||
void clear_interrupt(intr_kind intr);
|
void clear_interrupt(intr_kind intr);
|
||||||
intr_kind get_interrupt();
|
|
||||||
void set_interrupt_mask(intr_kind intr);
|
void set_interrupt_mask(intr_kind intr);
|
||||||
intr_kind get_interrupt_mask();
|
|
||||||
|
|
||||||
int8_t get_phy_link();
|
std::expected<socket_id, sock_error> open_socket(socket_id sn, protocol proto, sock_flag flag);
|
||||||
int8_t get_phy_power_mode();
|
|
||||||
void reset_phy();
|
|
||||||
void set_phy_conf(const phy_conf& conf);
|
|
||||||
phy_conf get_phy_conf();
|
|
||||||
phy_conf get_phy_status();
|
|
||||||
void set_phy_power_mode(uint8_t pmode);
|
|
||||||
|
|
||||||
void set_net_info(const net_info& info);
|
|
||||||
net_info get_net_info();
|
|
||||||
void set_net_mode(netmode_type mode);
|
|
||||||
netmode_type get_net_mode();
|
|
||||||
void set_timeout(const net_timeout& timeout);
|
|
||||||
net_timeout get_timeout();
|
|
||||||
|
|
||||||
int8_t send_arp(arp_request& arp);
|
|
||||||
int8_t send_ping(const ping_request& ping);
|
|
||||||
int8_t send_dad(std::span<const uint8_t, 16> ipv6);
|
|
||||||
int8_t send_slaac(prefix& pfx);
|
|
||||||
int8_t send_unsolicited();
|
|
||||||
int8_t get_prefix(prefix& pfx);
|
|
||||||
|
|
||||||
std::expected<socket_id, sock_error> open_socket(socket_id sn, protocol proto, port_num port, sock_flag flag);
|
|
||||||
std::expected<void, sock_error> close(socket_id sn);
|
|
||||||
std::expected<void, sock_error> listen(socket_id sn);
|
|
||||||
std::expected<void, sock_error> disconnect(socket_id sn);
|
|
||||||
std::expected<uint16_t, sock_error> send(socket_id sn, std::span<const uint8_t> buf);
|
std::expected<uint16_t, sock_error> send(socket_id sn, std::span<const uint8_t> buf);
|
||||||
std::expected<uint16_t, sock_error> recv(socket_id sn, std::span<uint8_t> buf);
|
std::expected<uint16_t, sock_error> recv(socket_id sn, std::span<uint8_t> buf);
|
||||||
|
|
||||||
std::expected<void, sock_error> connect(socket_id sn, const ip_address& addr, port_num port);
|
|
||||||
std::expected<uint16_t, sock_error> sendto(socket_id sn, std::span<const uint8_t> buf, const ip_address& addr, port_num port);
|
|
||||||
std::expected<uint16_t, sock_error> recvfrom(socket_id sn, std::span<uint8_t> buf, ip_address& addr, port_num& port);
|
|
||||||
|
|
||||||
std::expected<void, sock_error> set_socket_io_mode(socket_id sn, sock_io_mode mode);
|
|
||||||
sock_io_mode get_socket_io_mode(socket_id sn);
|
|
||||||
uint16_t get_socket_max_tx(socket_id sn);
|
|
||||||
uint16_t get_socket_max_rx(socket_id sn);
|
|
||||||
std::expected<void, sock_error> clear_socket_interrupt(socket_id sn, uint8_t flags);
|
|
||||||
uint8_t get_socket_interrupt(socket_id sn);
|
|
||||||
std::expected<void, sock_error> set_socket_interrupt_mask(socket_id sn, uint8_t mask);
|
|
||||||
uint8_t get_socket_interrupt_mask(socket_id sn);
|
|
||||||
std::expected<void, sock_error> set_socket_prefer(socket_id sn, srcv6_prefer pref);
|
|
||||||
srcv6_prefer get_socket_prefer(socket_id sn);
|
|
||||||
|
|
||||||
void set_socket_ttl(socket_id sn, uint8_t ttl);
|
|
||||||
uint8_t get_socket_ttl(socket_id sn);
|
|
||||||
void set_socket_tos(socket_id sn, uint8_t tos);
|
|
||||||
uint8_t get_socket_tos(socket_id sn);
|
|
||||||
void set_socket_mss(socket_id sn, uint16_t mss);
|
|
||||||
uint16_t get_socket_mss(socket_id sn);
|
|
||||||
void set_socket_dest_ip(socket_id sn, const ip_address& addr);
|
|
||||||
ip_address get_socket_dest_ip(socket_id sn);
|
|
||||||
void set_socket_dest_port(socket_id sn, port_num port);
|
|
||||||
port_num get_socket_dest_port(socket_id sn);
|
|
||||||
std::expected<void, sock_error> send_keepalive(socket_id sn);
|
|
||||||
void set_socket_keepalive_auto(socket_id sn, uint8_t interval);
|
|
||||||
uint8_t get_socket_keepalive_auto(socket_id sn);
|
|
||||||
uint16_t get_socket_send_buf(socket_id sn);
|
|
||||||
uint16_t get_socket_recv_buf(socket_id sn);
|
uint16_t get_socket_recv_buf(socket_id sn);
|
||||||
uint8_t get_socket_status(socket_id sn);
|
|
||||||
uint8_t get_socket_ext_status(socket_id sn);
|
|
||||||
uint8_t get_socket_mode(socket_id sn);
|
|
||||||
uint16_t get_socket_remain_size(socket_id sn);
|
|
||||||
pack_info get_socket_pack_info(socket_id sn);
|
|
||||||
|
|
||||||
std::optional<uint16_t> peek_socket_msg(socket_id sn, std::span<const uint8_t> submsg);
|
|
||||||
|
|
||||||
} // namespace w6300
|
} // namespace w6300
|
||||||
|
|||||||
+59
-31
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/theater/picomap/lib/halfsiphash"
|
"github.com/theater/picomap/lib/halfsiphash"
|
||||||
"github.com/theater/picomap/lib/msgpack"
|
"github.com/theater/picomap/lib/msgpack"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var HashKey = [8]byte{}
|
var HashKey = [8]byte{}
|
||||||
@@ -15,7 +14,8 @@ var HashKey = [8]byte{}
|
|||||||
type transport interface {
|
type transport interface {
|
||||||
Send(data []byte) error
|
Send(data []byte) error
|
||||||
SetReadTimeout(timeout time.Duration)
|
SetReadTimeout(timeout time.Duration)
|
||||||
Reader() io.Reader
|
Recv() (data []byte, from string, err error)
|
||||||
|
Broadcast() bool
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,51 +44,79 @@ func (c *Client) send(msg any) (uint32, error) {
|
|||||||
return id, c.transport.Send(data)
|
return id, c.transport.Send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) receive(expectedID uint32) (any, error) {
|
type Response[T any] struct {
|
||||||
c.transport.SetReadTimeout(c.timeout)
|
From string
|
||||||
dec := msgpack.NewDecoder(c.transport.Reader())
|
Value *T
|
||||||
var env Envelope
|
|
||||||
if err := dec.Decode(&env); err != nil {
|
|
||||||
return nil, fmt.Errorf("decode envelope: %w", err)
|
|
||||||
}
|
|
||||||
if env.MessageID != expectedID {
|
|
||||||
return nil, fmt.Errorf("message id mismatch: got %d, want %d", env.MessageID, expectedID)
|
|
||||||
}
|
|
||||||
expected := halfsiphash.Sum32(env.Payload, HashKey)
|
|
||||||
if env.Checksum != expected {
|
|
||||||
return nil, fmt.Errorf("checksum mismatch: got %08x, want %08x", env.Checksum, expected)
|
|
||||||
}
|
|
||||||
var inner any
|
|
||||||
if err := msgpack.Unmarshal(env.Payload, &inner); err != nil {
|
|
||||||
return nil, fmt.Errorf("decode inner: %w", err)
|
|
||||||
}
|
|
||||||
if devErr, ok := inner.(*DeviceError); ok {
|
|
||||||
return nil, devErr
|
|
||||||
}
|
|
||||||
return inner, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundTrip[T any](c *Client, req any) (*T, error) {
|
func roundTrip[T any](c *Client, req any) ([]Response[T], error) {
|
||||||
id, err := c.send(req)
|
id, err := c.send(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := c.receive(id)
|
c.transport.SetReadTimeout(c.timeout)
|
||||||
|
broadcast := c.transport.Broadcast()
|
||||||
|
var results []Response[T]
|
||||||
|
for {
|
||||||
|
data, from, err := c.transport.Recv()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var env Envelope
|
||||||
|
if err := msgpack.Unmarshal(data, &env); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if env.MessageID != id {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected := halfsiphash.Sum32(env.Payload, HashKey)
|
||||||
|
if env.Checksum != expected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var inner any
|
||||||
|
if err := msgpack.Unmarshal(env.Payload, &inner); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if devErr, ok := inner.(*DeviceError); ok {
|
||||||
|
return nil, devErr
|
||||||
|
}
|
||||||
|
if typed, ok := inner.(*T); ok {
|
||||||
|
results = append(results, Response[T]{From: from, Value: typed})
|
||||||
|
if !broadcast {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func first[T any](results []Response[T], err error) (*T, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
typed, ok := resp.(*T)
|
if len(results) == 0 {
|
||||||
if !ok {
|
return nil, fmt.Errorf("no response")
|
||||||
return nil, fmt.Errorf("unexpected response: %T", resp)
|
|
||||||
}
|
}
|
||||||
return typed, nil
|
return results[0].Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PICOBOOT() error {
|
func (c *Client) PICOBOOT() error {
|
||||||
_, err := roundTrip[ResponsePICOBOOT](c, &RequestPICOBOOT{})
|
_, err := first(roundTrip[ResponsePICOBOOT](c, &RequestPICOBOOT{}))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Info() (*ResponseInfo, error) {
|
func (c *Client) Info() (*ResponseInfo, error) {
|
||||||
|
return first(roundTrip[ResponseInfo](c, &RequestInfo{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) InfoAll() ([]Response[ResponseInfo], error) {
|
||||||
return roundTrip[ResponseInfo](c, &RequestInfo{})
|
return roundTrip[ResponseInfo](c, &RequestInfo{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Log() (*ResponseLog, error) {
|
||||||
|
return first(roundTrip[ResponseLog](c, &RequestLog{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Test(name string) (*ResponseTest, error) {
|
||||||
|
return first(roundTrip[ResponseTest](c, &RequestTest{Name: name}))
|
||||||
|
}
|
||||||
|
|||||||
+30
-8
@@ -2,9 +2,11 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"slices"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/theater/picomap/lib/msgpack"
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
"go.bug.st/serial/enumerator"
|
"go.bug.st/serial/enumerator"
|
||||||
)
|
)
|
||||||
@@ -14,17 +16,30 @@ func ListSerial() ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("enumerating ports: %w", err)
|
return nil, fmt.Errorf("enumerating ports: %w", err)
|
||||||
}
|
}
|
||||||
var result []string
|
type entry struct {
|
||||||
|
name string
|
||||||
|
serial string
|
||||||
|
}
|
||||||
|
var entries []entry
|
||||||
for _, p := range ports {
|
for _, p := range ports {
|
||||||
if p.IsUSB {
|
if p.IsUSB && p.VID == "2E8A" && strings.HasPrefix(p.Name, "/dev/cu.") {
|
||||||
result = append(result, p.Name)
|
entries = append(entries, entry{p.Name, p.SerialNumber})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
slices.SortFunc(entries, func(a, b entry) int {
|
||||||
|
return strings.Compare(a.serial, b.serial)
|
||||||
|
})
|
||||||
|
var result []string
|
||||||
|
for _, e := range entries {
|
||||||
|
result = append(result, e.name)
|
||||||
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type serialTransport struct {
|
type serialTransport struct {
|
||||||
port serial.Port
|
port serial.Port
|
||||||
|
portName string
|
||||||
|
dec *msgpack.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSerial(portName string, timeout time.Duration) (*Client, error) {
|
func NewSerial(portName string, timeout time.Duration) (*Client, error) {
|
||||||
@@ -32,7 +47,8 @@ func NewSerial(portName string, timeout time.Duration) (*Client, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("opening %s: %w", portName, err)
|
return nil, fmt.Errorf("opening %s: %w", portName, err)
|
||||||
}
|
}
|
||||||
return &Client{transport: &serialTransport{port: port}, timeout: timeout}, nil
|
t := &serialTransport{port: port, portName: portName, dec: msgpack.NewDecoder(port)}
|
||||||
|
return &Client{transport: t, timeout: timeout}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *serialTransport) Send(data []byte) error {
|
func (t *serialTransport) Send(data []byte) error {
|
||||||
@@ -44,10 +60,16 @@ func (t *serialTransport) SetReadTimeout(timeout time.Duration) {
|
|||||||
t.port.SetReadTimeout(timeout)
|
t.port.SetReadTimeout(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *serialTransport) Reader() io.Reader {
|
func (t *serialTransport) Recv() ([]byte, string, error) {
|
||||||
return t.port
|
var raw msgpack.RawMessage
|
||||||
|
if err := t.dec.Decode(&raw); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return []byte(raw), t.portName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *serialTransport) Broadcast() bool { return false }
|
||||||
|
|
||||||
func (t *serialTransport) Close() error {
|
func (t *serialTransport) Close() error {
|
||||||
return t.port.Close()
|
return t.port.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-2
@@ -7,8 +7,30 @@ type ResponsePICOBOOT struct{}
|
|||||||
|
|
||||||
type RequestInfo struct{}
|
type RequestInfo struct{}
|
||||||
type ResponseInfo struct {
|
type ResponseInfo struct {
|
||||||
BoardID [8]byte
|
BoardID [8]byte
|
||||||
MAC [6]byte
|
MAC [6]byte
|
||||||
|
IP [4]byte
|
||||||
|
FirmwareName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestLog struct{}
|
||||||
|
|
||||||
|
type LogEntry struct {
|
||||||
|
TimestampUS uint32
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseLog struct {
|
||||||
|
Entries []LogEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestTest struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseTest struct {
|
||||||
|
Pass bool
|
||||||
|
Messages []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceError struct {
|
type DeviceError struct {
|
||||||
@@ -33,4 +55,8 @@ func init() {
|
|||||||
msgpack.RegisterExt(3, (*ResponsePICOBOOT)(nil))
|
msgpack.RegisterExt(3, (*ResponsePICOBOOT)(nil))
|
||||||
msgpack.RegisterExt(4, (*RequestInfo)(nil))
|
msgpack.RegisterExt(4, (*RequestInfo)(nil))
|
||||||
msgpack.RegisterExt(5, (*ResponseInfo)(nil))
|
msgpack.RegisterExt(5, (*ResponseInfo)(nil))
|
||||||
|
msgpack.RegisterExt(6, (*RequestLog)(nil))
|
||||||
|
msgpack.RegisterExt(7, (*ResponseLog)(nil))
|
||||||
|
msgpack.RegisterExt(127, (*RequestTest)(nil))
|
||||||
|
msgpack.RegisterExt(126, (*ResponseTest)(nil))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PicomapPort = 28781
|
||||||
|
|
||||||
|
type udpTransport struct {
|
||||||
|
conn *net.UDPConn
|
||||||
|
addr *net.UDPAddr
|
||||||
|
broadcast bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func interfaceIPv4Net(name string) (net.IP, *net.IPNet, error) {
|
||||||
|
ifi, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("interface %s: %w", name, err)
|
||||||
|
}
|
||||||
|
addrs, err := ifi.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("interface %s addrs: %w", name, err)
|
||||||
|
}
|
||||||
|
for _, a := range addrs {
|
||||||
|
if ipnet, ok := a.(*net.IPNet); ok {
|
||||||
|
if ip4 := ipnet.IP.To4(); ip4 != nil {
|
||||||
|
return ip4, ipnet, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("interface %s has no IPv4 address", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
|
||||||
|
bcast := make(net.IP, 4)
|
||||||
|
ip4 := ip.To4()
|
||||||
|
for i := range 4 {
|
||||||
|
bcast[i] = ip4[i] | ^mask[i]
|
||||||
|
}
|
||||||
|
return bcast
|
||||||
|
}
|
||||||
|
|
||||||
|
func InterfaceBroadcast(name string) (string, error) {
|
||||||
|
ip, ipnet, err := interfaceIPv4Net(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return broadcastAddr(ip, ipnet.Mask).String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableBroadcast(conn *net.UDPConn) error {
|
||||||
|
raw, err := conn.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("syscall conn: %w", err)
|
||||||
|
}
|
||||||
|
var serr error
|
||||||
|
raw.Control(func(fd uintptr) {
|
||||||
|
serr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_BROADCAST, 1)
|
||||||
|
})
|
||||||
|
return serr
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInterfaceBroadcast(iface string, ip net.IP) bool {
|
||||||
|
if iface == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
localIP, ipnet, err := interfaceIPv4Net(iface)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ip.Equal(broadcastAddr(localIP, ipnet.Mask)) || ip.Equal(net.IPv4bcast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUDP(addr string, iface string, timeout time.Duration) (*Client, error) {
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", addr, PicomapPort))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve %s: %w", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var laddr *net.UDPAddr
|
||||||
|
if iface != "" {
|
||||||
|
ip, _, err := interfaceIPv4Net(iface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
laddr = &net.UDPAddr{IP: ip, Port: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.ListenUDP("udp4", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listen: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := enableBroadcast(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("SO_BROADCAST: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bcast := isInterfaceBroadcast(iface, raddr.IP)
|
||||||
|
return &Client{transport: &udpTransport{conn: conn, addr: raddr, broadcast: bcast}, timeout: timeout}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *udpTransport) Send(data []byte) error {
|
||||||
|
_, err := t.conn.WriteToUDP(data, t.addr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *udpTransport) SetReadTimeout(timeout time.Duration) {
|
||||||
|
t.conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *udpTransport) Recv() ([]byte, string, error) {
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
n, addr, err := t.conn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return buf[:n], addr.IP.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *udpTransport) Broadcast() bool { return t.broadcast }
|
||||||
|
|
||||||
|
func (t *udpTransport) Close() error {
|
||||||
|
return t.conn.Close()
|
||||||
|
}
|
||||||
@@ -3,15 +3,23 @@ package picotool
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Load(uf2Path string, serial string) error {
|
func Load(uf2Path string, serial string, timeout time.Duration) error {
|
||||||
cmd := exec.Command("picotool", "load", uf2Path, "--ser", serial)
|
deadline := time.Now().Add(timeout)
|
||||||
out, err := cmd.CombinedOutput()
|
var out []byte
|
||||||
if err != nil {
|
var err error
|
||||||
return fmt.Errorf("picotool load: %w\n%s", err, out)
|
for {
|
||||||
|
cmd := exec.Command("picotool", "load", uf2Path, "-x", "--ser", serial)
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
return fmt.Errorf("picotool load: %w\n%s", err, out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Reboot(serial string) error {
|
func Reboot(serial string) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user