Luigi Santivetti | 69972f9 | 2019-11-12 22:55:40 +0000 | [diff] [blame^] | 1 | #!/usr/bin/env python |
| 2 | """\ |
| 3 | |
| 4 | Stream g-code to grbl controller |
| 5 | |
| 6 | This script differs from the simple_stream.py script by |
| 7 | tracking the number of characters in grbl's serial read |
| 8 | buffer. This allows grbl to fetch the next line directly |
| 9 | from the serial buffer and does not have to wait for a |
| 10 | response from the computer. This effectively adds another |
| 11 | buffer layer to prevent buffer starvation. |
| 12 | |
| 13 | CHANGELOG: |
| 14 | - 20170531: Status report feedback at 1.0 second intervals. |
| 15 | Configurable baudrate and report intervals. Bug fixes. |
| 16 | - 20161212: Added push message feedback for simple streaming |
| 17 | - 20140714: Updated baud rate to 115200. Added a settings |
| 18 | write mode via simple streaming method. MIT-licensed. |
| 19 | |
| 20 | TODO: |
| 21 | - Add realtime control commands during streaming. |
| 22 | |
| 23 | --------------------- |
| 24 | The MIT License (MIT) |
| 25 | |
| 26 | Copyright (c) 2012-2017 Sungeun K. Jeon |
| 27 | |
| 28 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 29 | of this software and associated documentation files (the "Software"), to deal |
| 30 | in the Software without restriction, including without limitation the rights |
| 31 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 32 | copies of the Software, and to permit persons to whom the Software is |
| 33 | furnished to do so, subject to the following conditions: |
| 34 | |
| 35 | The above copyright notice and this permission notice shall be included in |
| 36 | all copies or substantial portions of the Software. |
| 37 | |
| 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 44 | THE SOFTWARE. |
| 45 | --------------------- |
| 46 | """ |
| 47 | |
| 48 | import serial |
| 49 | import re |
| 50 | import time |
| 51 | import sys |
| 52 | import argparse |
| 53 | import threading |
| 54 | |
| 55 | RX_BUFFER_SIZE = 128 |
| 56 | BAUD_RATE = 115200 |
| 57 | ENABLE_STATUS_REPORTS = True |
| 58 | REPORT_INTERVAL = 1.0 # seconds |
| 59 | |
| 60 | is_run = True # Controls query timer |
| 61 | |
| 62 | # Define command line argument interface |
| 63 | parser = argparse.ArgumentParser(description='Stream g-code file to grbl. (pySerial and argparse libraries required)') |
| 64 | parser.add_argument('gcode_file', type=argparse.FileType('r'), |
| 65 | help='g-code filename to be streamed') |
| 66 | parser.add_argument('device_file', |
| 67 | help='serial device path') |
| 68 | parser.add_argument('-q','--quiet',action='store_true', default=False, |
| 69 | help='suppress output text') |
| 70 | parser.add_argument('-s','--settings',action='store_true', default=False, |
| 71 | help='settings write mode') |
| 72 | parser.add_argument('-c','--check',action='store_true', default=False, |
| 73 | help='stream in check mode') |
| 74 | args = parser.parse_args() |
| 75 | |
| 76 | # Periodic timer to query for status reports |
| 77 | # TODO: Need to track down why this doesn't restart consistently before a release. |
| 78 | def send_status_query(): |
| 79 | s.write('?') |
| 80 | |
| 81 | def periodic_timer() : |
| 82 | while is_run: |
| 83 | send_status_query() |
| 84 | time.sleep(REPORT_INTERVAL) |
| 85 | |
| 86 | |
| 87 | # Initialize |
| 88 | s = serial.Serial(args.device_file,BAUD_RATE) |
| 89 | f = args.gcode_file |
| 90 | verbose = True |
| 91 | if args.quiet : verbose = False |
| 92 | settings_mode = False |
| 93 | if args.settings : settings_mode = True |
| 94 | check_mode = False |
| 95 | if args.check : check_mode = True |
| 96 | |
| 97 | # Wake up grbl |
| 98 | print "Initializing Grbl..." |
| 99 | s.write("\r\n\r\n") |
| 100 | |
| 101 | # Wait for grbl to initialize and flush startup text in serial input |
| 102 | time.sleep(2) |
| 103 | s.flushInput() |
| 104 | |
| 105 | if check_mode : |
| 106 | print "Enabling Grbl Check-Mode: SND: [$C]", |
| 107 | s.write("$C\n") |
| 108 | while 1: |
| 109 | grbl_out = s.readline().strip() # Wait for grbl response with carriage return |
| 110 | if grbl_out.find('error') >= 0 : |
| 111 | print "REC:",grbl_out |
| 112 | print " Failed to set Grbl check-mode. Aborting..." |
| 113 | quit() |
| 114 | elif grbl_out.find('ok') >= 0 : |
| 115 | if verbose: print 'REC:',grbl_out |
| 116 | break |
| 117 | |
| 118 | start_time = time.time(); |
| 119 | |
| 120 | # Start status report periodic timer |
| 121 | if ENABLE_STATUS_REPORTS : |
| 122 | timerThread = threading.Thread(target=periodic_timer) |
| 123 | timerThread.daemon = True |
| 124 | timerThread.start() |
| 125 | |
| 126 | # Stream g-code to grbl |
| 127 | l_count = 0 |
| 128 | error_count = 0 |
| 129 | if settings_mode: |
| 130 | # Send settings file via simple call-response streaming method. Settings must be streamed |
| 131 | # in this manner since the EEPROM accessing cycles shut-off the serial interrupt. |
| 132 | print "SETTINGS MODE: Streaming", args.gcode_file.name, " to ", args.device_file |
| 133 | for line in f: |
| 134 | l_count += 1 # Iterate line counter |
| 135 | # l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize |
| 136 | l_block = line.strip() # Strip all EOL characters for consistency |
| 137 | if verbose: print "SND>"+str(l_count)+": \"" + l_block + "\"" |
| 138 | s.write(l_block + '\n') # Send g-code block to grbl |
| 139 | while 1: |
| 140 | grbl_out = s.readline().strip() # Wait for grbl response with carriage return |
| 141 | if grbl_out.find('ok') >= 0 : |
| 142 | if verbose: print " REC<"+str(l_count)+": \""+grbl_out+"\"" |
| 143 | break |
| 144 | elif grbl_out.find('error') >= 0 : |
| 145 | if verbose: print " REC<"+str(l_count)+": \""+grbl_out+"\"" |
| 146 | error_count += 1 |
| 147 | break |
| 148 | else: |
| 149 | print " MSG: \""+grbl_out+"\"" |
| 150 | else: |
| 151 | # Send g-code program via a more agressive streaming protocol that forces characters into |
| 152 | # Grbl's serial read buffer to ensure Grbl has immediate access to the next g-code command |
| 153 | # rather than wait for the call-response serial protocol to finish. This is done by careful |
| 154 | # counting of the number of characters sent by the streamer to Grbl and tracking Grbl's |
| 155 | # responses, such that we never overflow Grbl's serial read buffer. |
| 156 | g_count = 0 |
| 157 | c_line = [] |
| 158 | for line in f: |
| 159 | l_count += 1 # Iterate line counter |
| 160 | l_block = re.sub('\s|\(.*?\)','',line).upper() # Strip comments/spaces/new line and capitalize |
| 161 | # l_block = line.strip() |
| 162 | c_line.append(len(l_block)+1) # Track number of characters in grbl serial read buffer |
| 163 | grbl_out = '' |
| 164 | while sum(c_line) >= RX_BUFFER_SIZE-1 | s.inWaiting() : |
| 165 | out_temp = s.readline().strip() # Wait for grbl response |
| 166 | if out_temp.find('ok') < 0 and out_temp.find('error') < 0 : |
| 167 | print " MSG: \""+out_temp+"\"" # Debug response |
| 168 | else : |
| 169 | if out_temp.find('error') >= 0 : error_count += 1 |
| 170 | g_count += 1 # Iterate g-code counter |
| 171 | if verbose: print " REC<"+str(g_count)+": \""+out_temp+"\"" |
| 172 | del c_line[0] # Delete the block character count corresponding to the last 'ok' |
| 173 | s.write(l_block + '\n') # Send g-code block to grbl |
| 174 | if verbose: print "SND>"+str(l_count)+": \"" + l_block + "\"" |
| 175 | # Wait until all responses have been received. |
| 176 | while l_count > g_count : |
| 177 | out_temp = s.readline().strip() # Wait for grbl response |
| 178 | if out_temp.find('ok') < 0 and out_temp.find('error') < 0 : |
| 179 | print " MSG: \""+out_temp+"\"" # Debug response |
| 180 | else : |
| 181 | if out_temp.find('error') >= 0 : error_count += 1 |
| 182 | g_count += 1 # Iterate g-code counter |
| 183 | del c_line[0] # Delete the block character count corresponding to the last 'ok' |
| 184 | if verbose: print " REC<"+str(g_count)+": \""+out_temp + "\"" |
| 185 | |
| 186 | # Wait for user input after streaming is completed |
| 187 | print "\nG-code streaming finished!" |
| 188 | end_time = time.time(); |
| 189 | is_run = False; |
| 190 | print " Time elapsed: ",end_time-start_time,"\n" |
| 191 | if check_mode : |
| 192 | if error_count > 0 : |
| 193 | print "CHECK FAILED:",error_count,"errors found! See output for details.\n" |
| 194 | else : |
| 195 | print "CHECK PASSED: No errors found in g-code program.\n" |
| 196 | else : |
| 197 | print "WARNING: Wait until Grbl completes buffered g-code blocks before exiting." |
| 198 | raw_input(" Press <Enter> to exit and disable Grbl.") |
| 199 | |
| 200 | # Close file and serial port |
| 201 | f.close() |
| 202 | s.close() |