Cannot get a hook to load

Hi,

I am having trouble getting a plugin to load when I am calling for a hook (similar to the example in the documentation for rewriting M107). Below is what I have in my init.py for my plugin. As well as the output from the console. Is there some other place this is supposed to be defined that I have missed?

Thanks,

Dave

init.py:

__plugin_name__ = "Hello World"
__plugin_implementation__ = HelloWorldPlugin()

def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = HelloWorldPlugin()
	
	global __plugin_hooks__
	__plugin_hooks__ = {
		"octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.rewrite_G1,
		"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
	}

Console output:

Traceback (most recent call last):
  File "c:\devel\octoprint\src\octoprint\plugin\core.py", line 836, in load_plugin
    plugin.load()
  File "c:\devel\octoprint-helloworld\octoprint_helloworld\__init__.py", line 54, in __plugin_load__
    "octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.rewrite_G1,
AttributeError: 'HelloWorldPlugin' object has no attribute 'rewrite_G1'

Can you share your full plugin code please? Or is that really all? I'm missing the declaration of the HelloWorldPlugin class.

Thanks for responding! How's this?

# coding=utf-8
from __future__ import absolute_import

from decimal import *
import re
import octoprint.plugin



class HelloWorldPlugin(octoprint.plugin.StartupPlugin,
                       octoprint.plugin.TemplatePlugin,
                       octoprint.plugin.SettingsPlugin,
                       octoprint.plugin.AssetPlugin):

 def on_after_startup(self):
     self._logger.info("Hello World! (more: %s)" % self._settings.get(["liveOffset"]))
 
 def get_settings_defaults(self):
     return dict(url="https://en.wikipedia.org/wiki/Hello_world",liveOffset=0)

 def get_template_configs(self):
     return [
         dict(type="navbar", custom_bindings=False),
         dict(type="settings", custom_bindings=False)
     ]

 def get_assets(self):
     return dict(
         js=["js/helloworld.js"],
         css=["css/helloworld.css"]
     )
	 
def rewrite_g1(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
	if gcode and cmd.startswith('g1'):
		curZ = re.search("\s[zZ]-?([0-9.]{1,15})", cmd) #capture the current Z value 
  		if curZ:
			self._logger.info("Found Z value " + curZ )
			# add the currrent live offset value to the gcode line
			newZ = float(curZ) + self.liveOffset
			# rebuild gcode line by substituting in new Z value for current
			newGcode = re.sub('\s[zZ]-?([0-9.]{1,15})', str(newZ), cmd)	
			cmd = newGcode
			self._logger.info("New Code to be sent " + cmd )
			return cmd,
	
__plugin_name__ = "Hello World"
__plugin_implementation__ = HelloWorldPlugin()

def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = HelloWorldPlugin()
	
	global __plugin_hooks__
	__plugin_hooks__ = {
		"octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.rewrite_g1,
		"octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
	}

Better :wink:

You have an indentation error there with your rewrite_g1 function. The way it is indented, it's not actually part of your HelloWorldPlugin class but instead a top level element. So it's no wonder that you are getting that error, your class actually doesn't have an attribute called rewrite_g1 (or rewrite_G1 as you had it named in your initial post).

Indentation in Python matters and is significant for your control flow.

tldr: Instead of

class Foo(object):
    def bar(self):
        pass

def fnord(self):
    # this isn't actually part of the class!
    pass

you want to make sure you have

class Foo(object):
    def bar(self):
        pass

    def fnord(self):
        # but now it is!
        pass

(Friendly advice: use 4 spaces (or one tab) per indentation level. Be consistent about that. Makes such errors immediately and glaringly visible)

The NOOB strikes again. I fixed it all up and it loads now. Doesn't work. :wink: But it is loading! Thanks so much for your help!

Does it matter if it is G1 or g1 and if so, would I need separate handlers for each?

You can call your hook handler handle_some_awesome_stuff and it won't care about it, your hook's code is what will have to see if it's actually supposed to run based on the passed in commands.

See this example:

# coding=utf-8

import octoprint.plugin

class RewriteM107Plugin(octoprint.plugin.OctoPrintPlugin):
    def rewrite_m107(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
        if gcode and gcode == "M107":
            cmd = "M106 S0"
        return cmd,

    def sent_m106(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
        if gcode and gcode == "M106":
            self._logger.info("Just sent M106: {cmd}".format(**locals()))

__plugin_name__ = "Rewrite M107"
def __plugin_load__():
    global __plugin_implementation__
    __plugin_implementation__ = RewriteM107Plugin()

    global __plugin_hooks__
    __plugin_hooks__ = {
        "octoprint.comm.protocol.gcode.queuing": __plugin_implementation__.rewrite_m107,
        "octoprint.comm.protocol.gcode.sent": __plugin_implementation__.sent_m106
    }

It's the if gcode and gcode == "M107" check that determines whether to rewrite here (and yes, those gcode values - if set - will always be upper case because GCODE commands are upper case), the method could have been called cute_little_bunny instead of rewrite_m107 and that still would work. However, it is generally a good idea to use meaningful names :rabbit: