@@ -54,6 +54,8 @@ class CNCDriver(object):
54
54
55
55
DISENGAGE_FEEDBACK = 'M63'
56
56
57
+ RESET = 'reset'
58
+
57
59
ABSOLUTE_POSITIONING = 'G90'
58
60
RELATIVE_POSITIONING = 'G91'
59
61
@@ -90,9 +92,9 @@ class CNCDriver(object):
90
92
ot_version = None
91
93
92
94
def __init__ (self ):
93
-
95
+ self . halted = Event ()
94
96
self .stopped = Event ()
95
- self .can_move = Event ()
97
+ self .do_not_pause = Event ()
96
98
self .resume ()
97
99
self .current_commands = []
98
100
@@ -125,6 +127,12 @@ def _copy_defaults_to_settings(self):
125
127
if key not in self .saved_settings [n ]:
126
128
self .saved_settings [n ][key ] = val
127
129
130
+ def _set_step_per_mm_from_config (self ):
131
+ for axis in 'xyz' :
132
+ value = self .saved_settings ['config' ].get (
133
+ self .CONFIG_STEPS_PER_MM [axis ])
134
+ self .set_steps_per_mm (axis , value )
135
+
128
136
def _apply_settings (self ):
129
137
self .serial_timeout = float (
130
138
self .saved_settings ['serial' ].get ('timeout' , 0.1 ))
@@ -133,6 +141,9 @@ def _apply_settings(self):
133
141
134
142
self .head_speed = int (
135
143
self .saved_settings ['state' ].get ('head_speed' , 3000 ))
144
+ self .plunger_speed = json .loads (
145
+ self .saved_settings ['state' ].get (
146
+ 'plunger_speed' , '{"a":300,"b",300}' ))
136
147
137
148
self .COMPATIBLE_FIRMARE = json .loads (
138
149
self .saved_settings ['versions' ].get ('firmware' , '[]' ))
@@ -194,47 +205,56 @@ def get_serial_ports_list(self):
194
205
return result
195
206
196
207
def disconnect (self ):
197
- if self .is_connected ():
208
+ if self .is_connected () and self . connection :
198
209
self .connection .close ()
199
210
self .connection = None
200
211
201
212
def connect (self , device ):
202
213
self .connection = device
203
- self .reset_port ()
214
+ self .toggle_port ()
204
215
log .debug ("Connected to {}" .format (device ))
216
+
217
+ self .turn_off_feedback ()
205
218
self .versions_compatible ()
206
- # set the previously saved steps_per_mm values for X and Y
207
219
if self .ignore_smoothie_sd :
208
- for axis in 'xyz' :
209
- self .set_steps_per_mm (
210
- axis , self .saved_settings ['config' ].get (
211
- self .CONFIG_STEPS_PER_MM [axis ]))
220
+ self ._set_step_per_mm_from_config ()
221
+
212
222
return self .calm_down ()
213
223
214
224
def is_connected (self ):
215
225
return self .connection and self .connection .isOpen ()
216
226
217
- def reset_port (self ):
227
+ def toggle_port (self ):
218
228
self .connection .close ()
219
229
self .connection .open ()
220
230
self .flush_port ()
221
231
222
- self .turn_off_feedback ()
223
-
224
232
def pause (self ):
225
- self .can_move .clear ()
233
+ self .halted .clear ()
234
+ self .stopped .clear ()
235
+ self .do_not_pause .clear ()
226
236
227
237
def resume (self ):
228
- self .can_move . set ()
238
+ self .halted . clear ()
229
239
self .stopped .clear ()
240
+ self .do_not_pause .set ()
230
241
231
242
def stop (self ):
243
+ self .halted .clear ()
232
244
self .stopped .set ()
233
- self .can_move .set ()
245
+ self .do_not_pause .set ()
246
+
247
+ def halt (self ):
248
+ self .halted .set ()
249
+ self .stopped .set ()
250
+ self .do_not_pause .set ()
234
251
235
252
def check_paused_stopped (self ):
236
- self .can_move .wait ()
253
+ self .do_not_pause .wait ()
237
254
if self .stopped .is_set ():
255
+ if self .halted .is_set ():
256
+ self .send_command (self .HALT )
257
+ self .calm_down ()
238
258
self .resume ()
239
259
raise RuntimeWarning (self .STOPPED )
240
260
@@ -256,16 +276,27 @@ def send_command(self, command, **kwargs):
256
276
return response
257
277
258
278
def write_to_serial (self , data , max_tries = 10 , try_interval = 0.2 ):
279
+ """
280
+ Sends data string to serial ports
281
+
282
+ Returns data immediately read from port after write
283
+
284
+ Raises RuntimeError write fails or connection times out
285
+ """
259
286
log .debug ("Write: {}" .format (str (data ).encode ()))
260
287
if self .is_connected ():
261
- self .connection .write (str (data ).encode ())
288
+ try :
289
+ self .connection .write (str (data ).encode ())
290
+ except Exception as e :
291
+ self .disconnect ()
292
+ raise RuntimeError ('Lost connection with serial port' ) from e
262
293
return self .wait_for_response ()
263
294
elif self .connection is None :
264
295
msg = "No connection found."
265
296
log .warn (msg )
266
297
raise RuntimeError (msg )
267
298
elif max_tries > 0 :
268
- self .reset_port ()
299
+ self .toggle_port ()
269
300
return self .write_to_serial (
270
301
data , max_tries = max_tries - 1 , try_interval = try_interval
271
302
)
@@ -276,9 +307,15 @@ def write_to_serial(self, data, max_tries=10, try_interval=0.2):
276
307
raise RuntimeError (msg )
277
308
278
309
def wait_for_response (self , timeout = 20.0 ):
310
+ """
311
+ Repeatedly reads from serial port until data is received,
312
+ or timeout is exceeded
313
+
314
+ Raises RuntimeWarning() if no response was recieved before timeout
315
+ """
279
316
count = 0
280
317
max_retries = int (timeout / self .serial_timeout )
281
- while count < max_retries :
318
+ while self . is_connected () and count < max_retries :
282
319
count = count + 1
283
320
out = self .readline_from_serial ()
284
321
if out :
@@ -292,23 +329,39 @@ def wait_for_response(self, timeout=20.0):
292
329
log .debug (
293
330
"Waiting {} lines for response." .format (count )
294
331
)
295
- raise RuntimeWarning ('no response after {} seconds' .format (timeout ))
332
+ raise RuntimeWarning (
333
+ 'No response from serial port after {} seconds' .format (timeout ))
296
334
297
335
def flush_port (self ):
298
336
while self .readline_from_serial ():
299
337
time .sleep (self .serial_timeout )
300
338
301
339
def readline_from_serial (self ):
340
+ """
341
+ Attempt to read a line of data from serial port
342
+
343
+ Raises RuntimeWarning if read fails on serial port
344
+ """
302
345
msg = b''
303
- if self .is_connected ():
304
- # serial.readline() returns an empty byte string if it times out
305
- msg = self .connection .readline ().strip ()
306
- if msg :
307
- log .debug ("Read: {}" .format (msg ))
346
+ try :
347
+ msg = self .connection .readline ()
348
+ msg = msg .strip ()
349
+ except Exception as e :
350
+ self .disconnect ()
351
+ raise RuntimeWarning ('Lost connection with serial port' ) from e
352
+ if msg :
353
+ log .debug ("Read: {}" .format (msg ))
354
+ self .detect_limit_hit (msg ) # raises RuntimeWarning if switch hit
355
+
356
+ return msg
357
+
358
+ def detect_limit_hit (self , msg ):
359
+ """
360
+ Detect if it hit a home switch
308
361
309
- # detect if it hit a home switch
362
+ Raises RuntimeWarning if Smoothie reports a limit hit
363
+ """
310
364
if b'!!' in msg or b'limit' in msg :
311
- # TODO (andy): allow this to bubble up so UI is notified
312
365
log .debug ('home switch hit' )
313
366
self .flush_port ()
314
367
self .calm_down ()
@@ -319,8 +372,6 @@ def readline_from_serial(self):
319
372
axis = ax
320
373
raise RuntimeWarning ('{} limit switch hit' .format (axis .upper ()))
321
374
322
- return msg
323
-
324
375
def set_coordinate_system (self , mode ):
325
376
if mode == 'absolute' :
326
377
self .send_command (self .ABSOLUTE_POSITIONING )
@@ -329,22 +380,10 @@ def set_coordinate_system(self, mode):
329
380
else :
330
381
raise ValueError ('Invalid coordinate mode: ' + mode )
331
382
332
- def move_plunger (self , mode = 'absolute' , ** kwargs ):
333
-
383
+ def move (self , mode = 'absolute' , ** kwargs ):
334
384
self .set_coordinate_system (mode )
335
385
336
- args = {axis .upper (): kwargs .get (axis )
337
- for axis in 'ab'
338
- if axis in kwargs }
339
-
340
- return self .consume_move_commands (args )
341
-
342
- def move_head (self , mode = 'absolute' , ** kwargs ):
343
-
344
- self .set_coordinate_system (mode )
345
- self .set_head_speed ()
346
386
current = self .get_head_position ()['target' ]
347
-
348
387
log .debug ('Current Head Position: {}' .format (current ))
349
388
target_point = {
350
389
axis : kwargs .get (
@@ -355,20 +394,30 @@ def move_head(self, mode='absolute', **kwargs):
355
394
}
356
395
log .debug ('Destination: {}' .format (target_point ))
357
396
358
- target_vector = Vector (target_point )
397
+ flipped_vector = self .flip_coordinates (
398
+ Vector (target_point ), mode )
399
+ for axis in 'xyz' :
400
+ kwargs [axis ] = flipped_vector [axis ]
359
401
360
- flipped_vector = self .flip_coordinates (target_vector , mode )
361
- args = (
362
- {axis .upper (): flipped_vector [axis ]
363
- for axis in 'xyz' if axis in kwargs }
364
- )
402
+ args = {axis .upper (): kwargs .get (axis )
403
+ for axis in 'xyzab'
404
+ if axis in kwargs }
405
+ args .update ({"F" : self .head_speed })
406
+ args .update ({"a" : self .plunger_speed ['a' ]})
407
+ args .update ({"b" : self .plunger_speed ['b' ]})
365
408
366
409
return self .consume_move_commands (args )
367
410
411
+ def move_plunger (self , mode = 'absolute' , ** kwargs ):
412
+ return self .move (mode , ** kwargs )
413
+
414
+ def move_head (self , mode = 'absolute' , ** kwargs ):
415
+ return self .move (mode , ** kwargs )
416
+
368
417
def consume_move_commands (self , args ):
369
418
self .check_paused_stopped ()
370
419
371
- log .debug ("Moving head : {}" .format (args ))
420
+ log .debug ("Moving : {}" .format (args ))
372
421
res = self .send_command (self .MOVE , ** args )
373
422
if res != b'ok' :
374
423
return (False , self .SMOOTHIE_ERROR )
@@ -399,6 +448,7 @@ def wait_for_arrival(self, tolerance=0.1):
399
448
arrived = False
400
449
coords = self .get_position ()
401
450
while not arrived :
451
+ self .check_paused_stopped ()
402
452
coords = self .get_position ()
403
453
diff = {}
404
454
for axis in coords .get ('target' , {}):
@@ -461,6 +511,11 @@ def calm_down(self):
461
511
res = self .send_command (self .CALM_DOWN )
462
512
return res == b'ok'
463
513
514
+ def reset (self ):
515
+ res = self .send_command (self .RESET )
516
+ if b'Rebooting' in res :
517
+ self .disconnect ()
518
+
464
519
def set_position (self , ** kwargs ):
465
520
uppercase_args = {}
466
521
for key in kwargs :
@@ -533,9 +588,7 @@ def set_head_speed(self, rate=None):
533
588
def set_plunger_speed (self , rate , axis ):
534
589
if axis .lower () not in 'ab' :
535
590
raise ValueError ('Axis {} not supported' .format (axis ))
536
- kwargs = {axis .lower (): rate }
537
- res = self .send_command (self .SET_SPEED , ** kwargs )
538
- return res == b'ok'
591
+ self .plunger_speed [axis ] = rate
539
592
540
593
def versions_compatible (self ):
541
594
self .get_ot_version ()
0 commit comments