Arksine / moonraker

Web API Server for Klipper
https://moonraker.readthedocs.io
GNU General Public License v3.0
1.05k stars 404 forks source link

[FR] PSU ON/OFF trigger 🔌 #167

Open uocnb opened 3 years ago

uocnb commented 3 years ago

Hi guys 👋

Thank you for the great API server! I'm using power config to control PSU and I think it would be great to have PSU trigger control with following conditions:

Would be configurable in the settings and work with existing Power control.

Inspired by Octoprint's PSUControl.

MarcPot commented 3 years ago

Don't think the auto ON is possible, since for giving a gcode command Klipper needs to be connected. And klipper is not connected when the psu is off.

For the auto off you just need to place the printer off in klippers idle_timeout

Here as example my setup

[idle_timeout]
timeout = 3600
gcode:
    {% if printer.pause_resume.is_paused %}
      M117 "timeout ignored because printer state == paused."
    {% else %}
      {% if (printer.toolhead.homed_axes == 'xyz' and printer.toolhead.axis_maximum.z > (printer.toolhead.position.z + 20)) %}
        G91
        G0 Z10 F600
        G90
        G0 X150 Y150 F2400
      {% endif %}
      TURN_OFF_MOTORS
      TURN_OFF_HEATERS
      UPDATE_DELAYED_GCODE ID=DELAYED_PRINTER_OFF DURATION=60
    {% endif %}

[gcode_macro POWER_OFF_PRINTER]
gcode =
  SET_ALL_LED
  {action_call_remote_method("set_device_power",
                             device="ssr",
                             state="off")}
  {action_call_remote_method("set_device_power",
                             device="printer",
                             state="off")}

[delayed_gcode DELAYED_PRINTER_OFF]
initial_duration = 0.
gcode =
  {% if printer.idle_timeout.state == "Idle" %}
    {% if printer["extruder"].temperature > 50 %}
      UPDATE_DELAYED_GCODE ID=DELAYED_PRINTER_OFF DURATION=60
    {% else %}
      POWER_OFF_PRINTER
    {% endif %}
  {% else %}
    M118 Printer not idle, cancelled PRINTER_OFF.
  {% endif %}
uocnb commented 3 years ago

Hi @MarcPot

I'm using Arduino Mega + RAMPS 1.6, the board is powering with USB cable from Raspberry Pi. The main power 12V / 24V (PSU) is connect via power input terminal in the RAMPS. With this config the controller board still powered on when PSU is off. I'm using similar klipper's idle timeout config for auto turn off PSU and START_PRINT macro to turn on PSU before print.

The probem is other command like homing, manual move, temperature pre-heat from UI or custom gcode in the console will need manual turn on PSU every time and I often forgot this step.

BTW, I don't think auto parking or move tool head like your idle timeout macro is a good idea. It can caused unexpected move when we not prepared for it yet like when you're maintaining the hotend or anything on the bed can caused collision 💥.

MarcPot commented 3 years ago

That auto parking thing is a personal thing, it doesn't matter for me since I don't work with the electricity on, and seeing the Z10 I'll never hit a part.

Yea okay you're the exception where that auto on would have use, for a temp solution: you can overwrite all gcode commands you need with

[gcode_macro G28]
rename_existing =     BASE_G28
gcode:
   PRINTER_ON
   BASE_G28

Although I admit this is a very ugly solution.

Another option would be to just stop powering the board through usb, that way you have to turn it on first. But again a very ugly solution.

What you could also do is just start a delayed gcode after the idle timeout that resets every second, then when the printer status moves away from idle it'll call a PRINTER_ON. Only problem with this is that it'll error commands like G28 or comparable.

uocnb commented 3 years ago

Overwrite base gcode commands is a doable solution. Stop powering through usb will need to modify hardware which I'm trying to avoid. Yes, messing with idle timeout in this case will create error first before the PSU is on.

I'm looking for something like gcode command event hook, but seem it will only work via API and not LCD menu. Klippy core or plugin implement probably is the best option IMO.

teslabol77 commented 2 years ago

Don't think the auto ON is possible, since for giving a gcode command Klipper needs to be connected. And klipper is not connected when the psu is off.

