Sensorless/Stallguard WebUI graph tool

The problem, how I see it:
Sometimes there are questions/feature requests:
What if we apply sensorless homing/detection (StallGuard) to something - extruder, Z stepper, or any other stepper in the system.
Also, sometimes there are silent question, like: “What have I done wrong with the sensorless homing?”
(It is actually mine, when I tried to setup the sensorless homing for the first time and can’t grasp why it didn’t work, do I messed with pins, do there is sensitivity issue? Like that.)

I want to somewhat democratize access to the underlying data.
As of v0.13.0-169-g9323a5df #6592 api to access the stallguard data from the TMC drivers is available.

It is possible to do so with Motan subsystem: Debugging - Klipper documentation
But it seems to be a complicated way sometimes.

Well, I glued together WebUI. Single HTML page.
stallguard-ui.html.zip (5.8 KB)


You can literally:

  1. Download
  2. Unzip
  3. Open file.
How to open file in Chrome
  • Press Ctrl+O on your keyboard and browse for the file on your computer.
  • Drag the file into Google Chrome from your computer desktop or folder. Your cursor displays a little ‘+’ sign if the action is successful.
  • Type the location, also known as the path, of the file in the address bar, then press Enter.

It allows connect to the moonraker on your printer, choose driver and see live data, with ability to pause and zoom (yes, pretty basics).

Hope, someone can find it useful.


Because moonraker does have CORS protection, it needs to be disabled, to run file from local desktop.

[authorization]
....
cors_domains:
    * # It is insecure, only as a duct tape to run file locally
    *.lan
    *.local
.....

Do not forget to restart moonraker, after modifications, and return it back when you done.


Ref: TMC Coolstep and how to make it works
Ref: klippy: add stallguard monitoring tool by nefelim4ag · Pull Request #6592 · Klipper3d/klipper · GitHub

5 Likes

Looks really interesting.

A few questions:

  1. Do I have to update the MCU to v013.0-169 or can I just run with it as is (v013.154 on the main controller board MCU that I want to use)?

  2. How do you determine the “Moonraker API URL”? I have fourteen machines running Klipper on my network (five printers, seven Boards Under Test, one rPi 4 and one rPi 5). I’m guessing I use the “Trusted Connection” in moonraker.log?
    image
    Unfortunately, when I plug that in, even with the default port 7125, I get:


    I would have thought I at least get a connection (Klipper on this particular machine’s host was just upgraded to v013.0-169).

  3. I’m not sure about the instruction “Because moonraker does have CORS protection, it needs to be disabled, to run file from local desktop.” What is the “local desktop”?
    – Is it the machine I’m accessing (which can be anything from a phone to a Chrombook to a Windows PC to an Apple Mac to a Linux PC) or do you just need to make this update in moonraker.conf and be done with it?

  4. Finally, once you have your connection do you execute a G28 X for Stepper X or does Play/Resume in stallguard-ui.html request this for you?

Sorry for all the questions - the tool looks really useful!

It is a host-side feature; there are no MCU side changes. So, it should work with older MCU code.

By default, Moonraker would have a similar config (moonraker.conf):

[server]
host: 0.0.0.0
port: 7125

That means it does listen on all interfaces/IPv4 addresses of the “server” (SBC/RPI in general).
So, generally, with this config, moonraker URL would equal the address of the printer’s SBC/RPI with moonraker port (7125 by default).
It would look like either your printer’s <IP address>:7125, or local DNS name, like voron24.local:7125

(Btw, to verify it, you can just open it)

Yes, by default, Moonraker would allow connections without authorization only from local networks defined in the moonraker.conf

[authorization]
trusted_clients:
    10.0.0.0/8
    127.0.0.0/8
    169.254.0.0/16
    172.16.0.0/12
    192.168.0.0/16
    FE80::/10
    ::1/128

So, you are the trusted client.

Yes, this is expected. Because of CORS, more on this below. You can easily verify it in the browser console (Ctrl + Shift + C), where the network information is shown:


(While the console is open, to make it possible to click on the button “Connect to …”, instead of inspecting it, you can press Ctrl + Shift + C again).

I meant your machine, the one you are plan to use, to open the HTML and use as the client.

Yes, this additional “*” mask is enough (allow everything - just disables the check).

