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
|
|
}
|