Experimental bridge between RPi 4 running Klipper and a MKS type TFT.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

331 lines
6.7 KiB

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
}