package telegram import ( "io" "regexp" "strings" "sync" ) const ( endTag = "NNNN" expression = "(?s)ZCZC.*?NNNN" maxBufferSize = 65536 bufferTrimSize = 32768 ) // Parser handles telegram extraction from raw serial data. type Parser struct { mu sync.Mutex buffer strings.Builder exp *regexp.Regexp rawLog io.Writer } // New creates a new Parser. func New(rawLog io.Writer) *Parser { return &Parser{ exp: regexp.MustCompile(expression), rawLog: rawLog, } } // Append adds raw data to the internal buffer and returns any complete // telegrams that were found. Returns nil if no complete telegram is ready. func (p *Parser) Append(data string) []string { p.mu.Lock() defer p.mu.Unlock() if p.buffer.Len() > maxBufferSize { // Truncate to prevent unbounded growth existing := p.buffer.String() p.buffer.Reset() p.buffer.WriteString(existing[len(existing)-bufferTrimSize:]) } p.buffer.WriteString(data) p.buffer.WriteByte('\n') return p.extract() } // extract finds and returns all complete telegrams in the buffer. func (p *Parser) extract() []string { content := p.buffer.String() if !strings.Contains(content, endTag) { return nil } loc := p.exp.FindStringIndex(content) if loc == nil { return nil } telegram := content[loc[0]:loc[1]] telegram = removeEmpty(telegram) + "\n\n\n" // Write raw log if p.rawLog != nil { p.rawLog.Write([]byte(telegram + "\n")) } // Keep remaining data after the matched telegram remaining := content[loc[1]:] p.buffer.Reset() p.buffer.WriteString(remaining) return []string{telegram} } func removeEmpty(s string) string { return regexp.MustCompile(`[\t\r\n]+`).ReplaceAllString(strings.TrimSpace(s), "\n") }