Add Go UART tool to query firmware VERSION over serial.
Framed protobuf client for /dev/ttyUSB0 at 921600 baud with generated uart_messages types matching the device protocol. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package uart
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
StartMarker = 0xAA
|
||||
StopMarker = 0xCC
|
||||
MaxPayload = 252
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidFrame = errors.New("invalid uart frame")
|
||||
ErrTimeout = errors.New("read timeout")
|
||||
)
|
||||
|
||||
// EncodeFrame builds a framed packet: 0xAA len payload xor(payload) 0xCC.
|
||||
func EncodeFrame(payload []byte) ([]byte, error) {
|
||||
if len(payload) == 0 || len(payload) > MaxPayload {
|
||||
return nil, fmt.Errorf("payload length %d out of range 1..%d", len(payload), MaxPayload)
|
||||
}
|
||||
|
||||
frame := make([]byte, 0, 4+len(payload))
|
||||
frame = append(frame, StartMarker, byte(len(payload)))
|
||||
var checksum byte
|
||||
for _, b := range payload {
|
||||
frame = append(frame, b)
|
||||
checksum ^= b
|
||||
}
|
||||
frame = append(frame, checksum, StopMarker)
|
||||
return frame, nil
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
state int
|
||||
len int
|
||||
payload []byte
|
||||
index int
|
||||
checksum byte
|
||||
}
|
||||
|
||||
const (
|
||||
stateStart = iota
|
||||
stateLen
|
||||
stateData
|
||||
stateChecksum
|
||||
stateStop
|
||||
)
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{state: stateStart}
|
||||
}
|
||||
|
||||
// Feed ingests one byte. ok is true when a complete frame is ready.
|
||||
func (p *Parser) Feed(b byte) (payload []byte, ok bool, err error) {
|
||||
switch p.state {
|
||||
case stateStart:
|
||||
if b == StartMarker {
|
||||
p.index = 0
|
||||
p.checksum = 0
|
||||
p.state = stateLen
|
||||
}
|
||||
case stateLen:
|
||||
if b == 0 || b > MaxPayload {
|
||||
p.state = stateStart
|
||||
} else {
|
||||
p.len = int(b)
|
||||
p.payload = make([]byte, p.len)
|
||||
p.state = stateData
|
||||
}
|
||||
case stateData:
|
||||
p.payload[p.index] = b
|
||||
p.checksum ^= b
|
||||
p.index++
|
||||
if p.index >= p.len {
|
||||
p.state = stateChecksum
|
||||
}
|
||||
case stateChecksum:
|
||||
if b == p.checksum {
|
||||
p.state = stateStop
|
||||
} else {
|
||||
p.state = stateStart
|
||||
return nil, false, ErrInvalidFrame
|
||||
}
|
||||
case stateStop:
|
||||
p.state = stateStart
|
||||
if b == StopMarker {
|
||||
out := make([]byte, len(p.payload))
|
||||
copy(out, p.payload)
|
||||
return out, true, nil
|
||||
}
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// ReadFrame reads bytes from r until one full frame is parsed or an error occurs.
|
||||
func ReadFrame(r io.Reader, buf []byte) ([]byte, error) {
|
||||
if buf == nil {
|
||||
buf = make([]byte, 256)
|
||||
}
|
||||
parser := NewParser()
|
||||
|
||||
for {
|
||||
n, err := r.Read(buf)
|
||||
if n > 0 {
|
||||
for i := 0; i < n; i++ {
|
||||
payload, ok, perr := parser.Feed(buf[i])
|
||||
if perr != nil {
|
||||
return nil, perr
|
||||
}
|
||||
if ok {
|
||||
return payload, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, ErrInvalidFrame
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user