Basically, CORS gives your browser information about which resources are secure/eligible to send requests to this specific backend server (moonraker).
So, to allow the web requests from application, it has to have a specific entry defined in the allowable CORS domains.
In this specific case, it is opened from local file and has a browser URL/path like file://C:/Users../stallguard-ui.html (for example).
I did not find a better mask to cover file url’s in browsers

For what the CORS.. well, basically, it is a security measure, to secure you, if you open: malicious-EEVBlog.xyz, which underneath would try to send requests to a different server, as example, to your moonraker, to do, idk, something. The browser will disallow that, as long as the mask does not match.

Pause/Resume only affects the browser behavior, so you basically pause the graph/log updates.
It is just a graph tool.
So, you still have to move your motors to get live data (or do G28/homing).

While standstill, it would continue to receive stall guard/current data. But it would have different meanings (it’s better to check the datasheet for your specific driver).
The only notice, TMC2209 only outputs StallGuard in the StealthChop (it is SG4), most others in the SpreadCycle only (SG2). TMC2240 does both.
Klipper does the required configuration/mode switches if you use the sensorless homing, upon homing.

Thanks.

Okay, I’ve got it running.

For some reason, it won’t connect with the IPv4 address, but it will with <klippername>.local:7125. I’m trying it on a Windows 11 machine with the Chrome browser.

I’m running with TMC2209s so the “CS Value” is not displayed as you indicated:

The image above shows a basic sensorless homing operation that I think is good - is there an “ideal” waveform that I should be looking for?

What kind of problem information should I be looking for in the waveform? I tried some invalid SGTHRS values and the waveform was drawn out and “Stallguard Value” doesn’t go to zero, but, other than that what should I be looking for?


Can I say, for people who are looking to use this tool, the workflow is a bit clunky, especially if you’re using this to “tune” your sensorless homing.

After updating your printer.cfg, you have to go back and “Connect to Moonraker” again followed by “Select TMC Object” and then “Clear Data” with “Resume” being the last thing you do before doing the homing operation in Mainsail/Fluidd.

Now, I understand the reasons by you have to go through the set up process each time after changing the printer.cfg values and doing a Klipper RESTART but it’s something that I found that I forgot to do as I worked with the tool.

@nefelim4ag thanx for putting in the time on this!

1 Like

It is omitted because there are some bandwidth limitation on TMC UART.
You actually see it in debug output, as -1.

This is a complicated question. Good explanation of everything is available here:
https://www.analog.com/en/resources/app-notes/an-002.html
And partly in your driver datasheet.

I suspect you have to have problem, to try to fix it.
Generally we expect to see values > 0 when motor spins faster than minimal speed.
Then as noted in the guide above, you can try to apply force and got some idea of how it is behave under the load and when it stops.
Also, I would expect that SG output would rise with acceleration, stabilize around some value with constant speed and goes down with deceleration/force applied.

SG2 (SpreadCycle specific) != SG4 (StealthChop specific).
SG2 shifted by the SGT value, until your workable range is roughly 0..100, under your load.
SG4 just show the values, and you have to detect what the minimal value (SGTHRS) is meant as the full stop.
Simply speaking:

  • SGTHRS does not alter the output of SG4 (current, load, speed does).
  • SGT does alter the output of SG2 (current, voltage, load, speed also does).

Small suggestion here, I think it would be much handier, if you initially put your SG4 (TMC2209) motor in stealthchop, so you would always see the output.
As example:

[stepper_x]
stealthchop_threshold: 99999

or

SET_TMC_FIELD STEPPER=stepper_x FIELD=TPWMTHRS VALUE=1
# TMC2209 specific (IIRC)
SET_TMC_FIELD STEPPER=stepper_x FIELD=en_spreadcycle VALUE=0

Then you can simply play with thresholds (from klipper docs):

# SG2 drivers
SET_TMC_FIELD STEPPER=stepper_x FIELD=SGTHRS VALUE=<0..255>
# or  SG4 drivers
SET_TMC_FIELD STEPPER=stepper_x FIELD=sgt VALUE=<-64..0..63>
# Modify current as example:
SET_TMC_CURRENT STEPPER=stepper_x CURRENT=1.0

That would allow to not touch the printer.cfg at all, while you are working with that.

If it is not convenient to apply load/breaking force to the motor by hand:
you can also actually hit your limit without homing procedure, with something like:

# totally untested, this is just an example to show
# that it is possible that way
G91
SET_KINEMATIC_POSITION X=110
G1 X-110
// hit at -100
G1 X100
// repeat
SET_KINEMATIC_POSITION X=110
G1 X-110
G1 X100
...