For the auto off you just need to place the printer off in klippers idle_timeout

Here as example my setup

[idle_timeout]
timeout = 3600
gcode:
  {% if printer.pause_resume.is_paused %}
    M117 "timeout ignored because printer state == paused."
  {% else %}
    {% if (printer.toolhead.homed_axes == 'xyz' and printer.toolhead.axis_maximum.z > (printer.toolhead.position.z + 20)) %}
      G91
      G0 Z10 F600
      G90
      G0 X150 Y150 F2400
    {% endif %}
    TURN_OFF_MOTORS
    TURN_OFF_HEATERS
    UPDATE_DELAYED_GCODE ID=DELAYED_PRINTER_OFF DURATION=60
  {% endif %}

[gcode_macro POWER_OFF_PRINTER]
gcode =
  SET_ALL_LED
  {action_call_remote_method("set_device_power",
                             device="ssr",
                             state="off")}
  {action_call_remote_method("set_device_power",
                             device="printer",
                             state="off")}

[delayed_gcode DELAYED_PRINTER_OFF]
initial_duration = 0.
gcode =
  {% if printer.idle_timeout.state == "Idle" %}
    {% if printer["extruder"].temperature > 50 %}
      UPDATE_DELAYED_GCODE ID=DELAYED_PRINTER_OFF DURATION=60
    {% else %}
      POWER_OFF_PRINTER
    {% endif %}
  {% else %}
    M118 Printer not idle, cancelled PRINTER_OFF.
  {% endif %}

Do I need to change "printer" to my TPLINK switch name? {action_call_remote_method("set_device_power", device="printer", state="off")}

ReubenBTalbott commented 2 years ago

I also have a very similar printer setup, I like keeping the main power off to conserve power, however I often forget to turn it back on so the printer gets mad when its steppers won't wake up 😅

uocnb commented 2 years ago

Thank @MarcPot reference config, I've using it to make the workaround solution. Here is what's I had done:

Implement custom M80 and M81 to call moonraker API for PSU on and off, put M80 on top of START_PRINT macro which is then place in beginning of the generated gcode (config in slicer). It allows PSU kick off when print job start and turn it off after idle timeout automatically by klipper.

Moonraker config

[power PSU]
type: homeassistant
address: x.x.x.x
port: 8123
device: switch.lumi_lumi_plug_bcc03702_on_off
token: TOKEN
off_when_shutdown: True

Klipper macro

[idle_timeout]
timeout: 600
gcode:
  MACHINE_IDLE_TIMEOUT

# Turn on PSU
[gcode_macro M80]
gcode:
  # Moonraker action
  {action_call_remote_method('set_device_power',
                             device='PSU',
                             state='on')}

# Turn off PSU
[gcode_macro M81]
gcode:
  # Moonraker action
  {action_call_remote_method('set_device_power',
                             device='PSU',
                             state='off')}

[gcode_macro MACHINE_IDLE_TIMEOUT]
gcode:
    M84
  TURN_OFF_HEATERS
  M81

[gcode_macro START_PRINT]
gcode:
  {% set BED_TEMP = params.BED_TEMP|default(60)|int %}
  {% set EXTRUDER_TEMP = params.EXTRUDER_TEMP|default(200)|int %}
  # Turn on PSU
  M80
...
Primer-Merc commented 1 year ago

uocnb,

Did you ever get this working as you would prefer? I have several marcos that I use from time to time and I always forget to power the mains relay before those macros.

I use GPIO on my RPI4 to control a relay to power the M/B with mains power. I use the PI to power the Printer M/B when the mains power is off. I also used Octoprint's PSUControl, which worked perfectly.

I tried this code and instantly got an error on reboot.

[gcode_macro G28] rename_existing = BASE_G28 gcode: PRINTER_ON BASE_G28

So weird that the Home All button doesen't do a check to see if the printer is powered. Or really that any command doesn't do a power check.

Primer-Merc commented 1 year ago

After a lot more digging, I was able to use the following script to power on the mains relay via the RPI4. Now when I click the HOME ALL button, the power switches on automatically.

I will now be able to use the same script commands for things like bed leveling and bed cleaning marcos.

