Copy M106 command plugin

For PrusaSlicer does not support a fan on a second extruder, I started to create a plugin that copies the M106 value to the second fan.

I started with this: Plugin-Examples/rewrite_m107.py at 48316b8a5257763966d0c6c0a2f57a4825bb1db1 · OctoPrint/Plugin-Examples · GitHub

And now I am here:

# coding=utf-8

import octoprint.plugin

class CopyM106Plugin(octoprint.plugin.OctoPrintPlugin):
	def copy_m106(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
		if gcode and gcode == "M106":	   
			# global cmd_copy_m106
			cmd_copy_m106 = cmd
			cmd_copy_m106 = cmd_copy_m106[5:]
			self._logger.info(cmd_copy_m106)
		return [("M106 "+ cmd_copy_m106),
				("M106 P1 " + cmd_copy_m106)]

			
__plugin_name__ = "Copy M106"
__plugin_pythoncompat__ = ">=2.7,<4"
def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = CopyM106Plugin()

	global __plugin_hooks__
	__plugin_hooks__ = {
		"octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.copy_m106,
	}

So far it works well on the virtual printer:

2021-04-08 18:50:29,781 - Changing monitoring state from "Operational" to "Starting"
2021-04-08 18:50:30,451 - Send: N1 M106 S0*102
2021-04-08 18:50:30,473 - Recv: ok
2021-04-08 18:50:30,479 - Send: N2 M106 P1 S0*36
2021-04-08 18:50:30,489 - Recv: ok
2021-04-08 18:50:30,494 - Changing monitoring state from "Starting" to "Printing"
2021-04-08 18:50:30,580 - Send: N3 M106 S10*85
2021-04-08 18:50:30,588 - Recv: ok
2021-04-08 18:50:30,590 - Send: N4 M106 P1 S10*19
2021-04-08 18:50:30,595 - Recv: ok
2021-04-08 18:50:30,654 - Send: N5 M106 S255*96
2021-04-08 18:50:30,661 - Recv: ok
2021-04-08 18:50:30,663 - Send: N6 M106 P1 S255*34
2021-04-08 18:50:30,668 - Recv: ok
2021-04-08 18:50:30,727 - Send: N7 M106 S0*96
2021-04-08 18:50:30,734 - Recv: ok
2021-04-08 18:50:30,736 - Send: N8 M106 P1 S0*46
2021-04-08 18:50:30,741 - Recv: ok
2021-04-08 18:50:30,813 - Changing monitoring state from "Printing" to "Finishing"
2021-04-08 18:50:31,741 - Recv: wait
2021-04-08 18:50:32,741 - Recv: wait
2021-04-08 18:50:33,744 - Recv: wait
2021-04-08 18:50:34,746 - Recv: wait
2021-04-08 18:50:35,746 - Recv: wait
2021-04-08 18:50:35,840 - Changing monitoring state from "Finishing" to "Operational"

But in octoprint.log I get the annoying UnboundLocalError:

2021-04-08 18:48:50,855 - octoprint.util.comm - ERROR - Error while processing hook M106_Copy for phase queuing and command M110 N0:
Traceback (most recent call last):
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/util/comm.py", line 4509, in _process_command_phase
    tags=tags,
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/util/__init__.py", line 1941, in wrapper
    return f(*args, **kwargs)
  File "/home/pi/.octoprint/plugins/M106_Copy.py", line 12, in copy_m106
    return [("M106 "+ cmd_copy_m106),
UnboundLocalError: local variable 'cmd_copy_m106' referenced before assignment
2021-04-08 18:48:51,018 - octoprint.filemanager.analysis - INFO - Starting analysis of local:Test.gcode
2021-04-08 18:48:51,022 - octoprint.filemanager.analysis - INFO - Invoking analysis command: /home/pi/oprint/bin/python -m octoprint analysis gcode --speed-x=6000 --speed-y=6000 --max-t=10 --throttle=0.01 --throttle-lines=100 --bed-z=0.0 --offset 0.0 0.0 /home/pi/.octoprint/uploads/Test.gcode
2021-04-08 18:48:51,224 - octoprint.util.comm - ERROR - Error while processing hook M106_Copy for phase queuing and command M110 N0:
Traceback (most recent call last):
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/util/comm.py", line 4509, in _process_command_phase
    tags=tags,
  File "/home/pi/oprint/lib/python3.7/site-packages/octoprint/util/__init__.py", line 1941, in wrapper
    return f(*args, **kwargs)
  File "/home/pi/.octoprint/plugins/M106_Copy.py", line 12, in copy_m106
    return [("M106 "+ cmd_copy_m106),
UnboundLocalError: local variable 'cmd_copy_m106' referenced before assignment
...

I set the variable cmd_copy_m106 to global, but that resulted in keeping the variable to the first truncated value (S0)

Any ideas?

Your return statement has the variable referenced and it's only defined if gcode and gcode == "M106"

Indent the return to be in line with the conditional then put a new return None in its former place

https://docs.octoprint.org/en/master/plugins/hooks.html#octoprint-comm-protocol-gcode-phase

Might also have to adjust the format for what is returned when M106

Yesss! This works fine!

I think I will dive deeper into plugin development.

Thanks a lot @kantlivelong !

So here is the plugin:

# coding=utf-8

import octoprint.plugin

class CopyM106Plugin(octoprint.plugin.OctoPrintPlugin):
	def copy_m106(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
		if gcode and gcode == "M106":	   
			cmd_copy_m106 = cmd
			cmd_copy_m106 = cmd_copy_m106[5:]
			self._logger.info(cmd_copy_m106)
			return [("M106 "+ cmd_copy_m106),
					("M106 P1 " + cmd_copy_m106)]
		return None			

			
__plugin_name__ = "Copy M106"
__plugin_pythoncompat__ = ">=2.7,<4"
def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = CopyM106Plugin()

	global __plugin_hooks__
	__plugin_hooks__ = {
		"octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.copy_m106,
	}

:thumbsup:

This looks exactly like what I need to control my second parts cooling fan connected on a different port than P0!
How do I use this script?

Just install the file inside this zip:

M106_Copy.zip (536 Bytes)

With: