package uart import ( "errors" "fmt" "io" "time" ) const ( StartMarker = 0xAA StopMarker = 0xCC // Must match main/uart.h MAX_PAYLOAD_SIZE. MaxPayload = 1024 // AA | len_lo | len_hi | payload | xor | CC FrameOverhead = 5 ) var ( ErrInvalidFrame = errors.New("invalid uart frame") ErrTimeout = errors.New("read timeout") ) // EncodeFrame builds a framed packet: 0xAA len_lo len_hi 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) } n := len(payload) frame := make([]byte, 0, FrameOverhead+n) frame = append(frame, StartMarker, byte(n&0xff), byte((n>>8)&0xff)) 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 lenLo byte payload []byte index int checksum byte } const ( stateStart = iota stateLenLo stateLenHi 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 = stateLenLo } case stateLenLo: p.lenLo = b p.state = stateLenHi case stateLenHi: n := int(p.lenLo) | (int(b) << 8) if n == 0 || n > MaxPayload { p.state = stateStart } else { p.len = n p.payload = make([]byte, p.len) p.index = 0 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. // maxWait bounds total wait time; zero means no limit (serial read timeouts retry forever). func ReadFrame(r io.Reader, buf []byte, maxWait time.Duration) ([]byte, error) { if buf == nil { buf = make([]byte, 1024) } parser := NewParser() var deadline time.Time if maxWait > 0 { deadline = time.Now().Add(maxWait) } for { if !deadline.IsZero() && !time.Now().Before(deadline) { return nil, ErrTimeout } 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 } } }