Hope that helps.

Hey,

Sorry for the long time on the reply - I had to design a couple of PCBs for my functional test and I had to put my full attention on it.

Going back to the thread, I’ve set up another test machine and I think I need to explain a few things.

First off, it’s a test machine, not a 3D printer. The stepper motors are built into a simple jig that consists of an arm that collides with a post to test movement and sensorless homing:

There are four stepper motor drivers connected to stepper motor & the test fixture shown above.

For simplicity, let’s talk about the Y or Z axis - X is basically the same but uses a “dual carriage” set up (to get four steppers with sensorless homing).

I believe that I’ve set up the stepper definition as how you recommend in the previous post:

# Motor 2
[stepper_y]
enable_pin: !PC14
dir_pin: PA13
step_pin: PA3
microsteps: 16
rotation_distance: 40
#endstop_pin: PB5
endstop_pin: tmc2209_stepper_y:virtual_endstop
position_min: -5
position_max: 250
position_endstop: 250
homing_speed: 25
[tmc2209 stepper_y]
uart_pin: PC15
uart_address: 2
diag_pin: PC0
run_current: 0.580
stealthchop_threshold: 999999
driver_SGTHRS: 70

The only thing that might be different from what you expect is that the homing_speed is only 25 which is probably too slow for the printer. I only set this value because my regular value of 50 was pretty violent.

As for the actual values used during sensorless homing operation, I use global variables rather than searching through and specifying them in the G28 homing_override macro code:

[gcode_macro global] ### Global Variables 
variable_xyz_run_current:   0.5
variable_xyz_home_current:  0.3
variable_xyz_home_sgthrs:  70
gcode:
 :

[gcode_macro _HOME_Y]
gcode:
    SET_TMC_CURRENT STEPPER=stepper_y CURRENT={printer["gcode_macro global"].xyz_home_current}
    SET_TMC_FIELD STEPPER=stepper_y FIELD=SGTHRS VALUE={printer["gcode_macro global"].xyz_home_sgthrs}

    G4 P500                                          # Wait for StallGuard registers to clear

    G28 Y

    SET_TMC_CURRENT STEPPER=stepper_y CURRENT={printer["gcode_macro global"].xyz_run_current}
    
    G91                                              # Move away to centre of build surface

 :

[homing_override]
axes: xyz                                            
gcode:
  {% set home_all = 'X' not in params and 'Y' not in params and 'Z' not in params %}

  {% if home_all or 'X' in params %}
    _HOME_X
  {% endif %}
   
  {% if home_all or 'Y' in params %}
    _HOME_Y
  {% endif %}
  
  {% if home_all or 'Z' in params %}
    _HOME_Z
  {% endif %}

So, I think I have had you points covered right from the start.

I just ran sensorless homing on the Y Axis, using the apparatus and the printer.cfg statements listed above and got the waveform:

This is consistent across all four steppers. I know it’s different from my previous post.

You implied above that what I was seeing wasn’t correct/optimal, could you please explain any concerns that you have?

Thanx,

myke

I rechecked my above sentences. Unfortunately, I do not see a place where I imply anything.
The closest thing that can imply something is that I said that SGTHRS does not move SG_RESULT in any way.
That means that SGTHRS does not influence the output of SG_RESULT.


As shown in the TMC2209 documentation, an example stall could take a place at SG_RESULT == ~130.
So, you do not have to expect 0 value with Stallgurad4.

If you have specific questions, you are free to ask, but I would prefer to abstain from guessing about the setup/motor’s behaviour that I do not have.
I can technically only quote the driver datasheet and application notes, and say this is what to expect or not.

So, you have SGTHRS == 70. So, it should trigger a stall if SG_RESULT < 70*2.
Also, as you now utilize the stealthchop_threshold: 999999. You seem to see the SG_RESULT values all the time.

This is what I would expect from your configuration, according to the docs.

Thanks.

1 Like

I was reacting to your statement in the 5th post:

I understand the lack of relationship between SG_RESULT and SGTHRS.

For example, I can reduce SGTHRS to 10 (from the typical 70) and get the waveform and response that follows. I should point out that there is a real “clunk” from the stepper and the test arm/fixture.

Similarly with a SGTHRS value of 140 gets the waveform and response that follows. It should be noted that the stepper only jerks the test arm without reaching the end post on the fixture before ending the sensorless homing operation:

