serialAlox/Parser/parser.go
2025-05-29 22:24:38 +02:00

148 lines
2.9 KiB
Go

package parser
import (
"fmt"
)
type ParsedMessage struct {
typeByte byte
payloadBytes []byte
}
type ParserConfig struct {
StartByte byte
}
func NewParserConfig() *ParserConfig {
return &ParserConfig{
StartByte: 0xAA,
}
}
type ParserErrorCodes int
type ParserError struct {
Code ParserErrorCodes
Message string
}
const (
ChecksumError ParserErrorCodes = iota
)
func (e *ParserError) Error() string {
return e.Message
}
type ParserState int
const (
StateWaitForStartByte ParserState = iota
StateReadType
StateReadLength
StateReadPayload
StateCheckChecksum
)
type Parser struct {
messages []ParsedMessage
currentMessageBuffer []byte
currentMsgType byte
readLength byte
readIndex byte
parserState ParserState
parserConfig ParserConfig
checksum byte
}
func NewParser(cfg ParserConfig) *Parser {
return &Parser{
messages: []ParsedMessage{},
currentMessageBuffer: []byte{},
currentMsgType: 0,
readLength: 0,
readIndex: 0,
parserState: StateWaitForStartByte,
parserConfig: cfg,
checksum: 0,
}
}
func (pa *Parser) ParseBytes(p []byte) (n int, err error) {
for i, b := range p {
err := pa.parseByte(b)
if err != nil {
return i, err
}
}
return len(p), nil
}
func (pa *Parser) parseByte(p byte) error {
switch pa.parserState {
case StateWaitForStartByte:
if p == pa.parserConfig.StartByte {
pa.parserState = StateReadType
pa.readIndex = 0
pa.checksum = 0
}
break
case StateReadType:
pa.currentMsgType = p
pa.checksum ^= p
pa.parserState = StateReadLength
case StateReadLength:
pa.readLength = p
pa.checksum ^= p
pa.parserState = StateReadPayload
break
case StateReadPayload:
if pa.readIndex < pa.readLength {
pa.currentMessageBuffer = append(pa.currentMessageBuffer, p)
pa.checksum ^= p
pa.readIndex++
if pa.readIndex == pa.readLength {
pa.parserState = StateCheckChecksum
}
}
break
case StateCheckChecksum:
if pa.checksum != p {
return &ParserError{
Code: ChecksumError,
Message: fmt.Sprintf("Checksum does not Match, want %v, got %v", pa.checksum, p),
}
}
pm := &ParsedMessage{
typeByte: pa.currentMsgType,
payloadBytes: pa.currentMessageBuffer,
}
pa.messages = append(pa.messages, *pm)
pa.reset()
break
}
return nil
}
func (pa *Parser) reset() {
pa.currentMessageBuffer = nil
pa.currentMsgType = 0
pa.readIndex = 0
pa.readLength = 0
pa.checksum = 0
pa.parserState = StateWaitForStartByte
}
func (pa *Parser) IsMessageAvailable() bool {
return len(pa.messages) > 0
}
func (pa *Parser) GetNextMessage() (typeByte byte, payload []byte, err error) {
if !pa.IsMessageAvailable() {
return 0x00, nil, fmt.Errorf("No message available")
}
lastMessage := pa.messages[0]
pa.messages = pa.messages[1:]
return lastMessage.typeByte, lastMessage.payloadBytes, nil
}