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 } } }