15
15
import octoprint .filemanager
16
16
from octoprint .filemanager .util import StreamWrapper
17
17
from octoprint .filemanager .destinations import FileDestinations
18
+ from octoprint .util import RepeatedTimer
19
+
18
20
19
21
from .print_queue import PrintQueue , QueueItem
20
- from .driver import ContinuousPrintDriver
22
+ from .driver import ContinuousPrintDriver , Action as DA , Printer as DP
21
23
22
24
QUEUE_KEY = "cp_queue"
23
25
CLEARING_SCRIPT_KEY = "cp_bed_clearing_script"
@@ -82,6 +84,10 @@ def get_settings_defaults(self):
82
84
d [BED_COOLDOWN_TIMEOUT_KEY ] = 60
83
85
return d
84
86
87
+ def _active (self ):
88
+ return self .d .state != self .d ._state_inactive if hasattr (self , "d" ) else False
89
+
90
+
85
91
def _rm_temp_files (self ):
86
92
# Clean up any file references from prior runs
87
93
for path in TEMP_FILES .values ():
@@ -108,25 +114,40 @@ def on_after_startup(self):
108
114
self .q = PrintQueue (self ._settings , QUEUE_KEY )
109
115
self .d = ContinuousPrintDriver (
110
116
queue = self .q ,
111
- finish_script_fn = self .run_finish_script ,
112
- clear_bed_fn = self .clear_bed ,
113
- start_print_fn = self .start_print ,
114
- cancel_print_fn = self .cancel_print ,
117
+ script_runner = self ,
115
118
logger = self ._logger ,
116
119
)
120
+ self .update (DA .DEACTIVATE ) # Initializes and passes printer state
117
121
self ._update_driver_settings ()
118
122
self ._rm_temp_files ()
119
123
self .next_pause_is_spaghetti = False
124
+
125
+ # It's possible to miss events or for some weirdness to occur in conditionals. Adding a watchdog
126
+ # timer with a periodic tick ensures that the driver knows what the state of the printer is.
127
+ self .watchdog = RepeatedTimer (5.0 , lambda : self .update (DA .TICK ))
128
+ self .watchdog .start ()
120
129
self ._logger .info ("Continuous Print Plugin started" )
121
130
131
+ def update (self , a : DA ):
132
+ # Access current file via `get_current_job` instead of `is_current_file` because the latter may go away soon
133
+ # See https://docs.octoprint.org/en/master/modules/printer.html#octoprint.printer.PrinterInterface.is_current_file
134
+ # Avoid using payload.get('path') as some events may not express path info.
135
+ path = self ._printer .get_current_job ().get ("file" , {}).get ("name" )
136
+ pstate = self ._printer .get_state_id ()
137
+ p = DP .BUSY
138
+ if pstate == "OPERATIONAL" :
139
+ p = DP .IDLE
140
+ elif pstate == "PAUSED" :
141
+ p = DP .PAUSED
142
+
143
+ if self .d .action (a , p , path ):
144
+ self ._msg (type = "reload" ) # Reload UI when new state is added
145
+
122
146
# part of EventHandlerPlugin
123
147
def on_event (self , event , payload ):
124
148
if not hasattr (self , "d" ): # Ignore any messages arriving before init
125
149
return
126
150
127
- # Access current file via `get_current_job` instead of `is_current_file` because the latter may go away soon
128
- # See https://docs.octoprint.org/en/master/modules/printer.html#octoprint.printer.PrinterInterface.is_current_file
129
- # Avoid using payload.get('path') as some events may not express path info.
130
151
current_file = self ._printer .get_current_job ().get ("file" , {}).get ("name" )
131
152
is_current_path = current_file == self .d .current_path ()
132
153
is_finish_script = current_file == TEMP_FILES [FINISHED_SCRIPT_KEY ]
@@ -146,64 +167,38 @@ def on_event(self, event, payload):
146
167
if self ._printer .is_current_file (path , sd = False ):
147
168
return
148
169
self ._rm_temp_files ()
149
- elif (is_current_path or is_finish_script ) and event == Events .PRINT_DONE :
150
- self .d .on_print_success (is_finish_script )
151
- self .paused = False
152
- self ._msg (type = "reload" ) # reload UI
153
- elif (
154
- is_current_path
155
- and event == Events .PRINT_FAILED
156
- and payload ["reason" ] != "cancelled"
157
- ):
170
+ elif event == Events .PRINT_DONE :
171
+ self .update (DA .SUCCESS )
172
+ elif event == Events .PRINT_FAILED :
158
173
# Note that cancelled events are already handled directly with Events.PRINT_CANCELLED
159
- self .d . on_print_failed ( )
160
- self . paused = False
161
- self . _msg ( type = "reload" ) # reload UI
162
- elif is_current_path and event == Events . PRINT_CANCELLED :
163
- self .d . on_print_cancelled ( initiator = payload . get ( 'user' , None ) )
164
- self . paused = False
165
- self ._msg ( type = "reload" ) # reload UI
174
+ self .update ( DA . FAILURE )
175
+ elif event == Events . PRINT_CANCELLED :
176
+ print ( payload . get ( 'user' ))
177
+ if payload . get ( 'user' ) is not None :
178
+ self .update ( DA . DEACTIVATE )
179
+ else :
180
+ self .update ( DA . TICK )
166
181
elif (
167
182
is_current_path
168
183
and tsd_command is not None
169
184
and event == tsd_command
170
185
and payload .get ("cmd" ) == "pause"
171
186
and payload .get ("initiator" ) == "system"
172
187
):
173
- self ._logger .info (
174
- "Got spaghetti detection event; flagging next pause event for restart"
175
- )
176
- self .next_pause_is_spaghetti = True
188
+ self .update (DA .SPAGHETTI )
177
189
elif is_current_path and event == Events .PRINT_PAUSED :
178
- self .d .on_print_paused (
179
- is_temp_file = (payload ["path" ] in TEMP_FILES .values ()),
180
- is_spaghetti = self .next_pause_is_spaghetti ,
181
- )
182
- self .next_pause_is_spaghetti = False
183
- self .paused = True
184
- self ._msg (type = "reload" ) # reload UI
190
+ self .update (DA .TICK )
185
191
elif is_current_path and event == Events .PRINT_RESUMED :
186
- self .d .on_print_resumed ()
187
- self .paused = False
188
- self ._msg (type = "reload" )
192
+ self .update (DA .TICK )
189
193
elif (
190
194
event == Events .PRINTER_STATE_CHANGED
191
195
and self ._printer .get_state_id () == "OPERATIONAL"
192
196
):
193
- self ._msg ( type = "reload" ) # reload UI
197
+ self .update ( DA . TICK )
194
198
elif event == Events .UPDATED_FILES :
195
199
self ._msg (type = "updatefiles" )
196
200
elif event == Events .SETTINGS_UPDATED :
197
201
self ._update_driver_settings ()
198
- # Play out actions until printer no longer in a state where we can run commands
199
- # Note that PAUSED state is respected so that gcode can include `@pause` commands.
200
- # See https://docs.octoprint.org/en/master/features/atcommands.html
201
- while (
202
- self ._printer .get_state_id () == "OPERATIONAL"
203
- and self .d .pending_actions () > 0
204
- ):
205
- self ._logger .warning ("on_printer_ready" )
206
- self .d .on_printer_ready ()
207
202
208
203
def _write_temp_gcode (self , key ):
209
204
gcode = self ._settings .get ([key ])
@@ -221,6 +216,7 @@ def run_finish_script(self):
221
216
self ._msg ("Print Queue Complete" , type = "complete" )
222
217
path = self ._write_temp_gcode (FINISHED_SCRIPT_KEY )
223
218
self ._printer .select_file (path , sd = False , printAfterSelect = True )
219
+ return path
224
220
225
221
def cancel_print (self ):
226
222
self ._msg ("Print cancelled" , type = "error" )
@@ -252,6 +248,7 @@ def clear_bed(self):
252
248
self .wait_for_bed_cooldown ()
253
249
path = self ._write_temp_gcode (CLEARING_SCRIPT_KEY )
254
250
self ._printer .select_file (path , sd = False , printAfterSelect = True )
251
+ return path
255
252
256
253
def start_print (self , item , clear_bed = True ):
257
254
self ._msg ("Starting print: " + item .name )
@@ -264,6 +261,7 @@ def start_print(self, item, clear_bed=True):
264
261
self ._msg ("File not found: " + item .path , type = "error" )
265
262
except InvalidFileType :
266
263
self ._msg ("File not gcode: " + item .path , type = "error" )
264
+ return item .path
267
265
268
266
def state_json (self , extra_message = None ):
269
267
# Values are stored json-serialized, so we need to create a json string and inject them into it
@@ -278,19 +276,18 @@ def state_json(self, extra_message=None):
278
276
# IMPORTANT: Non-additive changes to this response string must be released in a MAJOR version bump
279
277
# (e.g. 1.4.1 -> 2.0.0).
280
278
resp = '{"active": %s, "status": "%s", "queue": %s%s}' % (
281
- "true" if hasattr ( self , "d" ) and self . d . active else "false" ,
279
+ "true" if self . _active () else "false" ,
282
280
"Initializing" if not hasattr (self , "d" ) else self .d .status ,
283
281
q ,
284
282
extra_message ,
285
283
)
286
284
return resp
287
285
288
- # Listen for resume from printer ("M118 //action:queuego"), only act if actually paused. #from @grtrenchman
286
+ # Listen for resume from printer ("M118 //action:queuego") #from @grtrenchman
289
287
def resume_action_handler (self , comm , line , action , * args , ** kwargs ):
290
288
if not action == "queuego" :
291
289
return
292
- if self .paused :
293
- self .d .set_active ()
290
+ self .update (DA .ACTIVATE )
294
291
295
292
# Public API method returning the full state of the plugin in JSON format.
296
293
# See `state_json()` for return values.
@@ -308,10 +305,7 @@ def set_active(self):
308
305
if not Permissions .PLUGIN_CONTINUOUSPRINT_STARTQUEUE .can ():
309
306
return flask .make_response ("Insufficient Rights" , 403 )
310
307
self ._logger .info ("attempt failed due to insufficient permissions." )
311
- self .d .set_active (
312
- flask .request .form ["active" ] == "true" ,
313
- printer_ready = (self ._printer .get_state_id () == "OPERATIONAL" ),
314
- )
308
+ self .update (DA .ACTIVATE if flask .request .form ["active" ] == "true" else DA .DEACTIVATE )
315
309
return self .state_json ()
316
310
317
311
# PRIVATE API method - may change without warning.
0 commit comments