#!/usr/bin/env python3
"""Client minimal du socket API de klippy : envoie du G-code, affiche la reponse.

    klippy-client.py "G28" "M114"      -> envoie, attend, rend la main
    klippy-client.py                   -> console interactive (Ctrl-D / :q)

Le protocole est du JSON separe par l'octet 0x03 sur une socket unix.

Le script console fourni par Klipper reste connecte en mode interactif, donc il
est inutilisable depuis un script : celui-ci envoie, attend l'acquittement, et
rend la main. Le mode interactif sert a piloter soi-meme un calibrage manuel
(TESTZ / ACCEPT) sans passer par un intermediaire a chaque pas.
"""
import json
import socket
import sys
import time

UDS = "/tmp/klippy_uds"
TIMEOUT = 1500.0         # PID_CALIBRATE du plateau peut depasser 10 min


class Klippy:
    def __init__(self):
        self.s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        self.s.settimeout(5.0)
        self.s.connect(UDS)
        self.buf = b""
        self.n = 0
        # On s'abonne a la sortie console pour voir ce que klippy repond vraiment.
        self._send({"id": 0, "method": "gcode/subscribe_output",
                    "params": {"response_template": {"key": "out"}}})

    def _send(self, obj):
        self.s.sendall(json.dumps(obj).encode() + b"\x03")

    def _messages(self, deadline):
        """Genere les messages JSON recus jusqu'a la deadline."""
        while time.time() < deadline:
            try:
                self.s.settimeout(min(5.0, max(0.1, deadline - time.time())))
                chunk = self.s.recv(65536)
            except socket.timeout:
                continue
            if not chunk:
                return
            self.buf += chunk
            while b"\x03" in self.buf:
                raw, self.buf = self.buf.split(b"\x03", 1)
                try:
                    yield json.loads(raw)
                except ValueError:
                    continue

    def run(self, cmd):
        """Envoie une commande, affiche les reponses, retourne True si ok."""
        self.n += 1
        n = self.n
        print(">>> %s" % cmd, flush=True)
        self._send({"id": n, "method": "gcode/script", "params": {"script": cmd}})
        for msg in self._messages(time.time() + TIMEOUT):
            resp = msg.get("params", {}).get("response")
            if resp:
                print("    %s" % resp, flush=True)
            if msg.get("id") == n:
                if "error" in msg:
                    err = msg["error"]
                    print("!!! ERREUR: %s" % err.get("message", err), flush=True)
                    return False
                print("    [ok]", flush=True)
                return True
        print("!!! pas de reponse apres %ss" % TIMEOUT, flush=True)
        return False

    def close(self):
        self.s.close()


def main(argv):
    k = Klippy()
    rc = 0
    try:
        if argv:
            for cmd in argv:
                if not k.run(cmd):
                    rc = 1
        else:
            print("console klippy — Ctrl-D ou :q pour sortir", flush=True)
            while True:
                try:
                    line = input("gcode> ").strip()
                except EOFError:
                    print()
                    break
                if line in (":q", ":quit", "exit"):
                    break
                if line:
                    k.run(line)
    finally:
        k.close()
    return rc


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
