1717# You should have received a copy of the GNU Affero General Public License
1818# along with this program. If not, see <http://www.gnu.org/licenses/>.
1919#
20-
20+ import errno
2121import json
2222import requests
23- import threading
23+ import sentry_sdk
2424
2525# noinspection PyPackageRequirements
2626import flask
27+ import serial
28+ import tornado
2729
2830from octoprint .events import Events
2931import octoprint .plugin
3032import octoprint .settings
33+ from octoprint .util .commandline import CommandlineError
3134
32- from octoprint_simplyprint .comm import SimplyPrintComm
33- from octoprint_simplyprint .local import cron
35+ from octoprint_simplyprint .websocket import SimplyPrintWebsocket
3436
3537SIMPLYPRINT_EVENTS = [
38+ Events .PRINTER_STATE_CHANGED ,
39+ Events .TOOL_CHANGE ,
3640 Events .CONNECTING ,
3741 Events .CONNECTED ,
3842 Events .DISCONNECTING ,
3943 Events .DISCONNECTED ,
44+ Events .CLIENT_AUTHED ,
4045
4146 Events .STARTUP ,
4247 Events .SHUTDOWN ,
8085 Events .FILE_REMOVED ,
8186]
8287
88+ IGNORED_EXCEPTIONS = [
89+ # serial exceptions in octoprint.util.comm
90+ (
91+ serial .SerialException ,
92+ lambda exc , logger , plugin , cb : logger == "octoprint.util.comm" ,
93+ ),
94+ # KeyboardInterrupts
95+ KeyboardInterrupt ,
96+ # IOErrors of any kind due to a full file system
97+ (
98+ IOError ,
99+ lambda exc , logger , plugin , cb : exc .errorgetattr (exc , "errno" ) # noqa: B009
100+ and exc .errno in (getattr (errno , "ENOSPC" ),), # noqa: B009
101+ ),
102+ # RequestExceptions of any kind
103+ requests .exceptions .RequestException ,
104+ # Tornado WebSocketErrors of any kind
105+ tornado .websocket .WebSocketError ,
106+ # Tornado HTTPClientError
107+ tornado .httpclient .HTTPClientError ,
108+ # error from windows for linux specific commands related to wifi
109+ CommandlineError
110+ ]
83111
84112class SimplyPrint (
85113 octoprint .plugin .SettingsPlugin ,
@@ -94,47 +122,97 @@ class SimplyPrint(
94122 _files_analyzed = []
95123
96124 simply_print = None
97-
98- host = "127.0.0.1"
99125 port = "5000"
100126
101127 def initialize (self ):
102128 # Called once the plugin has been loaded by OctoPrint, all injections complete
103- self .simply_print = SimplyPrintComm (self )
129+ sp_cls = SimplyPrintWebsocket
130+ self .simply_print = sp_cls (self )
104131
105132 def on_startup (self , host , port ):
133+ # Initialize sentry.io for error tracking
134+ self ._initialize_sentry ()
106135 # Run startup thread and run the main loop in the background
107- self .simply_print .start_startup ()
108- self .simply_print .start_main_loop ()
136+ self .simply_print .on_startup ()
109137
110- self .host = host
111138 # Remember that this port is internal to OctoPrint, a proxy may exist.
112139 self .port = port
113-
114- ip = host
115-
116- if port :
117- ip += str (port )
118-
119- self ._logger .info ("Host is; " + str (host ) + " and port is; " + str (port ))
120- self .send_port_ip (None , ip )
140+ if port != 5000 and port != 80 and port != 443 :
141+ self .send_port_ip (port )
121142
122143 # #~~ StartupPlugin mixin
123144 def on_after_startup (self ):
124-
125145 self ._logger .info ("SimplyPrint OctoPrint plugin started" )
126146
127- # If cron jobs don't exist, create them
128- if not cron .check_cron_jobs ():
129- cron .create_cron_jobs ()
130-
131147 # The "Startup" event is never picked up by the plugin, as the plugin is loaded AFTER startup
132148 self .on_event ("Startup" , {})
133149
150+ def _initialize_sentry (self ):
151+ self ._logger .debug ("Initializing Sentry" )
152+
153+ def _before_send (event , hint ):
154+ if "exc_info" not in hint :
155+ # we only want exceptions
156+ return None
157+
158+ handled = True
159+ logger = event .get ("logger" , "" )
160+ plugin = event .get ("extra" , {}).get ("plugin" , None )
161+ callback = event .get ("extra" , {}).get ("callback" , None )
162+
163+ for ignore in IGNORED_EXCEPTIONS :
164+ if isinstance (ignore , tuple ):
165+ ignored_exc , matcher = ignore
166+ else :
167+ ignored_exc = ignore
168+ matcher = lambda * args : True
169+
170+ exc = hint ["exc_info" ][1 ]
171+ if isinstance (exc , ignored_exc ) and matcher (
172+ exc , logger , plugin , callback
173+ ):
174+ # exception ignored for logger, plugin and/or callback
175+ return None
176+
177+ elif isinstance (ignore , type ):
178+ if isinstance (hint ["exc_info" ][1 ], ignore ):
179+ # exception ignored
180+ return None
181+
182+ # if event.get("exception") and event["exception"].get("values"):
183+ # handled = not any(
184+ # map(
185+ # lambda x: x.get("mechanism")
186+ # and not x["mechanism"].get("handled", True),
187+ # event["exception"]["values"],
188+ # )
189+ # )
190+ #
191+ # if handled:
192+ # # error is handled, restrict further based on logger
193+ # if logger != "" and not (
194+ # logger.startswith("octoprint.plugins.SimplyPrint") or logger.startswith("octoprint.plugins.simplyprint")
195+ # ):
196+ # # we only want errors logged by our plugin's loggers
197+ # return None
198+
199+ if logger .startswith ("octoprint.plugins.SimplyPrint" ) or logger .startswith ("octoprint.plugins.simplyprint" ):
200+ return event
201+ else :
202+ return None
203+
204+ sentry_sdk .init (
205+ dsn = "https://[email protected] /6611344" ,
206+ traces_sample_rate = 0.01 ,
207+ before_send = _before_send ,
208+ release = "SimplyPrint@{}" .format (self ._plugin_version )
209+ )
210+ if self ._settings .get (["printer_id" ]) != "" :
211+ sentry_sdk .set_user ({"id" : self ._settings .get (["printer_id" ])})
212+
134213 def on_shutdown (self ):
135214 if self .simply_print is not None :
136- # SimplyPrintComm will stop on next loop
137- self .simply_print .run_loop = False
215+ self .simply_print .close ()
138216
139217 @staticmethod
140218 def get_settings_defaults ():
@@ -163,9 +241,21 @@ def get_settings_defaults():
163241 "gcode_scripts_backed_up" : False ,
164242 },
165243 "debug_logging" : False ,
166- "public_port" : "80"
244+ "public_port" : "80" ,
245+ # Websocket Default Settings
246+ "websocket_ready" : True ,
247+ "endpoint" : "production" ,
248+ "printer_token" : "" ,
249+ "ambient_temp" : "85" ,
167250 }
168251
252+ def on_settings_save (self , data ):
253+ octoprint .plugin .SettingsPlugin .on_settings_save (self , data )
254+ new_printer_id = self ._settings .get (["printer_id" ])
255+
256+ if new_printer_id != "" :
257+ sentry_sdk .set_user ({"id" : self ._settings .get (["printer_id" ])})
258+
169259 def get_template_vars (self ):
170260 return {
171261 "version" : self ._plugin_version
@@ -186,35 +276,23 @@ def get_api_commands(self):
186276 return {
187277 "setup" : [], # Sets up SimplyPrintRPiSoftware
188278 "uninstall" : [], # Uninstalls SimplyPrintRPiSoftware
279+ "message" : ["payload" ], # Inject websocket messages
189280 }
190281
191- @staticmethod
192- def _uninstall_sp ():
193- # All we need to do here is remove cron jobs, nothing else
194- cron .remove_cron_jobs ()
195-
196- @staticmethod
197- def _install_background ():
198- if not cron .check_cron_jobs ():
199- cron .create_cron_jobs ()
200-
201282 def on_api_command (self , command , data ):
202- if command == "setup" :
203- self ._uninstall_sp ()
204- elif command == "uninstall" :
205- self ._uninstall_sp ()
283+ if command == "message" and self .simply_print .test :
284+ msg = json .dumps (data ["payload" ])
285+ # Generally we do NOT want to access methods marked
286+ # as private, however this is for testing only
287+ self .simply_print ._process_message (msg )
288+ return
206289
207290 # Send public port to outside system
208- def send_port_ip (self , port = None , ip = None ):
291+ def send_port_ip (self , port = None ):
209292 self ._settings .set (["public_port" ], port )
210293 self ._settings .save ()
211294
212295 def on_api_get (self , request ):
213- import flask
214- import subprocess
215- # self.log(str(request))
216- # self.log(str(request.args))
217-
218296 if request .args is not None :
219297 if request .args .get ("install" , default = None , type = None ) is not None :
220298 # Install
@@ -274,9 +352,14 @@ def on_event(self, event, payload):
274352 if event in SIMPLYPRINT_EVENTS :
275353 self .simply_print .on_event (event , payload )
276354
277- # def gcode_sent(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
278- # if gcode and gcode == "M106":
279- # self._logger.info("Just sent M106: {cmd}".format(**locals()))
355+ def gcode_sent (self , comm_instance , phase , cmd , cmd_type , gcode , * args , ** kwargs ):
356+ # if gcode and gcode == "M106":
357+ # self._logger.info("Just sent M106: {cmd}".format(**locals()))
358+ tags = kwargs .get ("tags" , [])
359+ if tags is None :
360+ return
361+ if "source:api" in tags or "plugin:octoprint_simplyprint" in tags :
362+ self .simply_print .on_gcode_sent (cmd )
280363
281364 def gcode_received (self , comm_instance , line , * args , ** kwargs ):
282365 if line .strip () not in ["echo:busy: paused for user" , "echo:busy: processing" , "Unknown M code: M118 simplyprint unpause" , "simplyprint unpause" ]:
@@ -290,24 +373,26 @@ def gcode_received(self, comm_instance, line, *args, **kwargs):
290373 self ._logger .debug ("received line: echo:busy: processing, setting user_input_required False" )
291374 self .simply_print .user_input_required = False
292375
376+ self .simply_print .on_gcode_received (line )
377+
293378 return line
294379
295380 def process_at_command (self , comm , phase , command , parameters , tags = None , * args , ** kwargs ):
296381 if command .lower () not in ["simplyprint" , "pause" ]:
297382 return
298383
299- url_parameters = ""
300- if command . lower () == "pause" :
301- url_parameters += "&pause_message={}" . format (parameters )
302- elif command . lower () == "simplyprint" :
303- if parameters :
304- parameters_list = parameters . split ( " " )
305- if parameters_list [ 0 ] == "layer" :
306- url_parameters += "& layer={}" . format ( parameters_list [1 ])
307-
308- if url_parameters != "" :
309- self . simply_print . ping ( url_parameters )
310-
384+ cmd = command . lower ()
385+ if cmd == "pause" :
386+ self . simply_print . on_pause_at_command (parameters )
387+ elif cmd == "simplyprint" :
388+ params = parameters . strip (). split ( " " )
389+ if params and params [ 0 ] == "layer" :
390+ try :
391+ layer = int ( params [1 ])
392+ except Exception :
393+ pass
394+ else :
395+ self . simply_print . on_layer_change ( layer )
311396 return
312397
313398 def get_update_information (self ):
@@ -341,13 +426,9 @@ def get_update_information(self):
341426
342427
343428__plugin_name__ = "SimplyPrint Cloud"
344- __plugin_pythoncompat__ = ">=2.7,<4"
345- __plugin_disabling_discouraged__ = """
346- Please uninstall SimplyPrint Cloud rather than just disable it, since it sets up some background scripts
347- that will continue to run if you disable it.
348- """
429+ __plugin_pythoncompat__ = ">=3.7,<4"
349430# Remember to bump the version in setup.py as well
350- __plugin_version__ = "3 .1.2 "
431+ __plugin_version__ = "4 .1.0rc1 "
351432
352433
353434def __plugin_load__ ():
@@ -357,5 +438,5 @@ def __plugin_load__():
357438 "octoprint.plugin.softwareupdate.check_config" : __plugin_implementation__ .get_update_information ,
358439 "octoprint.comm.protocol.atcommand.sending" : __plugin_implementation__ .process_at_command ,
359440 "octoprint.comm.protocol.gcode.received" : __plugin_implementation__ .gcode_received ,
360- # "octoprint.comm.protocol.gcode.sent": __plugin_implementation__.gcode_sent
441+ "octoprint.comm.protocol.gcode.sent" : __plugin_implementation__ .gcode_sent
361442 }
0 commit comments