Added to mainsail.cfg

[gcode_macro G28] rename_existing = G9028 gcode: {action_call_remote_method('set_device_power', device='printer', state='on')} G9028

I used these instructions https://moonraker.readthedocs.io/en/latest/configuration/#power-on-g-code-uploads

Be careful, there is a typo in the linked instructions, but the code in the instructions is correct.

uocnb commented 1 year ago

@Primer-Merc I'm still using START_PRINT macro as previous comment. It is working the same way as your G28 command. I'm using this macro to all slicer software for custom head cleaner, z-layer and z-height message update for lcd controller etc...

iceaway commented 1 year ago

I have just switched to Moonraker/Klipper/MainsailOS from OctoPrint and really liking it so far. I'm having issues getting this to work though. I tried the setting by @uocnb above, but for some reason it does not power on my PSU properly when a print starts. If I run START_PRINT in the console my printer turns on (I turn it on via MQTT), but if I put START_PRINT in my slicer software custom g-code klipper enters shutdown mode whenever I start a print with the PSU off. My printer mainboard is powered by the USB-cable to my RaspberryPi, hence it is always on, but I need to power on the PSU via MQTT to power steppers, fans etc.

Isn't there any way the PSU can be turned on without involving g-code, like PSU Control in OctoPrint? I think the settings such as on_when_job_queued indicates that this should be possible, but I have not managed to figure out how it all comes together.

Primer-Merc commented 1 year ago

@iceaway

I have this in my moonraker.cfg , but I use the RP4 to control a relay for my mains power.

EDIT: to clarify I'm not controlling the 120VAC power. I am controlling the 24v power to the printer mainboard.

[power printer] type: gpio pin: !gpiochip0/gpio14 off_when_shutdown: True restart_klipper_when_powered: False locked_while_printing: True initial_state: off on_when_job_queued: True

@uocnb @iceaway

Because I was always forgetting to power the printer with the little "Power Printer" in the upper right-hand menu. I discovered with @uocnb 's I could edit the macros.

image

For instance, when I want to call the "built in" bed level macro I first call the G28 macro which powers the printer. Because G28 is also a "built in" macro, I need to temporarily rename it and then run the gcode. It may not be pretty but it works now everytime.

[gcode_macro Level_Bed] gcode: G28 ; home all axes BED_SCREWS_ADJUST

[gcode_macro G28] rename_existing = G9028 gcode: {action_call_remote_method('set_device_power', device='printer', state='on')} G9028

uocnb commented 1 year ago

Hi @iceaway, AFAIK there are no plugin or event handling with the current klipper's config. The custom / overwrite (inheritable) macros is the only option we have currently with this setup. IMO, it is similar to the event driven we are looking for, just in a different form. If your macro is working with the console, it should work. Possibly you put it in the wrong custom g-code config section in your slicer software, you should check the generated gcode file to verify it. Here is my config with few slicers, the configured macro can be found in the beginning of the generated gcode file.

SuperSlicer: image

Simplify3D: image

Cura: image

@Primer-Merc I've just recall why I'm using M80 and M81 here. The reason is following the standard command for PSU control. They are available in some firmware implementation as mentioned in this document RepRap. AFAIK it is using by other CNC controller as well.

iceaway commented 1 year ago

Thanks for the ideas, I will try it some more. Every time it fails it's a bit of a hassle for some reason to get it working again. I have to go to the printer, unplug the USB-cable, restart some things etc, not really sure exactly which steps I have to do to get it back again.

In my final g-code there is only a single command before my START_PRINT command, and that is M107. START_PRINT is the very first thing in PrusaSlicer "Start G-Code" so I'm not sure where that M107 comes from. I tried removing that command manually but unfortunately that changed nothing. I will try the modified G28 command to see if that makes any difference.

iceaway commented 1 year ago

I tried adding a 3 second delay using G40 P3000 at the start, just in case the PSU would need some time to start up. But even using that I cannot see that the printer turns on in the power menu. START_PRINT and M80 works perfectly from the console, but in the gcode file it does nothing. Any ideas how I could troubleshoot this further? I tried using both START_PRINT (which in my case is the same as M80) and M80, but neither makes any difference.

