Files
PrintControl/go/internal/printer/parse.go

143 lines
4.0 KiB
Go

package printer
import (
"regexp"
"strconv"
"strings"
)
var (
tempRe = regexp.MustCompile(`(T\d*|B|C):\s*(-?\d+(?:\.\d+)?)\s*/\s*(-?\d+(?:\.\d+)?)`)
sdRe = regexp.MustCompile(`(?i)SD printing byte\s+(\d+)\s*/\s*(\d+)`)
notSDRe = regexp.MustCompile(`(?i)Not SD printing`)
sdDoneRe = regexp.MustCompile(`(?i)Done printing file`)
fileOpen = regexp.MustCompile(`(?i)File opened:\s*(\S+)`)
sdListStart = regexp.MustCompile(`(?i)^Begin file list`)
sdListEnd = regexp.MustCompile(`(?i)^End file list`)
sdListLongRe = regexp.MustCompile(`"([^"]+)"`)
)
// Parse one line of printer output and mutate s. Returns true if anything changed.
func (s *State) Parse(line string) bool {
line = strings.TrimSpace(line)
if line == "" {
return false
}
changed := false
for _, m := range tempRe.FindAllStringSubmatch(line, -1) {
tool := m[1]
cur, _ := strconv.ParseFloat(m[2], 64)
tgt, _ := strconv.ParseFloat(m[3], 64)
if s.Temps == nil {
s.Temps = map[string]TempPair{}
}
s.Temps[tool] = TempPair{Current: cur, Target: tgt}
changed = true
}
if s.Firmware == "" {
if idx := strings.Index(line, "FIRMWARE_NAME:"); idx >= 0 {
rest := line[idx+len("FIRMWARE_NAME:"):]
// Cut at the next CAPS_TOKEN: marker that Marlin emits after FIRMWARE_NAME
for _, sep := range []string{" SOURCE_CODE_URL:", " PROTOCOL_VERSION:", " MACHINE_TYPE:", " EXTRUDER_COUNT:"} {
if i := strings.Index(rest, sep); i >= 0 {
rest = rest[:i]
}
}
s.Firmware = strings.TrimSpace(rest)
if len(s.Firmware) > 48 {
s.Firmware = s.Firmware[:48]
}
if s.Firmware != "" {
changed = true
}
}
}
if m := fileOpen.FindStringSubmatch(line); m != nil {
s.SDFilename = m[1]
changed = true
}
// SD card directory listing (M20). The firmware emits "Begin file list",
// one entry per line, then "End file list". While we're inside that
// block, every non-marker line is a file entry; we accumulate into
// SDFiles. When the block ends we flip SDListing off and leave the
// completed slice in place for the UI to consume.
if sdListStart.MatchString(line) {
s.SDListing = true
s.SDFiles = s.SDFiles[:0]
changed = true
} else if sdListEnd.MatchString(line) {
if s.SDListing {
s.SDListing = false
changed = true
}
} else if s.SDListing {
if f, ok := parseSDFileEntry(line); ok {
s.SDFiles = append(s.SDFiles, f)
changed = true
}
}
if m := sdRe.FindStringSubmatch(line); m != nil {
byteN, _ := strconv.ParseInt(m[1], 10, 64)
total, _ := strconv.ParseInt(m[2], 10, 64)
s.SDByte = byteN
s.SDTotal = total
if byteN > 0 && total > 0 {
if s.PrintState != StateSDPrinting && s.PrintState != StatePaused {
s.PrintState = StateSDPrinting
if s.SDStart.IsZero() {
s.SDStart = nowFn()
}
} else if s.SDStart.IsZero() {
s.SDStart = nowFn()
}
}
changed = true
} else if notSDRe.MatchString(line) {
if s.PrintState == StateSDPrinting {
s.PrintState = StateIdle
s.SDByte, s.SDTotal = 0, 0
s.SDStart = zeroTime
changed = true
}
} else if sdDoneRe.MatchString(line) {
s.PrintState = StateIdle
s.SDByte = s.SDTotal
changed = true
}
return changed
}
// parseSDFileEntry tries to read a single line from inside the
// "Begin file list" / "End file list" block. Marlin emits
// "FILENAME.GCO 123456" or "FILENAME.GCO 123456 \"Long Name.gcode\"".
// Directories are reported with a trailing "/" — we surface them as
// entries too so the user can spot them, even though M23 won't enter them.
func parseSDFileEntry(line string) (SDFile, bool) {
line = strings.TrimSpace(line)
if line == "" {
return SDFile{}, false
}
// Strip a trailing long-filename if present, capture it.
long := ""
if m := sdListLongRe.FindStringSubmatch(line); m != nil {
long = m[1]
line = strings.TrimSpace(line[:strings.Index(line, "\"")])
}
fields := strings.Fields(line)
if len(fields) == 0 {
return SDFile{}, false
}
name := fields[0]
var size int64
if len(fields) >= 2 {
size, _ = strconv.ParseInt(fields[len(fields)-1], 10, 64)
}
return SDFile{Name: name, LongName: long, Size: size}, true
}