|
|
@ -0,0 +1,331 @@ |
|
|
|
package main |
|
|
|
|
|
|
|
import ( |
|
|
|
"bufio" |
|
|
|
"bytes" |
|
|
|
"encoding/json" |
|
|
|
"flag" |
|
|
|
"io" |
|
|
|
"log" |
|
|
|
"math/rand" |
|
|
|
"net" |
|
|
|
"regexp" |
|
|
|
"strings" |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
|
|
|
|
"github.com/tarm/serial" |
|
|
|
) |
|
|
|
|
|
|
|
func main() { |
|
|
|
var ( |
|
|
|
port = flag.String("port", "/dev/ttyS0", "serial port") |
|
|
|
baud = flag.Int("rate", 115200, "serial baud rate") |
|
|
|
socket = flag.String("socket", "/tmp/klippy_uds", "klipper socket") |
|
|
|
) |
|
|
|
flag.Parse() |
|
|
|
|
|
|
|
log.Println("connecting to klipper at", *socket) |
|
|
|
last := time.Now() |
|
|
|
klipper, err := openKlipper(*socket) |
|
|
|
if err != nil { |
|
|
|
log.Fatalln(err) |
|
|
|
} |
|
|
|
defer klipper.Close() |
|
|
|
log.Println("connected to klipper in", time.Since(last)) |
|
|
|
|
|
|
|
log.Println("connecting to TFT at", *port, "using", *baud, "baud") |
|
|
|
last = time.Now() |
|
|
|
screen, err := openTFT(*port, *baud) |
|
|
|
if err != nil { |
|
|
|
log.Fatalln(err) |
|
|
|
} |
|
|
|
defer screen.Close() |
|
|
|
log.Println("connected to TFT in", time.Since(last)) |
|
|
|
|
|
|
|
var wait sync.WaitGroup |
|
|
|
|
|
|
|
wait.Add(1) |
|
|
|
go func(wait *sync.WaitGroup) { |
|
|
|
defer wait.Done() |
|
|
|
for line := range screen.Scan() { |
|
|
|
// log.Println("<t<", string(line))
|
|
|
|
if _, err := klipper.Write(line); err != nil { |
|
|
|
log.Fatalln("writing to klipper failed:", err) |
|
|
|
} |
|
|
|
} |
|
|
|
}(&wait) |
|
|
|
|
|
|
|
wait.Add(1) |
|
|
|
go func(wait *sync.WaitGroup) { |
|
|
|
defer wait.Done() |
|
|
|
for line := range klipper.Scan() { |
|
|
|
if _, err := screen.Write(line); err != nil { |
|
|
|
log.Fatalln("writing to tft failed:", err) |
|
|
|
} |
|
|
|
} |
|
|
|
}(&wait) |
|
|
|
|
|
|
|
wait.Wait() |
|
|
|
log.Println("fin") |
|
|
|
} |
|
|
|
|
|
|
|
type tft struct { |
|
|
|
c *serial.Port |
|
|
|
q []byte |
|
|
|
r chan []byte |
|
|
|
err error |
|
|
|
} |
|
|
|
|
|
|
|
func openTFT(name string, baud int) (*tft, error) { |
|
|
|
c, err := serial.OpenPort(&serial.Config{ |
|
|
|
Name: name, |
|
|
|
Baud: baud, |
|
|
|
}) |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
t := &tft{ |
|
|
|
c: c, |
|
|
|
// r: make(chan []byte, 8),
|
|
|
|
} |
|
|
|
// go t.reader()
|
|
|
|
|
|
|
|
return t, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (t *tft) Close() error { |
|
|
|
return t.c.Close() |
|
|
|
} |
|
|
|
|
|
|
|
func (t *tft) Scan() <-chan []byte { |
|
|
|
b := make(chan []byte, 8) |
|
|
|
go func(r io.Reader, b chan<- []byte) { |
|
|
|
defer close(b) |
|
|
|
s := bufio.NewScanner(t.c) |
|
|
|
s.Split(bufio.ScanLines) |
|
|
|
for s.Scan() { |
|
|
|
log.Printf("<t< %s\n", s.Text()) |
|
|
|
v := s.Bytes() |
|
|
|
if len(v) == 0 || v[0] == 0x00 { |
|
|
|
// Ignore reset line bytes
|
|
|
|
continue |
|
|
|
} |
|
|
|
b <- v |
|
|
|
} |
|
|
|
}(t.c, b) |
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
func (t *tft) Write(p []byte) (int, error) { |
|
|
|
log.Printf(">t> %s\n", string(p)) |
|
|
|
return t.c.Write(append(p, "\nok\n"...)) |
|
|
|
} |
|
|
|
|
|
|
|
type klippy struct { |
|
|
|
c net.Conn |
|
|
|
s *bufio.Scanner |
|
|
|
oid int // objects/subscribe for config
|
|
|
|
config map[string]interface{} |
|
|
|
gcode map[string]func(...string) |
|
|
|
w chan []byte |
|
|
|
r chan []byte |
|
|
|
mu sync.Mutex |
|
|
|
b *bytes.Buffer |
|
|
|
err error |
|
|
|
} |
|
|
|
|
|
|
|
func openKlipper(path string) (*klippy, error) { |
|
|
|
c, err := net.Dial("unix", path) |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
k := &klippy{ |
|
|
|
c: c, |
|
|
|
s: bufio.NewScanner(c), |
|
|
|
w: make(chan []byte, 8), |
|
|
|
r: make(chan []byte, 8), |
|
|
|
} |
|
|
|
k.s.Split(scanKlipper) |
|
|
|
|
|
|
|
// Local overrides.
|
|
|
|
k.gcode = map[string]func(...string){ |
|
|
|
"M92": k.handleM92, // Axis Steps-per-unit
|
|
|
|
"M115": k.handleM115, // Firmware Info
|
|
|
|
"M220": k.handleM220, // Feedrate Percentage
|
|
|
|
"M221": k.handleM221, // Flow Percentage
|
|
|
|
} |
|
|
|
|
|
|
|
if k.oid, err = k.request("objects/subscribe", map[string]interface{}{ |
|
|
|
"objects": map[string]interface{}{"configfile": nil}, |
|
|
|
"response_template": map[string]interface{}{"is_object": true}, |
|
|
|
}); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
if _, err = k.request("gcode/subscribe_output", map[string]interface{}{ |
|
|
|
"response_template": map[string]interface{}{"is_gcode": true}, |
|
|
|
}); err != nil { |
|
|
|
_ = k.c.Close() |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
return k, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) Close() error { |
|
|
|
return k.c.Close() |
|
|
|
} |
|
|
|
|
|
|
|
var responseTemplate = map[string]interface{}{ |
|
|
|
"response_template": map[string]interface{}{"echo": true}, |
|
|
|
} |
|
|
|
|
|
|
|
type klipperResponse struct { |
|
|
|
ID int `json:"id"` |
|
|
|
Params map[string]interface{} `json:"params"` |
|
|
|
Result map[string]interface{} `json:"result"` |
|
|
|
IsConfig bool `json:"is_config"` |
|
|
|
IsGCode bool `json:"is_gcode"` |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) Scan() <-chan []byte { |
|
|
|
b := make(chan []byte, 8) |
|
|
|
|
|
|
|
go func(r <-chan []byte, b chan<- []byte) { |
|
|
|
for s := range r { |
|
|
|
b <- s |
|
|
|
} |
|
|
|
}(k.r, b) |
|
|
|
|
|
|
|
go func(b chan<- []byte) { |
|
|
|
defer close(b) |
|
|
|
for k.s.Scan() { |
|
|
|
var response klipperResponse |
|
|
|
log.Printf("<k< %s\n", k.s.Text()) |
|
|
|
if err := json.Unmarshal(k.s.Bytes(), &response); err != nil { |
|
|
|
log.Println("error decoding klipper response (ignored):", err) |
|
|
|
continue |
|
|
|
} |
|
|
|
if response.ID == k.oid || response.IsConfig { |
|
|
|
_ = k.parseConfig(response.Result) |
|
|
|
} else if response.IsGCode { |
|
|
|
if r, ok := response.Params["response"].(string); ok { |
|
|
|
b <- []byte(r) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}(b) |
|
|
|
|
|
|
|
return b |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) parseConfig(r map[string]interface{}) (err error) { |
|
|
|
log.Printf("config: %#+v", r) |
|
|
|
k.config = r |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) parseGCode(r map[string]interface{}) (err error) { |
|
|
|
log.Printf("gcode: %#+v", r) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
var reGCode = regexp.MustCompile(`[A-Z][0-9]+`) |
|
|
|
|
|
|
|
func parseGCode(s string) (code string, args []string, ok bool) { |
|
|
|
p := strings.Fields(s) |
|
|
|
if len(p) < 1 || !reGCode.MatchString(p[0]) { |
|
|
|
return |
|
|
|
} |
|
|
|
return p[0], p[1:], true |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) intercept(s string) bool { |
|
|
|
if gcode, args, ok := parseGCode(s); ok { |
|
|
|
if f, ok := k.gcode[gcode]; ok { |
|
|
|
f(args...) |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) handleM92(args ...string) { |
|
|
|
k.r <- []byte("ok") |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) handleM115(args ...string) { |
|
|
|
k.r <- []byte("Cap:EEPROM:0") |
|
|
|
k.r <- []byte("Cap:SOFTWARE_POWER:0") |
|
|
|
k.r <- []byte("ok") |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) handleM220(args ...string) { |
|
|
|
k.r <- []byte("ok") |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) handleM221(args ...string) { |
|
|
|
k.r <- []byte("ok") |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) Write(p []byte) (int, error) { |
|
|
|
if k.intercept(string(p)) { |
|
|
|
return len(p), nil |
|
|
|
} |
|
|
|
if _, err := k.request("gcode/script", map[string]interface{}{"script": string(p)}); err != nil { |
|
|
|
return 0, err |
|
|
|
} |
|
|
|
return len(p), nil |
|
|
|
} |
|
|
|
|
|
|
|
type klipperRequest struct { |
|
|
|
ID int `json:"id"` |
|
|
|
Method string `json:"method"` |
|
|
|
Params map[string]interface{} `json:"params"` |
|
|
|
} |
|
|
|
|
|
|
|
func (k *klippy) request(method string, params map[string]interface{}) (id int, err error) { |
|
|
|
id = int(rand.Int31()) |
|
|
|
|
|
|
|
if params == nil { |
|
|
|
params = make(map[string]interface{}) |
|
|
|
} |
|
|
|
|
|
|
|
var ( |
|
|
|
request = klipperRequest{ |
|
|
|
ID: id, |
|
|
|
Method: method, |
|
|
|
Params: params, |
|
|
|
} |
|
|
|
o = new(bytes.Buffer) |
|
|
|
) |
|
|
|
if err = json.NewEncoder(o).Encode(request); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
log.Printf(">k> %s\n", o.String()) |
|
|
|
if err = o.WriteByte(0x03); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if _, err = o.WriteTo(k.c); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
return id, nil |
|
|
|
} |
|
|
|
|
|
|
|
func scanKlipper(data []byte, atEOF bool) (advance int, token []byte, err error) { |
|
|
|
if atEOF && len(data) == 0 { |
|
|
|
return 0, nil, nil |
|
|
|
} |
|
|
|
if i := bytes.IndexByte(data, 0x03); i >= 0 { |
|
|
|
return i + 1, data[0:i], nil |
|
|
|
} |
|
|
|
if atEOF { |
|
|
|
return len(data), data, nil |
|
|
|
} |
|
|
|
// Request more data.
|
|
|
|
return 0, nil, nil |
|
|
|
} |