Going back to SGTHRS of 70, movement is as expected, everything looks (and sounds) fine:

This seems to be the three states you can expect with the TMC2209.

Now, I’m not sure why I got the squiggles shown in the 4th post but everything appears like the last set above.

Thanx for the comments and the tool - I think they’ll be helpful for people using sensorless homing.

Hi all,

Just wanted to share how I got this working on my end.

Step 1 - ON MY VORON I uploaded the provided .html to a folder and started an HTTP server.

voron@voron:~ $ cd /home/voron/printer_data/config/stallguard/
voron@voron:~/printer_data/config/stallguard $ ls
stallguard-ui.html
voron@voron:~/printer_data/config/stallguard $ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.0.0.3 - - [22/Oct/2025 14:54:54] "GET / HTTP/1.1" 200 -
10.0.0.3 - - [22/Oct/2025 14:54:54] code 404, message File not found
10.0.0.3 - - [22/Oct/2025 14:54:54] "GET /favicon.ico HTTP/1.1" 404 -
10.0.0.3 - - [22/Oct/2025 14:54:56] "GET /stallguard-ui.html HTTP/1.1" 200 -

Step 2 - Updated moonraker.conf’s CORS to allow a connection from the stallguard webserver we just created.

[authorization]
trusted_clients:
    10.0.0.0/16
    10.0.0.0/8
    127.0.0.0/8
    169.254.0.0/16
    172.16.0.0/12
    192.168.0.0/16
    FC00::/7
    FE80::/10
    ::1/128
cors_domains:
    *.lan
    *.local
    *://localhost
    *://localhost:*
    *://my.mainsail.xyz
    *://app.fluidd.xyz
    http://voron.local
    http://voron.local:8000

Step 3 - Opened up firefox on my desktop, and pointed it towards the stallguard server (voron.local:8000) and on the “moonraker API URL” field I made sure to pick “voron.local:7125”.

I have ZERO idea why I couldn’t launch this .html straight from my desktop’s desktop, but hey… at least it’s working now.


Edit: Just to be clear, my voron also running a webserver for this .html file on port 8000 and all I’m doing is connecting to that. From the stallguard .html file I can then point towards the moonraker server on port 7125.


Also…

This is completely out of my field. I’m not sure how to use this information. I was hoping this would help me tune my sensorless homing settings. Idk.

I think I could make more sense out of this if it wasn’t moving over time and adjusting it’s scale accordingly. Idk.

All of this is new to me. The manual, the tech sheet, and the resources on this post… It’s all a bit overwhelming. I might have to keep my currently under-performing settings and give this a shot some other day.

2 Likes

Hi @nefelim4ag ,

I’ve been testing this for a little while and it seems really useful. However, loading the HTML file every time I want to use it is a little cumbersome. I’m wondering, is it ok if I host this file to make it easier for people to access? I can attribute you in the title. Also, is it ok if I make some small changes, particularly:

  • Adding a maximum buffer size/time input so the graph auto-scrolls
  • Changing a bit of styling

This is super useful. Thank you for making this!

2 Likes

@3dcoded, of course, you can do whatever you feel right with that.
Host, modify, replace & etc.
This is more of an example of what is possible with the Klipper’s bulk data API for me.

If you decide to host it somewhere, I can probably, add a simple link to this in the top post.

Thanks.

2 Likes

After some testing on an HTTPS server, I discovered that HTTPS servers cannot connect to unencrypted websockets like Moonraker.

To work around this, I cleaned up the page and uploaded it to a Github Gist so it can be installed with one command for Mainsail or Fluidd, then accessed at /stallguard.html

For Mainsail:

wget -O ~/mainsail/stallguard.html https://gist.githubusercontent.com/3DCoded/4586fd328cbb819f8586bfbbe0b342a0/raw/0331d4b46efab71e4252ed54810a9f3567036e20/stallguard.html

For Fluidd:

wget -O ~/fluidd/stallguard.html https://gist.githubusercontent.com/3DCoded/4586fd328cbb819f8586bfbbe0b342a0/raw/0331d4b46efab71e4252ed54810a9f3567036e20/stallguard.html

The updated page will autofill the Moonraker address (assuming it’s port 7125), then save the last successful connection so it auto-connects on future reloads. It also implements a configurable buffer time limit so only the last few seconds show on the graph.

Thank you @nefelim4ag for creating this awesome tool!!


1 Like