Add goTool web dashboard with UART auto-reconnect.
Serve polls the master over UART and pushes live state via WebSocket; reopens the serial port when the device is unplugged and comes back. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
-5
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.bug.st/serial"
|
||||
@@ -12,7 +13,9 @@ import (
|
||||
const readTimeout = 3 * time.Second
|
||||
|
||||
type serialPort struct {
|
||||
port serial.Port
|
||||
port serial.Port
|
||||
mu sync.Mutex
|
||||
quiet bool
|
||||
}
|
||||
|
||||
func openSerial(portName string, baud int) (*serialPort, error) {
|
||||
@@ -39,6 +42,12 @@ func (s *serialPort) Close() error {
|
||||
}
|
||||
|
||||
func (s *serialPort) exchangePayload(payload []byte, cmdName string) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.exchangePayloadLocked(payload, cmdName)
|
||||
}
|
||||
|
||||
func (s *serialPort) exchangePayloadLocked(payload []byte, cmdName string) ([]byte, error) {
|
||||
if len(payload) == 0 {
|
||||
return nil, fmt.Errorf("empty payload")
|
||||
}
|
||||
@@ -47,7 +56,9 @@ func (s *serialPort) exchangePayload(payload []byte, cmdName string) ([]byte, er
|
||||
return nil, fmt.Errorf("encode frame: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("sending %s command (%d bytes): % x", cmdName, len(frame), frame)
|
||||
if !s.quiet {
|
||||
log.Printf("sending %s command (%d bytes): % x", cmdName, len(frame), frame)
|
||||
}
|
||||
if _, err := s.port.Write(frame); err != nil {
|
||||
return nil, fmt.Errorf("write: %w", err)
|
||||
}
|
||||
@@ -57,7 +68,9 @@ func (s *serialPort) exchangePayload(payload []byte, cmdName string) ([]byte, er
|
||||
return nil, fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("response payload (%d bytes): % x", len(respPayload), respPayload)
|
||||
if !s.quiet {
|
||||
log.Printf("response payload (%d bytes): % x", len(respPayload), respPayload)
|
||||
}
|
||||
if len(respPayload) == 0 {
|
||||
return nil, fmt.Errorf("empty response payload")
|
||||
}
|
||||
@@ -65,12 +78,20 @@ func (s *serialPort) exchangePayload(payload []byte, cmdName string) ([]byte, er
|
||||
}
|
||||
|
||||
func (s *serialPort) exchange(cmdID byte, cmdName string) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.exchangeLocked(cmdID, cmdName)
|
||||
}
|
||||
|
||||
func (s *serialPort) exchangeLocked(cmdID byte, cmdName string) ([]byte, error) {
|
||||
frame, err := uartframe.EncodeFrame([]byte{cmdID})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode frame: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("sending %s command (%d bytes): % x", cmdName, len(frame), frame)
|
||||
if !s.quiet {
|
||||
log.Printf("sending %s command (%d bytes): % x", cmdName, len(frame), frame)
|
||||
}
|
||||
if _, err := s.port.Write(frame); err != nil {
|
||||
return nil, fmt.Errorf("write: %w", err)
|
||||
}
|
||||
@@ -80,7 +101,9 @@ func (s *serialPort) exchange(cmdID byte, cmdName string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("read response: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("response payload (%d bytes): % x", len(payload), payload)
|
||||
if !s.quiet {
|
||||
log.Printf("response payload (%d bytes): % x", len(payload), payload)
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return nil, fmt.Errorf("empty response payload")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user