From 93b9a85d19147ee57f6e79b1661e33df21aa1f20 Mon Sep 17 00:00:00 2001
From: Kevin O'Connor <kevin@koconnor.net>
Date: Wed, 28 Apr 2021 20:49:37 -0400
Subject: [PATCH] endstop_phase: Add support for reporting phase information
 via get_status()

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
---
 docs/Status_Reference.md       | 14 +++++++++++
 klippy/extras/endstop_phase.py | 46 +++++++++++++++++++++++-----------
 2 files changed, 46 insertions(+), 14 deletions(-)

diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md
index ab14b7f74..17a759f5b 100644
--- a/docs/Status_Reference.md
+++ b/docs/Status_Reference.md
@@ -36,6 +36,20 @@ The following information is available in the `display_status` object
   `virtual_sdcard.progress` if no recent `M73` received).
 - `message`: The message contained in the last `M117` G-Code command.
 
+# endstop_phase
+
+The following information is available in the
+[endstop_phase](Config_Reference.md#endstop_phase) object:
+- `last_home.<stepper name>.phase`: The phase of the stepper motor at
+  the end of the last home attempt.
+- `last_home.<stepper name>.phases`: The total number of phases
+  available on the stepper motor.
+- `last_home.<stepper name>.mcu_position`: The position (as tracked by
+  the micro-controller) of the stepper motor at the end of the last
+  home attempt. The position is the total number of steps taken in a
+  forward direction minus the total number of steps taken in the
+  reverse direction since the micro-controller was last restarted.
+
 # fan
 
 The following information is available in
diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py
index a2b2b5485..ed6c59608 100644
--- a/klippy/extras/endstop_phase.py
+++ b/klippy/extras/endstop_phase.py
@@ -117,6 +117,7 @@ class EndstopPhases:
     def __init__(self, config):
         self.printer = config.get_printer()
         self.tracking = {}
+        self.last_home_info = {}
         # Register handlers
         self.printer.register_event_handler("homing:home_rails_end",
                                             self.handle_home_rails_end)
@@ -124,21 +125,30 @@ class EndstopPhases:
         self.gcode.register_command("ENDSTOP_PHASE_CALIBRATE",
                                     self.cmd_ENDSTOP_PHASE_CALIBRATE,
                                     desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help)
-    def lookup_rail(self, stepper, stepper_name):
+    def lookup_stepper(self, stepper, stepper_name):
         mod_name = "endstop_phase %s" % (stepper_name,)
         m = self.printer.lookup_object(mod_name, None)
         if m is not None:
-            return (None, m.phase_history)
+            return {"get_phase": None, "is_rail": False,
+                    "phase_history": m.phase_history}
         for driver in TRINAMIC_DRIVERS:
             mod_name = "%s %s" % (driver, stepper_name)
             m = self.printer.lookup_object(mod_name, None)
             if m is not None:
-                return (m.get_phase, [0] * (m.get_microsteps() * 4))
+                return {"get_phase": m.get_phase, "is_rail": False,
+                        "phase_history": [0] * (m.get_microsteps() * 4)}
         return None
-    def update_rail(self, info, stepper):
+    def update_stepper(self, stepper, is_rail):
+        stepper_name = stepper.get_name()
+        if stepper_name not in self.tracking:
+            info = self.lookup_stepper(stepper, stepper_name)
+            self.tracking[stepper_name] = info
+        info = self.tracking[stepper_name]
         if info is None:
             return
-        get_phase, phase_history = info
+        if is_rail:
+            info["is_rail"] = True
+        get_phase = info["get_phase"]
         if get_phase is None:
             return
         try:
@@ -146,16 +156,19 @@ class EndstopPhases:
         except:
             logging.exception("Error in EndstopPhases get_phase")
             return
+        phase_history = info["phase_history"]
         phase = convert_phase(driver_phase, driver_phases, len(phase_history))
         phase_history[phase] += 1
+        self.last_home_info[stepper.get_name()] = {
+            'phase': phase, 'phases': len(phase_history),
+            'mcu_position': stepper.get_mcu_position()
+        }
     def handle_home_rails_end(self, homing_state, rails):
         for rail in rails:
-            stepper = rail.get_steppers()[0]
-            stepper_name = stepper.get_name()
-            if stepper_name not in self.tracking:
-                info = self.lookup_rail(stepper, stepper_name)
-                self.tracking[stepper_name] = info
-            self.update_rail(self.tracking[stepper_name], stepper)
+            is_rail = True
+            for stepper in rail.get_steppers():
+                self.update_stepper(stepper, is_rail)
+                is_rail = False
     cmd_ENDSTOP_PHASE_CALIBRATE_help = "Calibrate stepper phase"
     def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd):
         stepper_name = gcmd.get('STEPPER', None)
@@ -167,6 +180,8 @@ class EndstopPhases:
             raise gcmd.error("Stats not available for stepper %s"
                              % (stepper_name,))
         endstop_phase, phases = self.generate_stats(stepper_name, info)
+        if not info["is_rail"]:
+            return
         configfile = self.printer.lookup_object('configfile')
         section = 'endstop_phase %s' % (stepper_name,)
         configfile.remove_section(section)
@@ -176,7 +191,7 @@ class EndstopPhases:
             "The SAVE_CONFIG command will update the printer config\n"
             "file with these parameters and restart the printer.")
     def generate_stats(self, stepper_name, info):
-        get_phase, phase_history = info
+        phase_history = info["phase_history"]
         wph = phase_history + phase_history
         count = sum(phase_history)
         phases = len(phase_history)
@@ -200,10 +215,13 @@ class EndstopPhases:
             self.gcode.respond_info(
                 "No steppers found. (Be sure to home at least once.)")
             return
-        for stepper_name, info in sorted(self.tracking.items()):
-            if info is None:
+        for stepper_name in sorted(self.tracking.keys()):
+            info = self.tracking[stepper_name]
+            if info is None or not info["is_rail"]:
                 continue
             self.generate_stats(stepper_name, info)
+    def get_status(self, eventtime):
+        return { 'last_home': dict(self.last_home_info) }
 
 def load_config_prefix(config):
     return EndstopPhase(config)