uocnb commented 1 year ago

Sound like you're having issue with klipper config, possibly in the START_PRINT macro, you should check your klipper log file.

iceaway commented 1 year ago

I created a super simple test-file with the following contents:

START_PRINT
G4 P3000
M80
G4 P3000
M81
G4 P3000

It "printed" without any errors. The klippy.log showed the following:

Starting SD card print (position 0)
Stats 336472.6: gcodein=0  mcu: mcu_awake=0.009 mcu_task_avg=0.000097 mcu_task_stddev=0.000073 bytes_write=16452 bytes_read=171430 bytes_retransmit=27 bytes_invalid=0 send_seq=2025 receive_seq=2025 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=0 stalled_bytes=0 freq=15999912 sd_pos=12 heater_bed: target=0 temp=24.0 pwm=0.000 sysload=0.06 cputime=30.300 memavail=3474388 print_time=1486.669 buffer_time=2.637 print_stall=0 extruder: target=0 temp=23.9 pwm=0.000
Stats 336473.6: gcodein=0  mcu: mcu_awake=0.009 mcu_task_avg=0.000097 mcu_task_stddev=0.000073 bytes_write=16587 bytes_read=171544 bytes_retransmit=27 bytes_invalid=0 send_seq=2029 receive_seq=2026 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=7 stalled_bytes=60 freq=15999911 sd_pos=25 heater_bed: target=0 temp=24.0 pwm=0.000 sysload=0.06 cputime=30.315 memavail=3473944 print_time=1489.669 buffer_time=4.634 print_stall=0 extruder: target=0 temp=23.9 pwm=0.000
Stats 336474.6: gcodein=0  mcu: mcu_awake=0.017 mcu_task_avg=0.000164 mcu_task_stddev=0.000268 bytes_write=16670 bytes_read=171698 bytes_retransmit=27 bytes_invalid=0 send_seq=2032 receive_seq=2032 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=0 stalled_bytes=0 freq=15999912 sd_pos=25 heater_bed: target=0 temp=23.9 pwm=0.000 sysload=0.06 cputime=30.332 memavail=3473052 print_time=1489.669 buffer_time=3.633 print_stall=0 extruder: target=0 temp=23.8 pwm=0.000
Stats 336475.6: gcodein=0  mcu: mcu_awake=0.017 mcu_task_avg=0.000164 mcu_task_stddev=0.000268 bytes_write=16676 bytes_read=171798 bytes_retransmit=27 bytes_invalid=0 send_seq=2033 receive_seq=2033 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=0 stalled_bytes=0 freq=15999912 sd_pos=25 heater_bed: target=0 temp=23.9 pwm=0.000 sysload=0.05 cputime=30.353 memavail=3472604 print_time=1489.669 buffer_time=2.632 print_stall=0 extruder: target=0 temp=24.0 pwm=0.000
Stats 336476.6: gcodein=0  mcu: mcu_awake=0.017 mcu_task_avg=0.000164 mcu_task_stddev=0.000268 bytes_write=16871 bytes_read=171917 bytes_retransmit=27 bytes_invalid=0 send_seq=2038 receive_seq=2035 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=5 stalled_bytes=7 freq=15999912 sd_pos=38 heater_bed: target=0 temp=24.0 pwm=0.000 sysload=0.05 cputime=30.382 memavail=3473028 print_time=1492.669 buffer_time=4.624 print_stall=0 extruder: target=0 temp=24.0 pwm=0.000
Stats 336477.6: gcodein=0  mcu: mcu_awake=0.017 mcu_task_avg=0.000164 mcu_task_stddev=0.000268 bytes_write=16894 bytes_read=172051 bytes_retransmit=27 bytes_invalid=0 send_seq=2040 receive_seq=2040 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=0 stalled_bytes=0 freq=15999912 sd_pos=38 heater_bed: target=0 temp=24.0 pwm=0.000 sysload=0.05 cputime=30.405 memavail=3472540 print_time=1492.669 buffer_time=3.624 print_stall=0 extruder: target=0 temp=23.9 pwm=0.000
Stats 336478.6: gcodein=0  mcu: mcu_awake=0.017 mcu_task_avg=0.000164 mcu_task_stddev=0.000268 bytes_write=16900 bytes_read=172151 bytes_retransmit=27 bytes_invalid=0 send_seq=2041 receive_seq=2041 retransmit_seq=2 srtt=0.003 rttvar=0.000 rto=0.025 ready_bytes=0 stalled_bytes=0 freq=15999912 sd_pos=38 heater_bed: target=0 temp=23.9 pwm=0.000 sysload=0.05 cputime=30.427 memavail=3472704 print_time=1492.669 buffer_time=2.623 print_stall=0 extruder: target=0 temp=24.0 pwm=0.000
Finished SD card print
Exiting SD card print (position 48)

