From 073b78bcb33404e8079eccbbbebdbab1f639190a Mon Sep 17 00:00:00 2001 From: wisplite Date: Fri, 30 Jan 2026 06:18:25 -0600 Subject: [PATCH] upload page added + parchment properly waits for ack command --- device.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++--------- main.go | 3 ++- tui.go | 36 +++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/device.go b/device.go index 3016129..a747268 100644 --- a/device.go +++ b/device.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "time" @@ -15,6 +16,8 @@ type Device struct { Connected bool } +var commandAck = []byte{0xAA, 0x06, 0x00, 0x00, 0x00} + // NewDevice creates a new Device instance. func NewDevice() *Device { return &Device{} @@ -51,18 +54,16 @@ func (d *Device) Connect() error { d.Port = port d.Connected = true + _ = d.Port.SetReadTimeout(100 * time.Millisecond) // Send dev mode request // 0xAA 0x04 0x00 0x00 0x00 is the command sequence for dev mode - _, err = d.Port.Write([]byte{0xAA, 0x04, 0x00, 0x00, 0x00}) + err = d.sendCommand([]byte{0xAA, 0x04, 0x00, 0x00, 0x00}) if err != nil { d.Close() - return fmt.Errorf("failed to send dev mode request: %w", err) + return fmt.Errorf("failed to enter dev mode: %w", err) } - // Allow some time for the device to respond or settle if needed - time.Sleep(100 * time.Millisecond) - return nil } @@ -73,14 +74,10 @@ func (d *Device) ExitDevMode() error { } // disable dev mode - _, err := d.Port.Write([]byte{0xAA, 0x05, 0x00, 0x00, 0x00}) - if err != nil { - return fmt.Errorf("failed to send exit dev mode command: %w", err) + if err := d.sendCommand([]byte{0xAA, 0x05, 0x00, 0x00, 0x00}); err != nil { + return fmt.Errorf("failed to exit dev mode: %w", err) } - // Allow some time for the device to respond or settle if needed - time.Sleep(100 * time.Millisecond) - return nil } @@ -108,3 +105,57 @@ func (d *Device) Write(data []byte) (int, error) { } return d.Port.Write(data) } + +func (d *Device) sendCommand(cmd []byte) error { + if d.Port == nil { + return fmt.Errorf("port not open") + } + if _, err := d.Port.Write(cmd); err != nil { + return fmt.Errorf("failed to send command: %w", err) + } + if err := d.waitForResponse(commandAck, 2*time.Second); err != nil { + return err + } + return nil +} + +func (d *Device) waitForResponse(expected []byte, timeout time.Duration) error { + if d.Port == nil { + return fmt.Errorf("port not open") + } + if len(expected) == 0 { + return fmt.Errorf("expected response is empty") + } + + deadline := time.NewTimer(timeout) + defer deadline.Stop() + + readBuf := make([]byte, 64) + window := make([]byte, 0, len(expected)*4) + maxWindow := len(expected) * 4 + if maxWindow < 64 { + maxWindow = 64 + } + + for { + select { + case <-deadline.C: + return fmt.Errorf("timeout waiting for response %x", expected) + default: + n, err := d.Port.Read(readBuf) + if err != nil { + return fmt.Errorf("failed while waiting for response: %w", err) + } + if n == 0 { + continue + } + window = append(window, readBuf[:n]...) + if len(window) > maxWindow { + window = window[len(window)-maxWindow:] + } + if bytes.Contains(window, expected) { + return nil + } + } + } +} diff --git a/main.go b/main.go index 28f4ac4..f62ef85 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ func MonitorDevice(ctx context.Context, app *tview.Application, frame **tview.Fr err = device.Connect() if err != nil { UpdateDeviceStatus(app, *frame, state, "Error connecting to cardputer: "+err.Error(), tcell.ColorRed) + SetError(state, err.Error()) } else { UpdateDeviceStatus(app, *frame, state, "Connected to cardputer: "+port, tcell.ColorGreen) if state.Error == "Cardputer disconnected" { @@ -69,7 +70,7 @@ func main() { Page: "home", Error: "", } - frame := AppUI(app, ¤tFrame, state) + frame := AppUI(app, ¤tFrame, state, device) currentFrame = frame app.SetRoot(frame, true) diff --git a/tui.go b/tui.go index 1342789..baa68a7 100644 --- a/tui.go +++ b/tui.go @@ -4,6 +4,7 @@ import ( "os" "github.com/gdamore/tcell/v2" + "github.com/navidys/tvxwidgets" "github.com/rivo/tview" ) @@ -13,15 +14,26 @@ type UIState struct { Success string } -func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame { +func AppUI(app *tview.Application, currentFrame **tview.Frame, state *UIState, device *Device) *tview.Frame { list := tview.NewList() list.AddItem("CPkg Tools", "Create and unpack cpkg files for distribution", 'c', func() { - newFrame := PackageUI(app, currentFrame, state) + newFrame := PackageUI(app, currentFrame, state, device) *currentFrame = newFrame state.Page = "package" app.SetRoot(newFrame, true) }) - list.AddItem("Upload to Cardputer", "Creates a package and uploads it to the connected Cardputer", 'u', nil) + list.AddItem("Upload to Cardputer", "Creates a package and uploads it to the connected Cardputer", 'u', func() { + if !device.Connected { + SetError(state, "No cardputer connected") + return + } else { + SetSuccess(state, "Upload started") + } + newFrame := UploadUI(app, currentFrame, state, device) + *currentFrame = newFrame + state.Page = "upload" + app.SetRoot(newFrame, true) + }) list.AddItem("Live Refresh", "Monitors for changes and automatically uploads to the Cardputer (WIP)", 'l', nil) frame := tview.NewFrame(list) frame.SetBorders(0, 0, 0, 0, 0, 0) @@ -41,7 +53,19 @@ func SetSuccess(state *UIState, text string) { state.Success = text } -func PackageUI(app *tview.Application, currentFrame **tview.Frame, state *UIState) *tview.Frame { +func UploadUI(app *tview.Application, currentFrame **tview.Frame, state *UIState, device *Device) *tview.Frame { + list := tview.NewFlex() + list.SetDirection(tview.FlexRow) + progress := tvxwidgets.NewPercentageModeGauge() + progress.SetValue(0) + progress.SetMaxValue(100) + progress.SetBorder(true) + progress.SetTitle("Upload Progress") + list.AddItem(progress, 0, 1, false) + return tview.NewFrame(list) +} + +func PackageUI(app *tview.Application, currentFrame **tview.Frame, state *UIState, device *Device) *tview.Frame { list := tview.NewList() list.AddItem("Create Package", "Creates a .cpkg file in the running directory", 'c', func() { fsys := os.DirFS(".") @@ -63,7 +87,7 @@ func PackageUI(app *tview.Application, currentFrame **tview.Frame, state *UIStat }) list.AddItem("Unpack Package", "Unpacks a chosen .cpkg file into a subdirectory", 'u', nil) list.AddItem("Back", "Go back to the home page", 'b', func() { - newFrame := AppUI(app, currentFrame, state) + newFrame := AppUI(app, currentFrame, state, device) *currentFrame = newFrame state.Page = "home" app.SetRoot(newFrame, true) @@ -83,6 +107,8 @@ func UpdateDeviceStatus(app *tview.Application, frame *tview.Frame, state *UISta frame.AddText("Parchment - v0.0.1", true, tview.AlignLeft, tcell.ColorWhite) case "package": frame.AddText("Package Tools", true, tview.AlignLeft, tcell.ColorWhite) + case "upload": + frame.AddText("Uploading to Cardputer - Do not disconnect the cardputer until the upload is complete", true, tview.AlignLeft, tcell.ColorWhite) } if state.Error != "" { frame.AddText(state.Error, false, tview.AlignRight, tcell.ColorRed)