SOLVED: Trouble with "octoprint.comm.protocol.gcode.queuing" hook

Hi guys!

I've been working on a very specific plugin for a printer, which requires me to process GCODE on the fly before it is sent to the printer. The plugin also must connect to a websocket server implemented in another program which is preforming the processing and spitting out the several replacement commands to be queued.

This is the stack trace from octoprint.log from the plugin:

2018-12-30 06:37:19,370 - octoprint.util.comm - INFO - Changing monitoring state from "Operational" to "Printing"
2018-12-30 06:37:19,391 - octoprint.plugin - ERROR - Error while calling plugin tracking
Traceback (most recent call last):
File "/home/pi/oprint/local/lib/python2.7/site-packages/octoprint/plugin/init.py", line 230, in call_plugin
result = getattr(plugin, method)(*args, **kwargs)
File "/home/pi/oprint/lib/python2.7/site-packages/octoprint/plugins/tracking/init.py", line 119, in on_event
self._track_printjob_event(event, payload)
File "/home/pi/oprint/lib/python2.7/site-packages/octoprint/plugins/tracking/init.py", line 237, in _track_printjob_event
sha.update(self._settings.get([b"unique_id"]))
TypeError: update() argument 1 must be string or buffer, not None

This is the plugin code here [ Please excuse any obvious glaring issues here, Python isn't my language of choice :wink: ]

ws = None
_logger = None

def convert2(self, comm_instance, phase, cmd, cmd_type, gcode, subcode=None, tags=None, *args, **kwargs):
	ws.send("gcode cmd here")
	#self._logger.info("gcode was sent!");
    #return [data.splitlines()] # will take the (likely) several generated commands and put it enqueue
	return[cmd]

def on_open(ws):
	ws.send("Plugin Loaded!!!")


def __plugin_load__():
	_logger = logging.getLogger("octoprint.plugins.gcode_converter")
	_logger.info("Conversion Processor Init!");
	ws = websocket.WebSocketApp("ws://127.0.0.1:9000/gcodestream")
	ws.on_open = on_open
	wst = threading.Thread(target=ws.run_forever)
	wst.daemon = True
	wst.start()
	__plugin_hooks__ = {
		"octoprint.comm.protocol.gcode.queuing": convert2
	}

The websocket appears to connect perfectly fine, and the server receives the "Plugin Loaded!!!" string perfectly fine. The problem arises when GCODE is being sent however, and that error is sent on each line in the log file. I've been troubleshooting this for the past few hours and I've hit a brick wall.

Any ideas as to what's causing this issue??

Thanks,
Nick

These errors are caused by the tracking plugin and IMHO unrelated to your code.

I'm working on a plugin as well right now, get the same error messages, but my plugin seems to be unaffected by this.

EDIT: you might want to take a look here https://discourse.octoprint.org/t/error-while-calling-plugin-tracking/6115

I was thinking the same thing @rmie, however this code still doesn't seem to want to work :confused:

Looking a little closer to the docs to see if there is something obvious I missed.

Your ~/.octoprint/config.yaml file is missing the unique_id variable. @foosel

Go visit the Settings -> Anonymous Usage page. You'll likely seeing "N/A" for the identifier and perhaps the enabled box is unchecked. Enable it and Save, followed by a restart of OctoPrint. Then revisit that page and verify that there's an ID. Feel free to toggle it back off if you want.

Yep. There's a patch for the next version.

I'm wondering about this in your code. convert2 is implemented as a class method (self as first argument) but actually isn't a member of a class. Not sure how OctoPrint calls your function in that case (if at all) but maybe your arguments are one off.

@rmie I've at this point completely ditched that script and started a new one, but with more success.

I'm no longer getting those errors, and my hook is indeed being called correctly. I've also ditched websockets and opted to rewrite the conversion utility to interact with stdin and stdout as a command-line process to hopefully remove some overhead (and maybe latency) of a TCP/IP connection.

Although I wish the troubles stopped there, i'm at another roadblock :frowning:
I'm getting a broken pipe error:

File "/home/pi/.octoprint/plugins/polar_converter.py", line 15, in convert_to_polar_coord
polar_proc.stdin.write(cmd+"\n")
IOError: [Errno 32] Broken pipe

As you can see from the code below, I'm using subprocess to spawn the conversion utility, and attempting to interact with the process live via polar_proc.stdin.write() and polar_proc.stdout.readline()

# coding=utf-8

import octoprint
import subprocess
import logging
import threading

polar_proc = subprocess.Popen(['mono', 'PolarCoordinateConverterService.exe'], bufsize=0, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

class ConverterPlugin(octoprint.plugin.OctoPrintPlugin):

	def convert_to_polar_coord(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
		global polar_proc
		self._logger.info("INPUT COMMAND->"+cmd);
		polar_proc.stdin.write(cmd+"\n")
		polar_proc.stdin.flush()
		response = polar_proc.stdout.readline().split("~")
		new_cmds = list()
		for new in response:
			self._logger.info("RESP:"+new);
			new_cmd_struct = (new,)
			new_cmds.append(new_cmd_struct)
		return new_cmds


__plugin_name__ = "Polar Converter Plugin"


def __plugin_load__():
	global __plugin_implementation__
	__plugin_implementation__ = ConverterPlugin()

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

I'm unsure as to why i'm getting broken-pipe errors, and i've been researching it for hours at this point.
When attempting to interact with the subprocess using the code below in a python interactive terminal works perfectly fine, generating the desired output.

import subprocess
polar_proc = subprocess.Popen(['mono', 'PolarCoordinateConverterService.exe'], bufsize=0, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
polar_proc.stdin.write("G1 X44.321 Y-44.150 E2.84530\n")
polar_proc.stdin.flush()
print(polar_proc.stdout.readline().split("~"))

OUTPUT:

['M220 S300', 'M221 S50', 'G1 X17432503 E0.0760775401069519 ', 'G1 X2.0072265527 Y315.1107432503 E0.0912930481283423 ', 'G.1107432503 E0.106508556149733 ', 'M220 S270', 'M221 S75',...]

Any Ideas as to why this doesn't work?

Also, Thanks for fast responses you guys have been sending, I really appreciate it.

I've not worked with that before but maybe you need to set up a handler that waits for the pipe to be open before you try sending things into it. Initially, just insert a two-second delay to verify.

I was thinking the same thing, but the handler is only called when a line is sent to the printer, which only happens when its connected to the printer. It takes about 30 seconds before octoprint loads and connects. I would think that would he plenty of time, right? The python interactive terminal just takes a few milliseconds to run the same code, so I'm not sure what gives.

Think about it. Your plugin interprets GCODE and until the user has initiated something this doesn't happen for some time in the startup phase. Just put a five-second delay before the initial send and you should be fine. The average time between startup and a user print initiated is more like a few minutes.

Technically, you'd be creating a race condition (startup versus user) and you're gambling that the user can't fire off a job before the first five seconds of OctoPrint being 100% up. It should be fine, though.

I've figured it out!

I decided to pop open htop in an SSH term and filter for the words 'mono'
When I did this, I noticed that htop in tree-mode only had the words 'mono' listed, and not a file path. That's when it struck me.

When inside the python interactive terminal preforming the same subprocess.Popen(), I saw that mono had 'PolarCoordinateConverterService.exe' listed right after, which the script running from OctoPrint didn't.

Python term:

OctoPrint script:
--Apparently new users can only post one image per post, so imagine the same screenshot but without the PolarCoordinateConverterService.exe file listed--

So when I changed the subprocess.Popen() to contain the full path to the file something magical happened.. No more Broken Pipes, and the code appears to be working great :smile:

Thank you for your assistance @OutsourcedGuru and @rmie, I appreciate the help.
Still unsure as to why I needed to specify the full path, as I imagine(d) that the script is run from the same plugin directory where the .exe is? nonetheless, it works now, so I'm happy about that.

Thank you all!

1 Like

Sometimes code runs under a shell from a user like nobody or something other than pi as you might expect. Adding full paths is just good practice.

Ah, yeah I suppose that would explain it.