I am not really sure what I am looking for. Any ideas?

iceaway commented 1 year ago

I found it while looking an moonraker.log. I disabled changing the power for my printer during printing with locked_while_printing: True in moonraker.conf. I changed it to False and it appears to be working now :+1:

Arksine commented 1 year ago

The documentation regarding this functionality has recently been updated with further details. It is now possible to add force=True when calling the set_device_power remote method from Klipper, allowing the request to complete if locked_while_printing has been enabled. This allows a macro to explicitly turn on/off a peripheral during a print while protecting the device from an unintentional request to change the device state.

Separately, I thought I would address the OPs original request and explain why the way Octoprint's plugin handles this can't be implemented in Moonraker. Specifically, there is a philosophical difference between the way Octoprint and Moonraker handle G-Code commands. Octoprint generally processes each G-Code command, including those in a G-Code file, and provides hooks through which plugins can react or manipulate such commands. Moonraker does not do this. G-Code commands received through the console or API call are not inspected, they are passed directly to Klipper. Likewise G-Code files and not processed by Moonraker, Klipper's virtual_sdcard does the heavy lifting.

The reasons for this philosophical approach are two-fold: 1) Performance. Klipper has a well optimized path for gcode command processing. It isn't desirable for Moonraker to add overhead by doing its own G-Code processing. 2) Klipper G-Code commands are extensible through gcode_macos and Jinja2 scripting. All existing G-Codes can be overridden and its easy to create new commands. As some of you have discovered, much of what can be accomplished through an Octoprint plugin that relies on inspecting G-Code commands can be implemented by overriding gcode commands in Klipper. Additionally, it is possible to have a device power off when the extruder temp drops below 40C using a delayed_gcode. Anything that cannot be implemented in this fashion is likely a candidate for a Klippy extra rather than an extension in Moonraker.

iceaway commented 1 year ago

@Arksine That sounds great, I will try that! I like the idea of disabling it in the GUI just to avoid accidentally turning it off during a print.

I'm still not really clear on how mainsail/moonraker/klipper are all integrated with one another, but wouldn't it be possible to attach the "Turn on PSU"-hook to when a print is started? Such as when you click "Print" or "Upload and print" from i.e. PrusaSlicer?

Arksine commented 1 year ago

It is possible, but not necessary as it can be accomplished by overriding SDCARD_PRINT_FILE. The majority of printer builds don't separate logic from peripherals, in such cases it is not possible to start a print before powering on the device. Contrasting this functionality with on_when_queued would be confusing, and the latter cannot be implemented through a gcode override.

The docs provide a specific example of this use case.

iceaway commented 1 year ago

It is possible, but not necessary as it can be accomplished by overriding SDCARD_PRINT_FILE. The majority of printer builds don't separate logic from peripherals, in such cases it is not possible to start a print before powering on the device. Contrasting this functionality with on_when_queued would be confusing, and the latter cannot be implemented through a gcode override.

The docs provide a specific example of this use case.

The SDCARD_PRINT_FILE was just was just what I was looking for, now everything is working as expected. Thanks!

Gobol commented 1 year ago

Hi, I have similar problem. Is there any way to execute shell script after switching PSU ON ? I need to restart CANBUS to restore umbilical communication after printer shutdown, specifically, I need to exec "sudo ifdown can0 && sudo ifup can0" on RPi host (using rs485/can hat)

I've tried klipper's gcode_shell_command plugin, but klipper is dead without PSU... ;)

Thanks in advance!