From e7b0e7b43bbf20bf89f47444fbbfc0e10aca1ed1 Mon Sep 17 00:00:00 2001
From: Clifford Roche <clifford.roche@gmail.com>
Date: Wed, 14 Apr 2021 11:37:24 -0400
Subject: [PATCH] palette2: Add ping variation + ping status reports (#4114)

Signed-off-by: Clifford Roche <clifford.roche@gmail.com>
---
 docs/Command_Templates.md |  6 ++++
 docs/Config_Reference.md  |  6 ++--
 klippy/extras/palette2.py | 65 +++++++++++++++++++++++++++------------
 3 files changed, 55 insertions(+), 22 deletions(-)

diff --git a/docs/Command_Templates.md b/docs/Command_Templates.md
index 91ba72768..503066f79 100644
--- a/docs/Command_Templates.md
+++ b/docs/Command_Templates.md
@@ -313,6 +313,12 @@ The following are common printer attributes:
 - `printer.system_stats.sysload`, `printer.system_stats.cputime`,
   `printer.system_stats.memavail`: Information on the host operating
   system and process load.
+- `printer.palette2.ping`: Amount of the last reported Palette 2 ping
+  in percent.
+- `printer.palette2.remaining_load_length`: When starting a Palette 2
+  print, this will be the amount of filament to load into the extruder.
+- `printer.palette2.is_splicing`: True when the Palette 2 is splicing
+  filament.
 
 The above list is subject to change - if using an attribute be sure to
 review the [Config Changes document](Config_Changes.md) when upgrading
diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md
index 4322ae3b1..8035ce68c 100644
--- a/docs/Config_Reference.md
+++ b/docs/Config_Reference.md
@@ -3903,12 +3903,14 @@ for your print to begin.
 [palette2]
 serial:
 #   The serial port to connect to the Palette 2.
-#baud: 250000
-#   The baud rate to use. The default is 250000.
+#baud: 115200
+#   The baud rate to use. The default is 115200.
 #feedrate_splice: 0.8
 #   The feedrate to use when splicing, default is 0.8
 #feedrate_normal: 1.0
 #   The feedrate to use after splicing, default is 1.0
 #auto_load_speed: 2
 #   Extrude feedrate when autoloading, default is 2 (mm/s)
+#auto_cancel_variation: 0.1
+#   Auto cancel print when ping varation is above this threshold
 ```
diff --git a/klippy/extras/palette2.py b/klippy/extras/palette2.py
index f778039a6..55b7692cc 100644
--- a/klippy/extras/palette2.py
+++ b/klippy/extras/palette2.py
@@ -32,7 +32,7 @@ COMMAND_SMART_LOAD_STOP = "O102 D1"
 
 HEARTBEAT_SEND = 5.
 HEARTBEAT_TIMEOUT = (HEARTBEAT_SEND * 2.) + 1.
-SETUP_TIMEOUT = 300
+SETUP_TIMEOUT = 10
 
 SERIAL_TIMER = 0.1
 AUTOLOAD_TIMER = 5.
@@ -78,12 +78,14 @@ class Palette2:
         self.serial_port = config.get("serial")
         if not self.serial_port:
             raise config.error("Invalid serial port specific for Palette 2")
-        self.baud = config.getint("baud", default=250000)
+        self.baud = config.getint("baud", default=115200)
         self.feedrate_splice = config.getfloat(
-            "feedrate_splice", 0.8, minval=0., maxval=1.)
+            "feedrate_splice", default=0.8, minval=0., maxval=1.)
         self.feedrate_normal = config.getfloat(
-            "feedrate_normal", 1.0, minval=0., maxval=1.)
+            "feedrate_normal", default=1.0, minval=0., maxval=1.)
         self.auto_load_speed = config.getint("auto_load_speed", 2)
+        self.auto_cancel_variation = config.getfloat(
+            "auto_cancel_variation", default=None, minval=0.01, maxval=0.2)
 
         # Omega code matchers
         self.omega_header = [None] * 9
@@ -168,11 +170,12 @@ class Palette2:
         # Tell the device we're alive
         self.write_queue.put("\n")
         self.write_queue.put(COMMAND_FIRMWARE)
+        self._wait_for_heartbeat()
 
     cmd_Disconnect_Help = ("Disconnect from the Palette 2")
 
     def cmd_Disconnect(self, gmcd=None):
-        logging.info("Disconnecting from Palette 2")
+        self.gcode.respond_info("Disconnecting from Palette 2")
         if self.serial:
             self.serial.close()
             self.serial = None
@@ -215,6 +218,19 @@ class Palette2:
         if self._check_P2(gcmd):
             self.write_queue.put(gcmd.get_commandline())
 
+    def _wait_for_heartbeat(self):
+        startTs = self.reactor.monotonic()
+        currTs = startTs
+        while self.heartbeat is None and self.heartbeat < (
+                currTs - SETUP_TIMEOUT) and startTs > (
+                currTs - SETUP_TIMEOUT):
+            currTs = self.reactor.pause(currTs + 1.)
+
+        if self.heartbeat < (currTs - SETUP_TIMEOUT):
+            self.signal_disconnect = True
+            raise self.printer.command_error(
+                "No response from Palette 2")
+
     cmd_O1_help = (
         "Initialize the print, and check connection with the Palette 2")
 
@@ -224,18 +240,8 @@ class Palette2:
             raise self.printer.command_error(
                 "Cannot initialize print, palette 2 is not connected")
 
-        startTs = self.reactor.monotonic()
-        while self.heartbeat is None and self.heartbeat < (
-                self.reactor.monotonic() -
-                HEARTBEAT_TIMEOUT) and startTs > (
-                self.reactor.monotonic() -
-                HEARTBEAT_TIMEOUT):
-            self.reactor.pause(1)
-
-        if self.heartbeat < (self.reactor.monotonic() - HEARTBEAT_TIMEOUT):
-            raise self.printer.command_error(
-                "No response from Palette 2 when initializing")
-
+        self.reactor.update_timer(self.heartbeat_timer, self.reactor.NOW)
+        self._wait_for_heartbeat()
         self.write_queue.put(gcmd.get_commandline())
         self.gcode.respond_info(
             "Palette 2 waiting on user to complete setup")
@@ -363,6 +369,15 @@ class Palette2:
         if not self.is_printing:
             return
 
+        def check_ping_variation(last_ping):
+            if self.auto_cancel_variation is not None:
+                ping_max = 100. + (self.auto_cancel_variation * 100.)
+                ping_min = 100. - (self.auto_cancel_variation * 100.)
+                if last_ping < ping_min or last_ping > ping_max:
+                    logging.info("Ping variation is too high, "
+                                 "cancelling print")
+                    self.gcode.run_script("CANCEL_PRINT")
+
         if len(params) > 2:
             percent = float(params[1][1:])
             if params[0] == "D1":
@@ -370,6 +385,7 @@ class Palette2:
                 d = {"number": number, "percent": percent}
                 logging.info("Ping %d, %d percent" % (number, percent))
                 self.omega_pings.append(d)
+                check_ping_variation(percent)
             elif params[0] == "D2":
                 number = len(self.omega_pongs) + 1
                 d = {"number": number, "percent": percent}
@@ -563,12 +579,14 @@ class Palette2:
 
     def _run_Heartbeat(self, eventtime):
         self.write_queue.put(COMMAND_HEARTBEAT)
+        eventtime = self.reactor.pause(eventtime + 5)
         if self.heartbeat and self.heartbeat < (
                 eventtime - HEARTBEAT_TIMEOUT):
             logging.error(
-                "P2 has not responded to heartbeat, Palette will disconnect")
-            self.cmd_Disconnect()
-            return self.reactor.NEVER
+                "P2 has not responded to heartbeat")
+            if not self.is_printing or self.is_setup_complete:
+                self.cmd_Disconnect()
+                return self.reactor.NEVER
         return eventtime + HEARTBEAT_SEND
 
     def _run_Write(self, eventtime):
@@ -618,6 +636,13 @@ class Palette2:
             return self.reactor.NOW
         return eventtime + AUTOLOAD_TIMER
 
+    def get_status(self, eventtime=None):
+        status = {
+            "ping": self.omega_pings[-1],
+            "remaining_load_length": self.remaining_load_length,
+            "is_splicing": self.is_splicing
+        }
+        return status
 
 def load_config(config):
     return Palette2(config)