143 lines
4.0 KiB
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
|
|
}
|