Ejemplo n.º 1
1
Archivo: Sender.py Proyecto: YUESS/bCNC
	def __init__(self):
		# Global variables
		self.history     = []
		self._historyPos = None
		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc

		self.log         = Queue()	# Log queue returned from GRBL
		self.queue       = Queue()	# Command queue to send to GRBL
		self.pendant     = Queue()	# Command queue to be executed from Pendant
		self.serial      = None
		self.thread      = None
		self.controller  = Utils.CONTROLLER["Grbl"]

		self._posUpdate  = False	# Update position
		self._probeUpdate= False	# Update probe
		self._gUpdate    = False	# Update $G
		self._update     = None		# Generic update

		self.running     = False
		self._runLines   = 0
		self._stop       = False	# Raise to stop current run
		self._quit       = 0
		self._pause      = False	# machine is on Hold
		self._alarm      = True		# Display alarm message if true
		self._msg        = None
		self._sumcline   = 0
		self._lastFeed   = 0
		self._newFeed    = 0
Ejemplo n.º 2
0
    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None
        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self._runLines = 0
        self._stop = False  # Raise to stop current run
        self._quit = 0
        self._pause = False  # machine is on Hold
        self._alarm = True
        self._msg = None
Ejemplo n.º 3
0
    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None
        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to be send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None
        self.controller = Utils.CONTROLLER["Grbl"]

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self._runLines = 0
        self._quit = 0  # Quit counter to exit program
        self._stop = False  # Raise to stop current run
        self._pause = False  # machine is on Hold
        self._alarm = True  # Display alarm message if true
        self._msg = None
        self._sumcline = 0
        self._lastFeed = 0
        self._newFeed = 0

        self._onStart = ""
        self._onStop = ""
Ejemplo n.º 4
0
Archivo: Sender.py Proyecto: Sci33/bCNC
	def __init__(self):
		# Global variables
		self.history     = []
		self._historyPos = None
		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc
		self.wait  = False	# wait for commands to complete

		self.log         = Queue()	# Log queue returned from GRBL
		self.queue       = Queue()	# Command queue to send to GRBL
		self.pendant     = Queue()	# Command queue to be executed from Pendant
		self.serial      = None
		self.thread      = None

		self._posUpdate  = False
		self._probeUpdate= False
		self._gUpdate    = False
		self.running     = False
		self._runLines   = 0
		self._stop       = False	# Raise to stop current run
		self._quit       = 0
		self._pause      = False	# machine is on Hold
		self._alarm      = True
		self._msg        = None
		self._update     = None
Ejemplo n.º 5
0
    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None

        #self.mcontrol     = None
        self.controllers = {}
        self.controllerLoad()
        self.controllerSet("GRBL1")

        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to be send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self.runningPrev = None
        self.cleanAfter = False
        self._runLines = 0
        self._quit = 0  # Quit counter to exit program
        self._stop = False  # Raise to stop current run
        self._pause = False  # machine is on Hold
        self._alarm = True  # Display alarm message if true
        self._msg = None
        self._sumcline = 0
        self._lastFeed = 0
        self._newFeed = 0

        self._onStart = ""
        self._onStop = ""

        self._gpoll = G_POLL

        # For the case where there is a Pendant with direct access to the GRBL controller
        # we will need to poll the offsets more often to pick up the deltas.
        self._pollOffsets = False
Ejemplo n.º 6
0
Archivo: Sender.py Proyecto: onekk/bCNC
	def __init__(self):
		# Global variables
		self.history	 = []
		self._historyPos = None

		#self.mcontrol     = None
		self.controllers = {}
		self.controllerLoad()
		self.controllerSet("GRBL1")

		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc

		self.log	 = Queue()	# Log queue returned from GRBL
		self.queue	 = Queue()	# Command queue to be send to GRBL
		self.pendant	 = Queue()	# Command queue to be executed from Pendant
		self.serial	 = None
		self.thread	 = None

		self._posUpdate  = False	# Update position
		self._probeUpdate= False	# Update probe
		self._gUpdate	 = False	# Update $G
		self._update	 = None		# Generic update

		self.running	 = False
		self.runningPrev = None
		self.cleanAfter  = False
		self._runLines	 = 0
		self._quit	 = 0		# Quit counter to exit program
		self._stop	 = False	# Raise to stop current run
		self._pause	 = False	# machine is on Hold
		self._alarm	 = True		# Display alarm message if true
		self._msg	 = None
		self._sumcline	 = 0
		self._lastFeed	 = 0
		self._newFeed	 = 0

		self._onStart    = ""
		self._onStop     = ""
Ejemplo n.º 7
0
class Sender:
    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None
        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self._runLines = 0
        self._stop = False  # Raise to stop current run
        self._quit = 0
        self._pause = False  # machine is on Hold
        self._alarm = True
        self._msg = None

    #----------------------------------------------------------------------
    def quit(self, event=None):
        self.saveConfig()
        Pendant.stop()

    #----------------------------------------------------------------------
    def loadConfig(self):
        Pendant.port = Utils.getInt("Connection", "pendantport", Pendant.port)
        GCode.LOOP_MERGE = Utils.getBool("File", "dxfloopmerge")
        self.loadHistory()

    #----------------------------------------------------------------------
    def saveConfig(self):
        self.saveHistory()

    #----------------------------------------------------------------------
    def loadHistory(self):
        try:
            f = open(Utils.hisFile, "r")
        except:
            return
        self.history = [x.strip() for x in f]
        f.close()

    #----------------------------------------------------------------------
    def saveHistory(self):
        try:
            f = open(Utils.hisFile, "w")
        except:
            return
        f.write("\n".join(self.history))
        f.close()

    #----------------------------------------------------------------------
    # Evaluate a line for possible expressions
    # can return a python exception, needs to be catched
    #----------------------------------------------------------------------
    def evaluate(self, line):
        return self.gcode.evaluate(CNC.parseLine2(line, True))

    #----------------------------------------------------------------------
    # Execute a line as gcode if pattern matches
    # @return True on success
    #         False otherwise
    #----------------------------------------------------------------------
    def executeGcode(self, line):
        if isinstance(line, tuple):
            self.sendGrbl(line)
            return True

        elif line[0] in ("$", "!", "~", "?", "(", "@") or GPAT.match(line):
            self.sendGrbl(line + "\n")
            return True

        return False

    #----------------------------------------------------------------------
    # Execute a single command
    #----------------------------------------------------------------------
    def executeCommand(self, line):
        #print
        #print "<<<",line
        #try:
        #	line = self.gcode.evaluate(CNC.parseLine2(line,True))
        #except:
        #	return "Evaluation error", sys.exc_info()[1]
        #print ">>>",line

        if line is None: return

        oline = line.strip()
        line = oline.replace(",", " ").split()
        cmd = line[0].upper()

        # ABS*OLUTE: Set absolute coordinates
        if rexx.abbrev("ABSOLUTE", cmd, 3):
            self.sendGrbl("G90\n")

        # HELP: open browser to display help
        elif cmd == "HELP":
            self.help()

        # HOME: perform a homing cycle
        elif cmd == "HOME":
            self.home()

        # LO*AD [filename]: load filename containing g-code
        elif rexx.abbrev("LOAD", cmd, 2):
            self.load(line[1])

        # OPEN: open serial connection to grbl
        # CLOSE: close serial connection to grbl
        elif cmd in ("OPEN", "CLOSE"):
            self.openClose()

        # QU*IT: quit program
        # EX*IT: exit program
        elif rexx.abbrev("QUIT", cmd, 2) or rexx.abbrev("EXIT", cmd, 2):
            self.quit()

        # PAUSE: pause cycle
        elif cmd == "PAUSE":
            self.pause()

        # RESUME: resume
        elif cmd == "RESUME":
            self.resume()

        # FEEDHOLD: feedhold
        elif cmd == "FEEDHOLD":
            self.feedHold()

        # REL*ATIVE: switch to relative coordinates
        elif rexx.abbrev("RELATIVE", cmd, 3):
            self.sendGrbl("G91\n")

        # RESET: perform a soft reset to grbl
        elif cmd == "RESET":
            self.softReset()

        # RUN: run g-code
        elif cmd == "RUN":
            self.run()

        # SAFE [z]: safe z to move
        elif cmd == "SAFE":
            try:
                self.cnc.safe = float(line[1])
            except:
                pass
            self.statusbar["text"] = "Safe Z= %g" % (self.cnc.safe)

        # SA*VE [filename]: save to filename or to default name
        elif rexx.abbrev("SAVE", cmd, 2):
            if len(line) > 1:
                self.save(line[1])
            else:
                self.saveAll()

        # SENDHEX: send a hex-char in grbl
        elif cmd == "SENDHEX":
            self.sendHex(line[1])

        # STOP: stop current run
        elif cmd == "STOP":
            self.stopRun()

        # UNL*OCK: unlock grbl
        elif rexx.abbrev("UNLOCK", cmd, 3):
            self.unlock()

        else:
            return _("unknown command"), _("Invalid command %s") % (oline)

    #----------------------------------------------------------------------
    def help(self, event=None):
        webbrowser.open(WIKI, new=2)

    #----------------------------------------------------------------------
    def loadRecent(self, recent):
        filename = Utils.getRecent(recent)
        if filename is None: return
        self.load(filename)

    #----------------------------------------------------------------------
    def _loadRecent0(self, event):
        self.loadRecent(0)

    def _loadRecent1(self, event):
        self.loadRecent(1)

    def _loadRecent2(self, event):
        self.loadRecent(2)

    def _loadRecent3(self, event):
        self.loadRecent(3)

    def _loadRecent4(self, event):
        self.loadRecent(4)

    def _loadRecent5(self, event):
        self.loadRecent(5)

    def _loadRecent6(self, event):
        self.loadRecent(6)

    def _loadRecent7(self, event):
        self.loadRecent(7)

    def _loadRecent8(self, event):
        self.loadRecent(8)

    def _loadRecent9(self, event):
        self.loadRecent(9)

    #----------------------------------------------------------------------
    def _saveConfigFile(self, filename=None):
        if filename is None:
            filename = self.gcode.filename
        Utils.setUtf("File", "dir", os.path.dirname(os.path.abspath(filename)))
        Utils.setUtf("File", "file", os.path.basename(filename))
        Utils.setUtf("File", "probe",
                     os.path.basename(self.gcode.probe.filename))

    #----------------------------------------------------------------------
    # Load a file into editor
    #----------------------------------------------------------------------
    def load(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe":
            if filename is not None:
                self.gcode.probe.filename = filename
                self._saveConfigFile()
            self.gcode.probe.load(filename)
        elif ext == ".stl":
            # FIXME: implements solid import???
            pass
        elif ext == ".dxf":
            self.gcode.init()
            self.gcode.importDXF(filename)
            self._saveConfigFile(filename)
        else:
            self.gcode.load(filename)
            self._saveConfigFile()
        Utils.addRecent(filename)

    #----------------------------------------------------------------------
    def save(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe":
            # save probe
            if filename is not None:
                self.gcode.probe.filename = filename
                self._saveConfigFile()
            if not self.gcode.probe.isEmpty():
                self.gcode.probe.save()
        elif ext == ".stl":
            #save probe as STL
            self.gcode.probe.saveAsSTL(filename)
        elif ext == ".dxf":
            return self.gcode.saveDXF(filename)
        else:
            if filename is not None:
                self.gcode.filename = filename
                self._saveConfigFile()
            Utils.addRecent(self.gcode.filename)
            return self.gcode.save()

    #----------------------------------------------------------------------
    def saveAll(self, event=None):
        if self.gcode.filename:
            self.save(self.gcode.filename)
            if self.gcode.probe.filename:
                self.save(self.gcode.probe.filename)
        return "break"

    #----------------------------------------------------------------------
    # Open serial port
    #----------------------------------------------------------------------
    def open(self, device, baudrate):
        self.serial = serial.Serial(device,
                                    baudrate,
                                    bytesize=serial.EIGHTBITS,
                                    parity=serial.PARITY_NONE,
                                    stopbits=serial.STOPBITS_ONE,
                                    timeout=0.1,
                                    xonxoff=False,
                                    rtscts=False)
        # Toggle DTR to reset Arduino
        self.serial.setDTR(0)
        time.sleep(1)
        CNC.vars["state"] = CONNECTED
        CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
        #self.state.config(text=CNC.vars["state"],
        #		background=CNC.vars["color"])
        # toss any data already received, see
        # http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
        self.serial.flushInput()
        self.serial.setDTR(1)
        time.sleep(1)
        self.serial.write(b"\r\n\r\n")
        self._gcount = 0
        self._alarm = True
        self.thread = threading.Thread(target=self.serialIO)
        self.thread.start()
        return True

#	#----------------------------------------------------------------------
#	def close(self):
#		if self.serial is None: return
#		try:
#			self.stopRun()
#		except:
#			pass
#		self._runLines = 0
#		self.thread = None
#		time.sleep(1)
#		self.serial.close()
#		self.serial = None
#		CNC.vars["state"] = NOT_CONNECTED
#		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
#		try:
#			self.state.config(text=CNC.vars["state"],
#					background=CNC.vars["color"])
#		except TclError:
#			pass

#----------------------------------------------------------------------
# Send to grbl
#----------------------------------------------------------------------

    def sendGrbl(self, cmd):
        #		sys.stdout.write(">>> %s"%(cmd))
        #		import traceback
        #		traceback.print_stack()
        if self.serial and not self.running:
            self.queue.put(cmd)

    #----------------------------------------------------------------------
    def sendHex(self, hexcode):
        if self.serial is None: return
        self.serial.write(chr(int(hexcode, 16)))
        self.serial.flush()

    #----------------------------------------------------------------------
    def hardReset(self):
        if self.serial is not None:
            self.openClose()
        self.openClose()

    #----------------------------------------------------------------------
    def softReset(self):
        if self.serial:
            self.serial.write(b"\030")

    def unlock(self):
        self._alarm = False
        self.sendGrbl("$X\n")

    def home(self):
        self._alarm = False
        self.sendGrbl("$H\n")

    #----------------------------------------------------------------------
    def viewSettings(self):
        self.sendGrbl("$$\n")

    def viewParameters(self):
        self.sendGrbl("$#\n$G\n")

    def viewState(self):
        self.sendGrbl("$G\n")

    def viewBuild(self):
        self.sendGrbl("$I\n")

    def viewStartup(self):
        self.sendGrbl("$N\n")

    def checkGcode(self):
        self.sendGrbl("$C\n")

    def grblHelp(self):
        self.sendGrbl("$\n")

    def grblRestoreSettings(self):
        self.sendGrbl("$RST=$\n")

    def grblRestoreWCS(self):
        self.sendGrbl("$RST=#\n")

    def grblRestoreAll(self):
        self.sendGrbl("$RST=#\n")

    #----------------------------------------------------------------------
    def goto(self, x=None, y=None, z=None):
        cmd = "G90G0"
        if x is not None: cmd += "X%g" % (x)
        if y is not None: cmd += "Y%g" % (y)
        if z is not None: cmd += "Z%g" % (z)
        self.sendGrbl("%s\n" % (cmd))

    #----------------------------------------------------------------------
    def _wcsSet(self, x, y, z):
        p = WCS.index(CNC.vars["WCS"])
        if p < 6:
            cmd = "G10L20P%d" % (p + 1)
        elif p == 6:
            cmd = "G28.1"
        elif p == 7:
            cmd = "G30.1"
        elif p == 8:
            cmd = "G92"

        if x is not None and abs(x) < 10000.0: cmd += "X" + str(x)
        if y is not None and abs(y) < 10000.0: cmd += "Y" + str(y)
        if z is not None and abs(z) < 10000.0: cmd += "Z" + str(z)
        self.sendGrbl(cmd + "\n$#\n")
        self.event_generate("<<Status>>",
                            data=_("Set workspace %s to X%s Y%s Z%s") %
                            (WCS[p], str(x), str(y), str(z)))
        self.event_generate("<<CanvasFocus>>")

    #----------------------------------------------------------------------
    def feedHold(self, event=None):
        if event is not None and not self.acceptKey(True): return
        if self.serial is None: return
        self.serial.write(b"!")
        self.serial.flush()
        self._pause = True

    #----------------------------------------------------------------------
    def resume(self, event=None):
        if event is not None and not self.acceptKey(True): return
        if self.serial is None: return
        self.serial.write(b"~")
        self.serial.flush()
        self._msg = None
        self._alarm = False
        self._pause = False

    #----------------------------------------------------------------------
    def pause(self, event=None):
        if self.serial is None: return
        if self._pause:
            self.resume()
        else:
            self.feedHold()

    #----------------------------------------------------------------------
    # FIXME ????
    #----------------------------------------------------------------------
    def g28Command(self):
        self.sendGrbl("G28.1\n")

    #----------------------------------------------------------------------
    # FIXME ????
    #----------------------------------------------------------------------
    def g30Command(self):
        self.sendGrbl("G30.1\n")

    #----------------------------------------------------------------------
    def emptyQueue(self):
        while self.queue.qsize() > 0:
            try:
                self.queue.get_nowait()
            except Empty:
                break

    #----------------------------------------------------------------------
    def initRun(self):
        self._quit = 0
        self._pause = False
        self._paths = None
        self.running = True
        self.disable()
        self.emptyQueue()
        self.queue.put(self.tools["CNC"]["startup"] + "\n")
        time.sleep(1)

    #----------------------------------------------------------------------
    # Called when run is finished
    #----------------------------------------------------------------------
    def runEnded(self):
        self._runLines = 0
        self._quit = 0
        self._pause = False
        self.running = False
        self._msg = None
        self.enable()

    #----------------------------------------------------------------------
    # Stop the current run
    #----------------------------------------------------------------------
    def stopRun(self, event=None):
        self.feedHold()
        self._stop = True
        time.sleep(1)
        self.softReset()
        time.sleep(1)
        self.unlock()
        self.runEnded()

    #----------------------------------------------------------------------
    # thread performing I/O on serial line
    #----------------------------------------------------------------------
    def serialIO(self):
        cline = []  # length of pipeline commands
        sline = []  # pipeline commands
        wait = False  # wait for commands to complete
        tosend = None  # next string to send
        status = False  # waiting for status <...> report
        tr = tg = time.time()  # last time a ? or $G was send to grbl

        while self.thread:
            t = time.time()

            # refresh machine position?
            if t - tr > SERIAL_POLL:
                # Send one ?
                self.serial.write(b"?")
                status = True
                #print ">S> ?"
                tr = t

            # Fetch new command to send if...
            if tosend is None and not wait and not self._pause and self.queue.qsize(
            ) > 0:
                try:
                    tosend = self.queue.get_nowait()
                    #print "+++",repr(tosend)

                    if isinstance(tosend, tuple):
                        # Count executed commands as well
                        self._gcount += 1
                        # wait to empty the grbl buffer
                        if tosend[0] == WAIT:
                            wait = True
                            #print "+++ WAIT ON"
                        elif tosend[0] == PAUSE:
                            if tosend[1] is not None:
                                # show our message on machine status
                                self._msg = tosend[1]
                            # Feed hold
                            # Maybe a M0 would be better?
                            self.serial.write(b"!")
                            #print ">S> !"
                        elif tosend[0] == UPDATE:
                            self._update = tosend[1]
                        tosend = None

                    elif not isinstance(tosend, str):
                        try:
                            tosend = self.gcode.evaluate(tosend)
                            #							if isinstance(tosend, list):
                            #								cline.append(len(tosend[0]))
                            #								sline.append(tosend[0])
                            #								self.log.put((True,tosend[0]))
                            if isinstance(tosend, str):
                                tosend += "\n"
                            else:
                                # Count executed commands as well
                                self._gcount += 1
                            #print "+++ eval=",repr(tosend),type(tosend)
                        except:
                            self.log.put((True, sys.exc_info()[1]))
                            tosend = None
                except Empty:
                    break

                if tosend is not None:
                    # All modification in tosend should be
                    # done before adding it to cline
                    if isinstance(tosend, unicode):
                        tosend = tosend.encode("ascii", "replace")

                    # FIXME should be smarter and apply the feed override
                    # also on cards with out feed (the first time only)
                    # I should track the feed rate for every card
                    # and when it is changed apply a F### command
                    # even if it is not there
                    if CNC.vars["override"] != 100:
                        pat = FEEDPAT.match(tosend)
                        if pat is not None:
                            try:
                                tosend = "%sf%g%s\n" % \
                                 (pat.group(1),
                                  float(pat.group(2))*CNC.vars["override"]/100.0,
                                  pat.group(3))
                            except:
                                pass

                    # Bookkeeping of the buffers
                    sline.append(tosend)
                    cline.append(len(tosend))
                    self.log.put((True, tosend))

            # Anything to receive?
            if self.serial.inWaiting() or tosend is None:
                line = str(self.serial.readline()).strip()
                #print "<R<",repr(line)
                #print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
                if line:
                    if line[0] == "<":
                        pat = STATUSPAT.match(line)
                        if pat:
                            if not status: self.log.put((False, line + "\n"))
                            status = False
                            if not self._alarm:
                                CNC.vars["state"] = pat.group(1)
                            CNC.vars["mx"] = float(pat.group(2))
                            CNC.vars["my"] = float(pat.group(3))
                            CNC.vars["mz"] = float(pat.group(4))
                            CNC.vars["wx"] = float(pat.group(5))
                            CNC.vars["wy"] = float(pat.group(6))
                            CNC.vars["wz"] = float(pat.group(7))
                            self._posUpdate = True

                            if pat.group(1) != "Hold" and self._msg:
                                self._msg = None

                            # Machine is Idle buffer is empty
                            # stop waiting and go on
                            #print "<<< WAIT=",wait,sline,pat.group(1),sum(cline)
                            if wait and not cline and pat.group(1) == "Idle":
                                wait = False
                                #print "<<< NO MORE WAIT"
                        else:
                            self.log.put((False, line + "\n"))

                    elif line[0] == "[":
                        self.log.put((False, line + "\n"))
                        pat = POSPAT.match(line)
                        if pat:
                            if pat.group(1) == "PRB":
                                CNC.vars["prbx"] = float(pat.group(2))
                                CNC.vars["prby"] = float(pat.group(3))
                                CNC.vars["prbz"] = float(pat.group(4))
                                if self.running:
                                    self.gcode.probe.add(
                                        CNC.vars["prbx"] + CNC.vars["wx"] -
                                        CNC.vars["mx"], CNC.vars["prby"] +
                                        CNC.vars["wy"] - CNC.vars["my"],
                                        CNC.vars["prbz"] + CNC.vars["wz"] -
                                        CNC.vars["mz"])
                                self._probeUpdate = True
                            CNC.vars[pat.group(1)] = \
                             [float(pat.group(2)),
                              float(pat.group(3)),
                              float(pat.group(4))]
                        else:
                            pat = TLOPAT.match(line)
                            if pat:
                                CNC.vars[pat.group(1)] = pat.group(2)
                                self._probeUpdate = True
                            else:
                                CNC.vars["G"] = line[1:-1].split()
                                CNC.updateG()
                                self._gUpdate = True

                    else:
                        #print "<r<",repr(line)
                        self.log.put((False, line + "\n"))
                        uline = line.upper()
                        if uline.find("ERROR") == 0 or uline.find(
                                "ALARM") == 0:
                            self._gcount += 1
                            if cline: del cline[0]
                            if sline: CNC.vars["errline"] = sline.pop(0)
                            if not self._alarm: self._posUpdate = True
                            self._alarm = True
                            CNC.vars["state"] = line
                            if self.running:
                                self.emptyQueue()
                                # Dangerous calling state of Tk if not reentrant
                                self.runEnded()
                                tosend = None
                                del cline[:]
                                del sline[:]

                        elif line.find("ok") >= 0:
                            self._gcount += 1
                            if cline: del cline[0]
                            if sline: del sline[0]

            # Received external message to stop
            if self._stop:
                self.emptyQueue()
                tosend = None
                del cline[:]
                del sline[:]
                self._stop = False

            #print "tosend='%s'"%(repr(tosend)),"stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
            if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
                #				if isinstance(tosend, list):
                #					self.serial.write(str(tosend.pop(0)))
                #					if not tosend: tosend = None

                #print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
                self.serial.write(bytes(tosend))
                #				self.serial.flush()

                tosend = None
                if not self.running and t - tg > G_POLL:
                    tosend = b"$G\n"
                    sline.append(tosend)
                    cline.append(len(tosend))
                    tg = t
Ejemplo n.º 8
0
Archivo: Sender.py Proyecto: YUESS/bCNC
class Sender:
	def __init__(self):
		# Global variables
		self.history     = []
		self._historyPos = None
		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc

		self.log         = Queue()	# Log queue returned from GRBL
		self.queue       = Queue()	# Command queue to send to GRBL
		self.pendant     = Queue()	# Command queue to be executed from Pendant
		self.serial      = None
		self.thread      = None
		self.controller  = Utils.CONTROLLER["Grbl"]

		self._posUpdate  = False	# Update position
		self._probeUpdate= False	# Update probe
		self._gUpdate    = False	# Update $G
		self._update     = None		# Generic update

		self.running     = False
		self._runLines   = 0
		self._stop       = False	# Raise to stop current run
		self._quit       = 0
		self._pause      = False	# machine is on Hold
		self._alarm      = True		# Display alarm message if true
		self._msg        = None
		self._sumcline   = 0
		self._lastFeed   = 0
		self._newFeed    = 0

	#----------------------------------------------------------------------
	def quit(self, event=None):
		self.saveConfig()
		Pendant.stop()

	#----------------------------------------------------------------------
	def loadConfig(self):
		self.controller  = Utils.CONTROLLER.get(Utils.getStr("Connection", "controller"), 0)
		Pendant.port     = Utils.getInt("Connection","pendantport",Pendant.port)
		GCode.LOOP_MERGE = Utils.getBool("File","dxfloopmerge")
		self.loadHistory()

	#----------------------------------------------------------------------
	def saveConfig(self):
		self.saveHistory()

	#----------------------------------------------------------------------
	def loadHistory(self):
		try:
			f = open(Utils.hisFile,"r")
		except:
			return
		self.history = [x.strip() for x in f]
		f.close()

	#----------------------------------------------------------------------
	def saveHistory(self):
		try:
			f = open(Utils.hisFile,"w")
		except:
			return
		f.write("\n".join(self.history))
		f.close()

	#----------------------------------------------------------------------
	# Evaluate a line for possible expressions
	# can return a python exception, needs to be catched
	#----------------------------------------------------------------------
	def evaluate(self, line):
		return self.gcode.evaluate(CNC.parseLine2(line,True))

	#----------------------------------------------------------------------
	# Execute a line as gcode if pattern matches
	# @return True on success
	#         False otherwise
	#----------------------------------------------------------------------
	def executeGcode(self, line):
		if isinstance(line, tuple):
			self.sendGCode(line)
			return True

		elif line[0] in ("$","!","~","?","(","@") or GPAT.match(line):
			self.sendGCode(line+"\n")
			return True

		return False

	#----------------------------------------------------------------------
	# Execute a single command
	#----------------------------------------------------------------------
	def executeCommand(self, line):
		#print
		#print "<<<",line
		#try:
		#	line = self.gcode.evaluate(CNC.parseLine2(line,True))
		#except:
		#	return "Evaluation error", sys.exc_info()[1]
		#print ">>>",line

		if line is None: return

		oline = line.strip()
		line  = oline.replace(","," ").split()
		cmd   = line[0].upper()

		# ABS*OLUTE: Set absolute coordinates
		if rexx.abbrev("ABSOLUTE",cmd,3):
			self.sendGCode("G90\n")

		# HELP: open browser to display help
		elif cmd == "HELP":
			self.help()

		# HOME: perform a homing cycle
		elif cmd == "HOME":
			self.home()

		# LO*AD [filename]: load filename containing g-code
		elif rexx.abbrev("LOAD",cmd,2):
			self.load(line[1])

		# OPEN: open serial connection to grbl
		# CLOSE: close serial connection to grbl
		elif cmd in ("OPEN","CLOSE"):
			self.openClose()

		# QU*IT: quit program
		# EX*IT: exit program
		elif rexx.abbrev("QUIT",cmd,2) or rexx.abbrev("EXIT",cmd,2):
			self.quit()

		# PAUSE: pause cycle
		elif cmd == "PAUSE":
			self.pause()

		# RESUME: resume
		elif cmd == "RESUME":
			self.resume()

		# FEEDHOLD: feedhold
		elif cmd == "FEEDHOLD":
			self.feedHold()

		# REL*ATIVE: switch to relative coordinates
		elif rexx.abbrev("RELATIVE",cmd,3):
			self.sendGCode("G91\n")

		# RESET: perform a soft reset to grbl
		elif cmd == "RESET":
			self.softReset()

		# RUN: run g-code
		elif cmd == "RUN":
			self.run()

		# SAFE [z]: safe z to move
		elif cmd=="SAFE":
			try: self.cnc.safe = float(line[1])
			except: pass
			self.statusbar["text"] = "Safe Z= %g"%(self.cnc.safe)

		# SA*VE [filename]: save to filename or to default name
		elif rexx.abbrev("SAVE",cmd,2):
			if len(line)>1:
				self.save(line[1])
			else:
				self.saveAll()

		# SENDHEX: send a hex-char in grbl
		elif cmd == "SENDHEX":
			self.sendHex(line[1])

		# SET [x [y [z]]]: set x,y,z coordinates to current workspace
		elif cmd == "SET":
			try: x = float(line[1])
			except: x = None
			try: y = float(line[2])
			except: y = None
			try: z = float(line[3])
			except: z = None
			self._wcsSet(x,y,z)

		elif cmd == "SET0":
			self._wcsSet(0.,0.,0.)

		elif cmd == "SETX":
			try: x = float(line[1])
			except: x = ""
			self._wcsSet(x,None,None)

		elif cmd == "SETY":
			try: y = float(line[1])
			except: y = ""
			self._wcsSet(None,y,None)

		elif cmd == "SETZ":
			try: z = float(line[1])
			except: z = ""
			self._wcsSet(None,None,z)

		# STOP: stop current run
		elif cmd == "STOP":
			self.stopRun()

		# UNL*OCK: unlock grbl
		elif rexx.abbrev("UNLOCK",cmd,3):
			self.unlock()

		# Send commands to SMOOTHIE
		elif self.controller == Utils.SMOOTHIE:
			if line[0] in (	"help", "version", "mem", "ls",
					"cd", "pwd", "cat", "rm", "mv",
					"remount", "play", "progress", "abort",
					"reset", "dfu", "break", "config-get",
					"config-set", "get", "set_temp", "get",
					"get", "net", "load", "save", "upload",
					"calc_thermistor", "thermistors", "md5sum"):
				self.serial.write(oline+"\n")

		else:
			return _("unknown command"),_("Invalid command %s")%(oline)

	#----------------------------------------------------------------------
	def help(self, event=None):
		webbrowser.open(WIKI,new=2)

	#----------------------------------------------------------------------
	def loadRecent(self, recent):
		filename = Utils.getRecent(recent)
		if filename is None: return
		self.load(filename)

	#----------------------------------------------------------------------
	def _loadRecent0(self,event): self.loadRecent(0)
	def _loadRecent1(self,event): self.loadRecent(1)
	def _loadRecent2(self,event): self.loadRecent(2)
	def _loadRecent3(self,event): self.loadRecent(3)
	def _loadRecent4(self,event): self.loadRecent(4)
	def _loadRecent5(self,event): self.loadRecent(5)
	def _loadRecent6(self,event): self.loadRecent(6)
	def _loadRecent7(self,event): self.loadRecent(7)
	def _loadRecent8(self,event): self.loadRecent(8)
	def _loadRecent9(self,event): self.loadRecent(9)

	#----------------------------------------------------------------------
	def _saveConfigFile(self, filename=None):
		if filename is None:
			filename = self.gcode.filename
		Utils.setUtf("File", "dir",   os.path.dirname(os.path.abspath(filename)))
		Utils.setUtf("File", "file",  os.path.basename(filename))
		Utils.setUtf("File", "probe", os.path.basename(self.gcode.probe.filename))

	#----------------------------------------------------------------------
	# Load a file into editor
	#----------------------------------------------------------------------
	def load(self, filename):
		fn,ext = os.path.splitext(filename)
		ext = ext.lower()
		if ext==".probe":
			if filename is not None:
				self.gcode.probe.filename = filename
				self._saveConfigFile()
			self.gcode.probe.load(filename)
		elif ext == ".orient":
			# save orientation file
			self.gcode.orient.load(filename)
		elif ext == ".stl":
			# FIXME: implements solid import???
			pass
		elif ext==".dxf":
			self.gcode.init()
			self.gcode.importDXF(filename)
			self._saveConfigFile(filename)
		else:
			self.gcode.load(filename)
			self._saveConfigFile()
		Utils.addRecent(filename)

	#----------------------------------------------------------------------
	def save(self, filename):
		fn,ext = os.path.splitext(filename)
		ext = ext.lower()
		if ext == ".probe":
			# save probe
			if filename is not None:
				self.gcode.probe.filename = filename
				self._saveConfigFile()
			if not self.gcode.probe.isEmpty():
				self.gcode.probe.save()
		elif ext == ".orient":
			# save orientation file
			self.gcode.orient.save(filename)
		elif ext == ".stl":
			#save probe as STL
			self.gcode.probe.saveAsSTL(filename)
		elif ext == ".dxf":
			return self.gcode.saveDXF(filename)
		else:
			if filename is not None:
				self.gcode.filename = filename
				self._saveConfigFile()
			Utils.addRecent(self.gcode.filename)
			return self.gcode.save()

	#----------------------------------------------------------------------
	def saveAll(self, event=None):
		if self.gcode.filename:
			self.save(self.gcode.filename)
			if self.gcode.probe.filename:
				self.save(self.gcode.probe.filename)
		return "break"

	#----------------------------------------------------------------------
	# Open serial port
	#----------------------------------------------------------------------
	def open(self, device, baudrate):
		self.serial = serial.Serial(	device,
						baudrate,
						bytesize=serial.EIGHTBITS,
						parity=serial.PARITY_NONE,
						stopbits=serial.STOPBITS_ONE,
						timeout=0.1,
						xonxoff=False,
						rtscts=False)
		# Toggle DTR to reset Arduino
		try:
			self.serial.setDTR(0)
		except IOError:
			pass
		time.sleep(1)
		CNC.vars["state"] = CONNECTED
		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
		#self.state.config(text=CNC.vars["state"],
		#		background=CNC.vars["color"])
		# toss any data already received, see
		# http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
		self.serial.flushInput()
		try:
			self.serial.setDTR(1)
		except IOError:
			pass
		time.sleep(1)
		self.serial.write(b"\r\n\r\n")
		self._gcount = 0
		self._alarm  = True
		self.thread  = threading.Thread(target=self.serialIO)
		self.thread.start()
		return True

	#----------------------------------------------------------------------
	# Close serial port
	#----------------------------------------------------------------------
	def close(self):
		if self.serial is None: return
		try:
			self.stopRun()
		except:
			pass
		self._runLines = 0
		self.thread = None
		time.sleep(1)
		self.serial.close()
		self.serial = None
		CNC.vars["state"] = NOT_CONNECTED
		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]

	#----------------------------------------------------------------------
	# Send to controller a gcode or command
	#----------------------------------------------------------------------
	def sendGCode(self, cmd):
		if self.serial and not self.running:
			self.queue.put(cmd)

	#----------------------------------------------------------------------
	def sendHex(self, hexcode):
		if self.serial is None: return
		self.serial.write(chr(int(hexcode,16)))
		self.serial.flush()

	#----------------------------------------------------------------------
	def hardReset(self):
		self.busy()
		if self.serial is not None:
			if self.controller == Utils.SMOOTHIE:
				self.serial.write(b"reset\n")
			self.openClose()
			if self.controller == Utils.SMOOTHIE:
				time.sleep(6)
		self.openClose()
		self.stopProbe()
		self._alarm = False
		self.notBusy()

	#----------------------------------------------------------------------
	def softReset(self):
		if self.serial:
		#	if self.controller == Utils.GRBL:
				self.serial.write(b"\030")
		#	elif self.controller == Utils.SMOOTHIE:
		#		self.serial.write(b"reset\n")
		self.stopProbe()
		self._alarm = False

	#----------------------------------------------------------------------
	def unlock(self):
		self._alarm = False
		self.sendGCode("$X\n")

	#----------------------------------------------------------------------
	def home(self, event=None):
		self._alarm = False
		self.sendGCode("$H\n")

	#----------------------------------------------------------------------
	def viewSettings(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$$\n")

	def viewParameters(self):
		self.sendGCode("$#\n")

	def viewState(self):
		self.sendGCode("$G\n")

	def viewBuild(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$I\n")
		elif self.controller == Utils.SMOOTHIE:
			self.serial.write(b"version\n")

	def viewStartup(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$N\n")

	def checkGcode(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$C\n")

	def grblHelp(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$\n")
		elif self.controller == Utils.SMOOTHIE:
			self.serial.write(b"help\n")

	def grblRestoreSettings(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$RST=$\n")

	def grblRestoreWCS(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$RST=#\n")

	def grblRestoreAll(self):
		if self.controller == Utils.GRBL:
			self.sendGCode("$RST=#\n")

	#----------------------------------------------------------------------
	def goto(self, x=None, y=None, z=None):
		cmd = "G90G0"
		if x is not None: cmd += "X%g"%(x)
		if y is not None: cmd += "Y%g"%(y)
		if z is not None: cmd += "Z%g"%(z)
		self.sendGCode("%s\n"%(cmd))

	#----------------------------------------------------------------------
	# FIXME Duplicate with ControlPage
	#----------------------------------------------------------------------
	def _wcsSet(self, x, y, z):
		p = WCS.index(CNC.vars["WCS"])
		if p<6:
			cmd = "G10L20P%d"%(p+1)
		elif p==6:
			cmd = "G28.1"
		elif p==7:
			cmd = "G30.1"
		elif p==8:
			cmd = "G92"

		pos = ""
		if x is not None and abs(x)<10000.0: pos += "X"+str(x)
		if y is not None and abs(y)<10000.0: pos += "Y"+str(y)
		if z is not None and abs(z)<10000.0: pos += "Z"+str(z)
		cmd += pos
		self.sendGCode(cmd+"\n$#\n")
		self.event_generate("<<Status>>",
			data=(_("Set workspace %s to %s")%(WCS[p],pos)))
			#data=(_("Set workspace %s to %s")%(WCS[p],pos)).encode("utf8"))
		self.event_generate("<<CanvasFocus>>")

	#----------------------------------------------------------------------
	def feedHold(self, event=None):
		if event is not None and not self.acceptKey(True): return
		if self.serial is None: return
		self.serial.write(b"!")
		self.serial.flush()
		self._pause = True

	#----------------------------------------------------------------------
	def resume(self, event=None):
		if event is not None and not self.acceptKey(True): return
		if self.serial is None: return
		self.serial.write(b"~")
		self.serial.flush()
		self._msg   = None
		self._alarm = False
		self._pause = False

	#----------------------------------------------------------------------
	def pause(self, event=None):
		if self.serial is None: return
		if self._pause:
			self.resume()
		else:
			self.feedHold()

	#----------------------------------------------------------------------
	# FIXME ????
	#----------------------------------------------------------------------
	def g28Command(self):
		self.sendGCode("G28.1\n")

	#----------------------------------------------------------------------
	# FIXME ????
	#----------------------------------------------------------------------
	def g30Command(self):
		self.sendGCode("G30.1\n")

	#----------------------------------------------------------------------
	def emptyQueue(self):
		while self.queue.qsize()>0:
			try:
				self.queue.get_nowait()
			except Empty:
				break

	#----------------------------------------------------------------------
	def stopProbe(self):
		if self.gcode.probe.start:
			self.gcode.probe.clear()

	#----------------------------------------------------------------------
	def getBufferFill(self):
		return self._sumcline * 100. / RX_BUFFER_SIZE

	#----------------------------------------------------------------------
	def initRun(self):
		self._quit   = 0
		self._pause  = False
		self._paths  = None
		self.running = True
		self.disable()
		self.emptyQueue()
		time.sleep(1)

	#----------------------------------------------------------------------
	# Called when run is finished
	#----------------------------------------------------------------------
	def runEnded(self):
		self._runLines = 0
		self._quit     = 0
		self._pause    = False
		self.running   = False
		self._msg      = None
		self.enable()

	#----------------------------------------------------------------------
	# Purge the buffer of the controller. Unfortunately we have to perform
	# a reset to clear the buffer of the controller
	#---------------------------------------------------------------------
	def purgeController(self):
		time.sleep(1)
		# remember and send all G commands
		G = " ".join([x for x in CNC.vars["G"] if x[0]=="G"])	# remember $G
		TLO = CNC.vars["TLO"]
		self.softReset()			# reset controller
		if self.controller == Utils.GRBL:
			time.sleep(1)
			self.unlock()
		self.runEnded()
		self.stopProbe()
		if G: self.sendGCode("%s\n$G\n"%(G))	# restore $G
		self.sendGCode("G43.1Z%s\n$G\n"%(TLO))	# restore TLO

	#----------------------------------------------------------------------
	# Stop the current run
	#----------------------------------------------------------------------
	def stopRun(self, event=None):
		self.feedHold()
		self._stop = True
		# if we are in the process of submitting do not do anything
		if self._runLines != sys.maxint:
			self.purgeController()

	#----------------------------------------------------------------------
	# thread performing I/O on serial line
	#----------------------------------------------------------------------
	def serialIO(self):
		cline  = []		# length of pipeline commands
		sline  = []		# pipeline commands
		wait   = False		# wait for commands to complete
		tosend = None		# next string to send
		status = False		# waiting for status <...> report
		tr = tg = time.time()	# last time a ? or $G was send to grbl

		while self.thread:
			t = time.time()

			# refresh machine position?
			if t-tr > SERIAL_POLL:
				# Send one ?
				self.serial.write(b"?")
				status = True
				#print ">S> ?"
				tr = t

			# Fetch new command to send if...
			if tosend is None and not wait and not self._pause and self.queue.qsize()>0:
				try:
					tosend = self.queue.get_nowait()
					#print "+++",repr(tosend)

					if isinstance(tosend, tuple):
						#print "gcount tuple=",self._gcount
						# wait to empty the grbl buffer
						if tosend[0] == WAIT:
							# Don't count WAIT until we are idle!
							wait = True
							#print "+++ WAIT ON"
							#print "gcount=",self._gcount, self._runLines
						elif tosend[0] == MSG:
							# Count executed commands as well
							self._gcount += 1
							if tosend[1] is not None:
								# show our message on machine status
								self._msg = tosend[1]
						elif tosend[0] == UPDATE:
							# Count executed commands as well
							self._gcount += 1
							self._update = tosend[1]
						else:
							# Count executed commands as well
							self._gcount += 1
						tosend = None

					elif not isinstance(tosend,str) and not isinstance(tosend,unicode):
						try:
							tosend = self.gcode.evaluate(tosend)
#							if isinstance(tosend, list):
#								cline.append(len(tosend[0]))
#								sline.append(tosend[0])
#								self.log.put((True,tosend[0]))
							if isinstance(tosend,str) or isinstance(tosend,unicode):
								tosend += "\n"
							else:
								# Count executed commands as well
								self._gcount += 1
								#print "gcount str=",self._gcount
							#print "+++ eval=",repr(tosend),type(tosend)
						except:
							self.log.put((True,sys.exc_info()[1]))
							tosend = None
				except Empty:
					break

				if tosend is not None:
					# All modification in tosend should be
					# done before adding it to cline
					if isinstance(tosend, unicode):
						tosend = tosend.encode("ascii","replace")

					#Keep track of last feed
					pat = FEEDPAT.match(tosend)
					if pat is not None:
						self._lastFeed = pat.group(2)
						self._newFeed = float(self._lastFeed)*CNC.vars["override"]/100.0

					#If Override change, attach feed
					if CNC.vars["overrideChanged"]:
						CNC.vars["overrideChanged"] = False
						if pat is None and self._newFeed!=0:
							tosend = "f%g" % (self._newFeed) + tosend

					#Apply override Feed
					if CNC.vars["override"] != 100 and self._newFeed!=0:
						pat = FEEDPAT.match(tosend)
						if pat is not None:
							try:
								tosend = "%sf%g%s\n" % \
									(pat.group(1),
									 self._newFeed,
									 pat.group(3))
							except:
								pass

					# Bookkeeping of the buffers
					sline.append(tosend)
					cline.append(len(tosend))
					self.log.put((True,tosend))

			# Anything to receive?
			if self.serial.inWaiting() or tosend is None:
				line = str(self.serial.readline()).strip()
				#print "<R<",repr(line)
				#print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
				if line:
					if line[0]=="<":
						pat = STATUSPAT.match(line)
						if pat:
							if not status: self.log.put((False, line+"\n"))
							status = False
							if not self._alarm:
								CNC.vars["state"] = pat.group(1)
							CNC.vars["mx"] = float(pat.group(2))
							CNC.vars["my"] = float(pat.group(3))
							CNC.vars["mz"] = float(pat.group(4))
							CNC.vars["wx"] = float(pat.group(5))
							CNC.vars["wy"] = float(pat.group(6))
							CNC.vars["wz"] = float(pat.group(7))
							self._posUpdate = True
							if pat.group(1) != "Hold" and self._msg:
								self._msg = None

							# Machine is Idle buffer is empty
							# stop waiting and go on
							#print "<<< WAIT=",wait,sline,pat.group(1),sum(cline)
							#print ">>>", line
							if wait and not cline and pat.group(1)=="Idle":
								#print ">>>",line
								wait = False
								#print "<<< NO MORE WAIT"
								self._gcount += 1
						else:
							self.log.put((False, line+"\n"))

					elif line[0]=="[":
						self.log.put((False, line+"\n"))
						pat = POSPAT.match(line)
						if pat:
							if pat.group(1) == "PRB":
								CNC.vars["prbx"] = float(pat.group(2))
								CNC.vars["prby"] = float(pat.group(3))
								CNC.vars["prbz"] = float(pat.group(4))
								#if self.running:
								self.gcode.probe.add(
									 CNC.vars["prbx"]
									+CNC.vars["wx"]
									-CNC.vars["mx"],

									 CNC.vars["prby"]
									+CNC.vars["wy"]
									-CNC.vars["my"],

									 CNC.vars["prbz"]
									+CNC.vars["wz"]
									-CNC.vars["mz"])
								self._probeUpdate = True
							CNC.vars[pat.group(1)] = \
								[float(pat.group(2)),
								 float(pat.group(3)),
								 float(pat.group(4))]
						else:
							pat = TLOPAT.match(line)
							if pat:
								CNC.vars[pat.group(1)] = pat.group(2)
								self._probeUpdate = True
							elif DOLLARPAT.match(line):
								CNC.vars["G"] = line[1:-1].split()
								CNC.updateG()
								self._gUpdate = True

					else:
						#print "<r<",repr(line)
						self.log.put((False, line+"\n"))
						uline = line.upper()
						if uline.find("ERROR")==0 or uline.find("ALARM")==0:
							self._gcount += 1
							#print "gcount ERROR=",self._gcount
							if cline: del cline[0]
							if sline: CNC.vars["errline"] = sline.pop(0)
							if not self._alarm: self._posUpdate = True
							self._alarm = True
							CNC.vars["state"] = line
							if self.running:
								self._stop = True
								#self.emptyQueue()
								# Dangerous calling state of Tk if not reentrant
								self.runEnded()
								#tosend = None
								#del cline[:]
								#del sline[:]

						elif line.find("ok")>=0:
							self._gcount += 1
							#print "gcount OK=",self._gcount
							if cline: del cline[0]
							if sline: del sline[0]
							#print "SLINE:",sline

			# Received external message to stop
			if self._stop:
				self.emptyQueue()
				tosend = None
				del cline[:]
				del sline[:]
				# WARNING if maxint then it means we are still preparing/sending
				# lines from from bCNC.run(), so don't stop
				if self._runLines != sys.maxint:
					self._stop = False

			#print "tosend='%s'"%(repr(tosend)),"stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
			if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
				self._sumcline = sum(cline)
#				if isinstance(tosend, list):
#					self.serial.write(str(tosend.pop(0)))
#					if not tosend: tosend = None

				#print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
				if self.controller==Utils.SMOOTHIE: tosend = tosend.upper()
				self.serial.write(bytes(tosend))
#				self.serial.write(tosend.encode("utf8"))
#				self.serial.flush()

				tosend = None
				if not self.running and t-tg > G_POLL:
					tosend = b"$G\n"
					sline.append(tosend)
					cline.append(len(tosend))
					tg = t
Ejemplo n.º 9
0
class Sender:
    # Messages types for log Queue
    MSG_BUFFER = 0  # write to buffer one command
    MSG_SEND = 1  # send message
    MSG_RECEIVE = 2  # receive message from controller
    MSG_OK = 3  # ok response from controller, move top most command to terminal
    MSG_ERROR = 4  # error message or exception
    MSG_RUNEND = 5  # run ended
    MSG_CLEAR = 6  # clear buffer

    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None

        #self.mcontrol     = None
        self.controllers = {}
        self.controllerLoad()
        self.controllerSet("GRBL1")

        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to be send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self.runningPrev = None
        self.cleanAfter = False
        self._runLines = 0
        self._quit = 0  # Quit counter to exit program
        self._stop = False  # Raise to stop current run
        self._pause = False  # machine is on Hold
        self._alarm = True  # Display alarm message if true
        self._msg = None
        self._sumcline = 0
        self._lastFeed = 0
        self._newFeed = 0

        self._onStart = ""
        self._onStop = ""

    #----------------------------------------------------------------------
    def controllerLoad(self):
        # Find plugins in the controllers directory and load them
        for f in glob.glob("%s/controllers/*.py" % (Utils.prgpath)):
            name, ext = os.path.splitext(os.path.basename(f))
            if name[0] == '_': continue
            #print("Loaded motion controller plugin: %s"%(name))
            try:
                exec("import %s" % (name))
                self.controllers[name] = eval("%s.Controller(self)" % (name))
            except (ImportError, AttributeError):
                typ, val, tb = sys.exc_info()
                traceback.print_exception(typ, val, tb)

    #----------------------------------------------------------------------
    def controllerList(self):
        #print("ctrlist")
        #self.controllers["GRBL1"].test()
        #if len(self.controllers.keys()) < 1: self.controllerLoad()
        return sorted(self.controllers.keys())

    #----------------------------------------------------------------------
    def controllerSet(self, ctl):
        #print("Activating motion controller plugin: %s"%(ctl))
        if ctl in self.controllers.keys():
            self.controller = ctl
            CNC.vars["controller"] = ctl
            self.mcontrol = self.controllers[ctl]
            #self.mcontrol.test()

    #----------------------------------------------------------------------
    def quit(self, event=None):
        self.saveConfig()
        Pendant.stop()

    #----------------------------------------------------------------------
    def loadConfig(self):
        self.controllerSet(Utils.getStr("Connection", "controller"))
        Pendant.port = Utils.getInt("Connection", "pendantport", Pendant.port)
        GCode.LOOP_MERGE = Utils.getBool("File", "dxfloopmerge")
        self.loadHistory()

    #----------------------------------------------------------------------
    def saveConfig(self):
        self.saveHistory()

    #----------------------------------------------------------------------
    def loadHistory(self):
        try:
            f = open(Utils.hisFile, "r")
        except:
            return
        self.history = [x.strip() for x in f]
        f.close()

    #----------------------------------------------------------------------
    def saveHistory(self):
        try:
            f = open(Utils.hisFile, "w")
        except:
            return
        f.write("\n".join(self.history))
        f.close()

    #----------------------------------------------------------------------
    # Evaluate a line for possible expressions
    # can return a python exception, needs to be catched
    #----------------------------------------------------------------------
    def evaluate(self, line):
        return self.gcode.evaluate(CNC.compileLine(line, True), self)

    #----------------------------------------------------------------------
    # Execute a line as gcode if pattern matches
    # @return True on success
    #	  False otherwise
    #----------------------------------------------------------------------
    def executeGcode(self, line):
        if isinstance(line, tuple) or \
           line[0] in ("$","!","~","?","(","@") or GPAT.match(line):
            self.sendGCode(line)
            return True
        return False

    #----------------------------------------------------------------------
    # Execute a single command
    #----------------------------------------------------------------------
    def executeCommand(self, line):
        #print
        #print "<<<",line
        #try:
        #	line = self.gcode.evaluate(CNC.compileLine(line,True))
        #except:
        #	return "Evaluation error", sys.exc_info()[1]
        #print ">>>",line

        if line is None: return

        oline = line.strip()
        line = oline.replace(",", " ").split()
        cmd = line[0].upper()

        # ABS*OLUTE: Set absolute coordinates
        if rexx.abbrev("ABSOLUTE", cmd, 3):
            self.sendGCode("G90")

        # HELP: open browser to display help
        elif cmd == "HELP":
            self.help()

        # HOME: perform a homing cycle
        elif cmd == "HOME":
            self.home()

        # LO*AD [filename]: load filename containing g-code
        elif rexx.abbrev("LOAD", cmd, 2):
            self.load(line[1])

        # OPEN: open serial connection to grbl
        # CLOSE: close serial connection to grbl
        elif cmd in ("OPEN", "CLOSE"):
            self.openClose()

        # QU*IT: quit program
        # EX*IT: exit program
        elif rexx.abbrev("QUIT", cmd, 2) or rexx.abbrev("EXIT", cmd, 2):
            self.quit()

        # PAUSE: pause cycle
        elif cmd == "PAUSE":
            self.pause()

        # RESUME: resume
        elif cmd == "RESUME":
            self.resume()

        # FEEDHOLD: feedhold
        elif cmd == "FEEDHOLD":
            self.feedHold()

        # REL*ATIVE: switch to relative coordinates
        elif rexx.abbrev("RELATIVE", cmd, 3):
            self.sendGCode("G91")

        # RESET: perform a soft reset to grbl
        elif cmd == "RESET":
            self.softReset()

        # RUN: run g-code
        elif cmd == "RUN":
            self.run()

        # SAFE [z]: safe z to move
        elif cmd == "SAFE":
            try:
                CNC.vars["safe"] = float(line[1])
            except:
                pass
            self.statusbar["text"] = "Safe Z= %g" % (CNC.vars["safe"])

        # SA*VE [filename]: save to filename or to default name
        elif rexx.abbrev("SAVE", cmd, 2):
            if len(line) > 1:
                self.save(line[1])
            else:
                self.saveAll()

        # SENDHEX: send a hex-char in grbl
        elif cmd == "SENDHEX":
            self.sendHex(line[1])

        # SET [x [y [z]]]: set x,y,z coordinates to current workspace
        elif cmd == "SET":
            try:
                x = float(line[1])
            except:
                x = None
            try:
                y = float(line[2])
            except:
                y = None
            try:
                z = float(line[3])
            except:
                z = None
            self._wcsSet(x, y, z)

        elif cmd == "SET0":
            self._wcsSet(0., 0., 0.)

        elif cmd == "SETX":
            try:
                x = float(line[1])
            except:
                x = ""
            self._wcsSet(x, None, None)

        elif cmd == "SETY":
            try:
                y = float(line[1])
            except:
                y = ""
            self._wcsSet(None, y, None)

        elif cmd == "SETZ":
            try:
                z = float(line[1])
            except:
                z = ""
            self._wcsSet(None, None, z)

        # STOP: stop current run
        elif cmd == "STOP":
            self.stopRun()

        # UNL*OCK: unlock grbl
        elif rexx.abbrev("UNLOCK", cmd, 3):
            self.unlock()

        # Send commands to SMOOTHIE
        elif self.mcontrol.executeCommand(oline, line, cmd):
            pass

        else:
            return _("unknown command"), _("Invalid command %s") % (oline)

    #----------------------------------------------------------------------
    def help(self, event=None):
        webbrowser.open(WIKI, new=2)

    #----------------------------------------------------------------------
    def loadRecent(self, recent):
        filename = Utils.getRecent(recent)
        if filename is None: return
        self.load(filename)

    #----------------------------------------------------------------------
    def _loadRecent0(self, event):
        self.loadRecent(0)

    # ----------------------------------------------------------------------
    def _loadRecent1(self, event):
        self.loadRecent(1)

    # ----------------------------------------------------------------------
    def _loadRecent2(self, event):
        self.loadRecent(2)

    # ----------------------------------------------------------------------
    def _loadRecent3(self, event):
        self.loadRecent(3)

    # ----------------------------------------------------------------------
    def _loadRecent4(self, event):
        self.loadRecent(4)

    # ----------------------------------------------------------------------
    def _loadRecent5(self, event):
        self.loadRecent(5)

    # ----------------------------------------------------------------------
    def _loadRecent6(self, event):
        self.loadRecent(6)

    # ----------------------------------------------------------------------
    def _loadRecent7(self, event):
        self.loadRecent(7)

    # ----------------------------------------------------------------------
    def _loadRecent8(self, event):
        self.loadRecent(8)

    # ----------------------------------------------------------------------
    def _loadRecent9(self, event):
        self.loadRecent(9)

    #----------------------------------------------------------------------
    def _saveConfigFile(self, filename=None):
        if filename is None:
            filename = self.gcode.filename
        Utils.setUtf("File", "dir", os.path.dirname(os.path.abspath(filename)))
        Utils.setUtf("File", "file", os.path.basename(filename))
        Utils.setUtf("File", "probe",
                     os.path.basename(self.gcode.probe.filename))

    #----------------------------------------------------------------------
    # Load a file into editor
    #----------------------------------------------------------------------
    def load(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe":
            if filename is not None:
                self.gcode.probe.filename = filename
                self._saveConfigFile()
            self.gcode.probe.load(filename)
        elif ext == ".orient":
            # save orientation file
            self.gcode.orient.load(filename)
        elif ext == ".stl" or ext == ".ply":
            # FIXME: implements solid import???
            import tkMessageBox
            tkMessageBox.showinfo(
                "Open 3D Mesh",
                "Importing of 3D mesh files in .STL and .PLY format is supported by SliceMesh plugin.\nYou can find it in CAM->SliceMesh."
            )
        elif ext == ".dxf":
            self.gcode.init()
            self.gcode.importDXF(filename)
            self._saveConfigFile(filename)
        elif ext == ".svg":
            self.gcode.init()
            self.gcode.importSVG(filename)
            self._saveConfigFile(filename)
        else:
            self.gcode.load(filename)
            self._saveConfigFile()
        Utils.addRecent(filename)

    #----------------------------------------------------------------------
    def save(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe" or ext == ".xyz":
            # save probe
            if not self.gcode.probe.isEmpty():
                self.gcode.probe.save(filename)
            if filename is not None:
                self._saveConfigFile()
        elif ext == ".orient":
            # save orientation file
            self.gcode.orient.save(filename)
        elif ext == ".stl":
            #save probe as STL
            self.gcode.probe.saveAsSTL(filename)
        elif ext == ".dxf":
            return self.gcode.saveDXF(filename)
        elif ext == ".svg":
            return self.gcode.saveSVG(filename)
        elif ext == ".txt":
            #save gcode as txt (only enabled blocks and no bCNC metadata)
            return self.gcode.saveTXT(filename)
        else:
            if filename is not None:
                self.gcode.filename = filename
                self._saveConfigFile()
            Utils.addRecent(self.gcode.filename)
            return self.gcode.save()

    #----------------------------------------------------------------------
    def saveAll(self, event=None):
        if self.gcode.filename:
            self.save(self.gcode.filename)
            if self.gcode.probe.filename:
                self.save(self.gcode.probe.filename)
        return "break"

    #----------------------------------------------------------------------
    # Serial write
    #----------------------------------------------------------------------
    def serial_write(self, data):
        #print("W "+str(type(data))+" : "+str(data))

        #if sys.version_info[0] == 2:
        #	ret = self.serial.write(str(data))
        if isinstance(data, bytes):
            ret = self.serial.write(data)
        else:
            ret = self.serial.write(data.encode())

        return ret

    #----------------------------------------------------------------------
    # Open serial port
    #----------------------------------------------------------------------
    def open(self, device, baudrate):
        #self.serial = serial.Serial(
        self.serial = serial.serial_for_url(
            device.replace('\\', '\\\\'),  #Escape for windows
            baudrate,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=SERIAL_TIMEOUT,
            xonxoff=False,
            rtscts=False)
        # Toggle DTR to reset Arduino
        try:
            self.serial.setDTR(0)
        except IOError:
            pass
        time.sleep(1)
        CNC.vars["state"] = CONNECTED
        CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
        #self.state.config(text=CNC.vars["state"],
        #		background=CNC.vars["color"])
        # toss any data already received, see
        # http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
        self.serial.flushInput()
        try:
            self.serial.setDTR(1)
        except IOError:
            pass
        time.sleep(1)
        self.serial_write(b"\n\n")
        self._gcount = 0
        self._alarm = True
        self.thread = threading.Thread(target=self.serialIO)
        self.thread.start()
        return True

    #----------------------------------------------------------------------
    # Close serial port
    #----------------------------------------------------------------------
    def close(self):
        if self.serial is None: return
        try:
            self.stopRun()
        except:
            pass
        self._runLines = 0
        self.thread = None
        time.sleep(1)
        try:
            self.serial.close()
        except:
            pass
        self.serial = None
        CNC.vars["state"] = NOT_CONNECTED
        CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]

    #----------------------------------------------------------------------
    # Send to controller a gcode or command
    # WARNING: it has to be a single line!
    #----------------------------------------------------------------------
    def sendGCode(self, cmd):
        if self.serial and not self.running:
            if isinstance(cmd, tuple):
                self.queue.put(cmd)
            else:
                self.queue.put(cmd + "\n")

    #----------------------------------------------------------------------
    def sendHex(self, hexcode):
        if self.serial is None: return
        self.serial_write(chr(int(hexcode, 16)))
        self.serial.flush()

    #----------------------------------------------------------------------
    # FIXME: legacy wrappers. try to call mcontrol directly instead:
    #----------------------------------------------------------------------
    def hardReset(self):
        self.mcontrol.hardReset()

    def softReset(self, clearAlarm=True):
        self.mcontrol.softReset(clearAlarm)

    def unlock(self, clearAlarm=True):
        self.mcontrol.unlock(clearAlarm)

    def home(self, event=None):
        self.mcontrol.home(event)

    def viewSettings(self):
        self.mcontrol.viewSettings()

    def viewParameters(self):
        self.mcontrol.viewParameters()

    def viewState(self):
        self.mcontrol.viewState()

    def viewBuild(self):
        self.mcontrol.viewBuild()

    def viewStartup(self):
        self.mcontrol.viewStartup()

    def checkGcode(self):
        self.mcontrol.checkGcode()

    def grblHelp(self):
        self.mcontrol.grblHelp()

    def grblRestoreSettings(self):
        self.mcontrol.grblRestoreSettings()

    def grblRestoreWCS(self):
        self.mcontrol.grblRestoreWCS()

    def grblRestoreAll(self):
        self.mcontrol.grblRestoreAll()

    def goto(self, x=None, y=None, z=None):
        self.mcontrol.goto(x, y, z)

    def _wcsSet(self, x, y, z):
        self.mcontrol._wcsSet(x, y, z)  # FIXME Duplicate with ControlPage

    def feedHold(self, event=None):
        self.mcontrol.feedHold(event)

    def resume(self, event=None):
        self.mcontrol.resume(event)

    def pause(self, event=None):
        self.mcontrol.pause(event)

    def purgeController(self):
        self.mcontrol.purgeController()

    def g28Command(self):
        self.sendGCode("G28.1")  #FIXME: ???

    def g30Command(self):
        self.sendGCode("G30.1")  #FIXME: ???

    #----------------------------------------------------------------------
    def emptyQueue(self):
        while self.queue.qsize() > 0:
            try:
                self.queue.get_nowait()
            except Empty:
                break

    #----------------------------------------------------------------------
    def stopProbe(self):
        if self.gcode.probe.start:
            self.gcode.probe.clear()

    #----------------------------------------------------------------------
    def getBufferFill(self):
        return self._sumcline * 100. / RX_BUFFER_SIZE

    #----------------------------------------------------------------------
    def initRun(self):
        self._quit = 0
        self._pause = False
        self._paths = None
        self.running = True
        self.disable()
        self.emptyQueue()
        time.sleep(1)

    #----------------------------------------------------------------------
    # Called when run is finished
    #----------------------------------------------------------------------
    def runEnded(self):
        if self.running:
            self.log.put((Sender.MSG_RUNEND, _("Run ended")))
            self.log.put((Sender.MSG_RUNEND, str(datetime.now())))
            self.log.put((Sender.MSG_RUNEND, str(CNC.vars["msg"])))
            if self._onStop:
                try:
                    os.system(self._onStop)
                except:
                    pass
        self._runLines = 0
        self._quit = 0
        self._msg = None
        self._pause = False
        self.running = False
        CNC.vars["running"] = False

    #----------------------------------------------------------------------
    # Stop the current run
    #----------------------------------------------------------------------
    def stopRun(self, event=None):
        self.feedHold()
        self._stop = True
        # if we are in the process of submitting do not do anything
        if self._runLines != sys.maxsize:
            self.purgeController()

    #----------------------------------------------------------------------
    # This should be called everytime that milling of g-code file is finished
    # So we can purge the controller for the next job
    # See https://github.com/vlachoudis/bCNC/issues/1035
    #----------------------------------------------------------------------
    def jobDone(self):
        print("Job done. Purging the controller. (Running: %s)" %
              (self.running))
        self.purgeController()

    #----------------------------------------------------------------------
    # This is called everytime that motion controller changes the state
    # YOU SHOULD PASS ONLY REAL HW STATE TO THIS, NOT BCNC STATE
    # Right now the primary idea of this is to detect when job stopped running
    #----------------------------------------------------------------------
    def controllerStateChange(self, state):
        print("Controller state changed to: %s (Running: %s)" %
              (state, self.running))
        if state in ("Idle"):
            self.mcontrol.viewParameters()
            self.mcontrol.viewState()

        if self.cleanAfter == True and self.running == False and state in (
                "Idle"):
            self.cleanAfter = False
            self.jobDone()

    #----------------------------------------------------------------------
    # thread performing I/O on serial line
    #----------------------------------------------------------------------
    def serialIO(self):
        self.sio_wait = False  # wait for commands to complete (status change to Idle)
        self.sio_status = False  # waiting for status <...> report
        cline = []  # length of pipeline commands
        sline = []  # pipeline commands
        tosend = None  # next string to send
        tr = tg = time.time()  # last time a ? or $G was send to grbl

        while self.thread:
            t = time.time()
            # refresh machine position?
            if t - tr > SERIAL_POLL:
                self.mcontrol.viewStatusReport()
                tr = t

                #If Override change, attach feed
                if CNC.vars["_OvChanged"]:
                    self.mcontrol.overrideSet()

            # Fetch new command to send if...
            if tosend is None and not self.sio_wait and not self._pause and self.queue.qsize(
            ) > 0:
                try:
                    tosend = self.queue.get_nowait()
                    #print "+++",repr(tosend)
                    if isinstance(tosend, tuple):
                        #print "gcount tuple=",self._gcount
                        # wait to empty the grbl buffer and status is Idle
                        if tosend[0] == WAIT:
                            # Don't count WAIT until we are idle!
                            self.sio_wait = True
                            #print "+++ WAIT ON"
                            #print "gcount=",self._gcount, self._runLines
                        elif tosend[0] == MSG:
                            # Count executed commands as well
                            self._gcount += 1
                            if tosend[1] is not None:
                                # show our message on machine status
                                self._msg = tosend[1]
                        elif tosend[0] == UPDATE:
                            # Count executed commands as well
                            self._gcount += 1
                            self._update = tosend[1]
                        else:
                            # Count executed commands as well
                            self._gcount += 1
                        tosend = None

                    elif not isinstance(tosend, str):
                        try:
                            tosend = self.gcode.evaluate(tosend)
                            #							if isinstance(tosend, list):
                            #								cline.append(len(tosend[0]))
                            #								sline.append(tosend[0])
                            if isinstance(tosend, str):
                                tosend += "\n"
                            else:
                                # Count executed commands as well
                                self._gcount += 1
                                #print "gcount str=",self._gcount
                            #print "+++ eval=",repr(tosend),type(tosend)
                        except:
                            for s in str(sys.exc_info()[1]).splitlines():
                                self.log.put((Sender.MSG_ERROR, s))
                            self._gcount += 1
                            tosend = None
                except Empty:
                    break

                if tosend is not None:
                    # All modification in tosend should be
                    # done before adding it to cline

                    # Keep track of last feed
                    pat = FEEDPAT.match(tosend)
                    if pat is not None:
                        self._lastFeed = pat.group(2)

                    # Modify sent g-code to reflect overrided feed for controllers without override support
                    if not self.mcontrol.has_override:
                        if CNC.vars["_OvChanged"]:
                            CNC.vars["_OvChanged"] = False
                            self._newFeed = float(
                                self._lastFeed) * CNC.vars["_OvFeed"] / 100.0
                            if pat is None and self._newFeed!=0 \
                               and not tosend.startswith("$"):
                                tosend = "f%g%s" % (self._newFeed, tosend)

                        # Apply override Feed
                        if CNC.vars["_OvFeed"] != 100 and self._newFeed != 0:
                            pat = FEEDPAT.match(tosend)
                            if pat is not None:
                                try:
                                    tosend = "%sf%g%s\n" % \
                                     (pat.group(1),
                                      self._newFeed,
                                      pat.group(3))
                                except:
                                    pass

                    # Bookkeeping of the buffers
                    sline.append(tosend)
                    cline.append(len(tosend))

            # Anything to receive?
            if self.serial.inWaiting() or tosend is None:
                try:
                    line = str(self.serial.readline().decode()).strip()
                except:
                    self.log.put((Sender.MSG_RECEIVE, str(sys.exc_info()[1])))
                    self.emptyQueue()
                    self.close()
                    return

                #print "<R<",repr(line)
                #print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
                if not line:
                    pass
                elif self.mcontrol.parseLine(line, cline, sline):
                    pass
                else:
                    self.log.put((Sender.MSG_RECEIVE, line))

            # Received external message to stop
            if self._stop:
                self.emptyQueue()
                tosend = None
                self.log.put((Sender.MSG_CLEAR, ""))
                # WARNING if runLines==maxint then it means we are
                # still preparing/sending lines from from bCNC.run(),
                # so don't stop
                if self._runLines != sys.maxsize:
                    self._stop = False

            #print "tosend='%s'"%(repr(tosend)),"stack=",sline,
            #	"sum=",sum(cline),"wait=",wait,"pause=",self._pause
            if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
                self._sumcline = sum(cline)
                #				if isinstance(tosend, list):
                #					self.serial_write(str(tosend.pop(0)))
                #					if not tosend: tosend = None

                #print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
                if self.mcontrol.gcode_case > 0: tosend = tosend.upper()
                if self.mcontrol.gcode_case < 0: tosend = tosend.lower()

                self.serial_write(tosend)

                #self.serial_write(tosend)
                #self.serial.flush()
                self.log.put((Sender.MSG_BUFFER, tosend))

                tosend = None
                if not self.running and t - tg > G_POLL:
                    tosend = b"$G\n"  #FIXME: move to controller specific class
                    sline.append(tosend)
                    cline.append(len(tosend))
                    tg = t
Ejemplo n.º 10
0
class Sender:
	def __init__(self):
		# Global variables
		self.history     = []
		self._historyPos = None
		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc

		self.log         = Queue()	# Log queue returned from GRBL
		self.queue       = Queue()	# Command queue to send to GRBL
		self.pendant     = Queue()	# Command queue to be executed from Pendant
		self.serial      = None
		self.thread      = None

		self._posUpdate  = False	# Update position
		self._probeUpdate= False	# Update probe
		self._gUpdate    = False	# Update $G
		self._update     = None		# Generic update

		self.running     = False
		self._runLines   = 0
		self._stop       = False	# Raise to stop current run
		self._quit       = 0
		self._pause      = False	# machine is on Hold
		self._alarm      = True
		self._msg        = None

	#----------------------------------------------------------------------
	def quit(self, event=None):
		self.saveConfig()
		Pendant.stop()

	#----------------------------------------------------------------------
	def loadConfig(self):
		Pendant.port = Utils.getInt("Connection","pendantport",Pendant.port)
		GCode.LOOP_MERGE = Utils.getBool("File","dxfloopmerge")
		self.loadHistory()

	#----------------------------------------------------------------------
	def saveConfig(self):
		self.saveHistory()

	#----------------------------------------------------------------------
	def loadHistory(self):
		try:
			f = open(Utils.hisFile,"r")
		except:
			return
		self.history = [x.strip() for x in f]
		f.close()

	#----------------------------------------------------------------------
	def saveHistory(self):
		try:
			f = open(Utils.hisFile,"w")
		except:
			return
		f.write("\n".join(self.history))
		f.close()

	#----------------------------------------------------------------------
	# Evaluate a line for possible expressions
	# can return a python exception, needs to be catched
	#----------------------------------------------------------------------
	def evaluate(self, line):
		return self.gcode.evaluate(CNC.parseLine2(line,True))

	#----------------------------------------------------------------------
	# Execute a line as gcode if pattern matches
	# @return True on success
	#         False otherwise
	#----------------------------------------------------------------------
	def executeGcode(self, line):
		if isinstance(line, tuple):
			self.sendGrbl(line)
			return True

		elif line[0] in ("$","!","~","?","(","@") or GPAT.match(line):
			self.sendGrbl(line+"\n")
			return True

		return False

	#----------------------------------------------------------------------
	# Execute a single command
	#----------------------------------------------------------------------
	def executeCommand(self, line):
		#print
		#print "<<<",line
		#try:
		#	line = self.gcode.evaluate(CNC.parseLine2(line,True))
		#except:
		#	return "Evaluation error", sys.exc_info()[1]
		#print ">>>",line

		if line is None: return

		oline = line.strip()
		line  = oline.replace(","," ").split()
		cmd   = line[0].upper()

		# ABS*OLUTE: Set absolute coordinates
		if rexx.abbrev("ABSOLUTE",cmd,3):
			self.sendGrbl("G90\n")

		# HELP: open browser to display help
		elif cmd == "HELP":
			self.help()

		# HOME: perform a homing cycle
		elif cmd == "HOME":
			self.home()

		# LO*AD [filename]: load filename containing g-code
		elif rexx.abbrev("LOAD",cmd,2):
			self.load(line[1])

		# OPEN: open serial connection to grbl
		# CLOSE: close serial connection to grbl
		elif cmd in ("OPEN","CLOSE"):
			self.openClose()

		# QU*IT: quit program
		# EX*IT: exit program
		elif rexx.abbrev("QUIT",cmd,2) or rexx.abbrev("EXIT",cmd,2):
			self.quit()

		# PAUSE: pause cycle
		elif cmd == "PAUSE":
			self.pause()

		# RESUME: resume
		elif cmd == "RESUME":
			self.resume()

		# FEEDHOLD: feedhold
		elif cmd == "FEEDHOLD":
			self.feedHold()

		# REL*ATIVE: switch to relative coordinates
		elif rexx.abbrev("RELATIVE",cmd,3):
			self.sendGrbl("G91\n")

		# RESET: perform a soft reset to grbl
		elif cmd == "RESET":
			self.softReset()

		# RUN: run g-code
		elif cmd == "RUN":
			self.run()

		# SAFE [z]: safe z to move
		elif cmd=="SAFE":
			try: self.cnc.safe = float(line[1])
			except: pass
			self.statusbar["text"] = "Safe Z= %g"%(self.cnc.safe)

		# SA*VE [filename]: save to filename or to default name
		elif rexx.abbrev("SAVE",cmd,2):
			if len(line)>1:
				self.save(line[1])
			else:
				self.saveAll()

		# SENDHEX: send a hex-char in grbl
		elif cmd == "SENDHEX":
			self.sendHex(line[1])

		# STOP: stop current run
		elif cmd == "STOP":
			self.stopRun()

		# UNL*OCK: unlock grbl
		elif rexx.abbrev("UNLOCK",cmd,3):
			self.unlock()

		else:
			return "unknown command","Invalid command %s"%(oline)

	#----------------------------------------------------------------------
	def help(self, event=None):
		webbrowser.open(WIKI,new=2)

	#----------------------------------------------------------------------
	def loadRecent(self, recent):
		filename = Utils.getRecent(recent)
		if filename is None: return
		self.load(filename)

	#----------------------------------------------------------------------
	def _loadRecent0(self,event): self.loadRecent(0)
	def _loadRecent1(self,event): self.loadRecent(1)
	def _loadRecent2(self,event): self.loadRecent(2)
	def _loadRecent3(self,event): self.loadRecent(3)
	def _loadRecent4(self,event): self.loadRecent(4)
	def _loadRecent5(self,event): self.loadRecent(5)
	def _loadRecent6(self,event): self.loadRecent(6)
	def _loadRecent7(self,event): self.loadRecent(7)
	def _loadRecent8(self,event): self.loadRecent(8)
	def _loadRecent9(self,event): self.loadRecent(9)

	#----------------------------------------------------------------------
	def _saveConfigFile(self, filename=None):
		if filename is None:
			filename = self.gcode.filename
		Utils.setStr("File", "dir",   os.path.dirname(os.path.abspath(filename)))
		Utils.setStr("File", "file",  os.path.basename(filename))
		Utils.setStr("File", "probe", os.path.basename(self.gcode.probe.filename))

	#----------------------------------------------------------------------
	# Load a file into editor
	#----------------------------------------------------------------------
	def load(self, filename):
		fn,ext = os.path.splitext(filename)
		if ext==".probe":
			if filename is not None:
				self.gcode.probe.filename = filename
				self._saveConfigFile()
			self.gcode.probe.load(filename)
		elif ext == ".stl":
			# FIXME: implements solid import???
			pass
		elif ext==".dxf":
			self.gcode.init()
			self.gcode.importDXF(filename)
			self._saveConfigFile(filename)
		else:
			self.gcode.load(filename)
			self._saveConfigFile()
		Utils.addRecent(filename)

	#----------------------------------------------------------------------
	def save(self, filename):
		fn,ext = os.path.splitext(filename)
		if ext == ".probe":
			# save probe
			if filename is not None:
				self.gcode.probe.filename = filename
				self._saveConfigFile()
			if not self.gcode.probe.isEmpty():
				self.gcode.probe.save()
		elif ext == ".stl":
			#save probe as STL
			self.gcode.probe.saveAsSTL(filename)
		elif ext == ".dxf":
			return self.gcode.saveDXF(filename)
		else:
			if filename is not None:
				self.gcode.filename = filename
				self._saveConfigFile()
			Utils.addRecent(self.gcode.filename)
			return self.gcode.save()

	#----------------------------------------------------------------------
	def saveAll(self, event=None):
		if self.gcode.filename:
			self.save(self.gcode.filename)
			if self.gcode.probe.filename:
				self.save(self.gcode.probe.filename)
		return "break"

	#----------------------------------------------------------------------
	# Open serial port
	#----------------------------------------------------------------------
	def open(self, device, baudrate):
		self.serial = serial.Serial(	device,
						baudrate,
						bytesize=serial.EIGHTBITS,
						parity=serial.PARITY_NONE,
						stopbits=serial.STOPBITS_ONE,
						timeout=0.1,
						xonxoff=False,
						rtscts=False)
		# Toggle DTR to reset Arduino
		self.serial.setDTR(0)
		time.sleep(1)
		CNC.vars["state"] = CONNECTED
		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
		#self.state.config(text=CNC.vars["state"],
		#		background=CNC.vars["color"])
		# toss any data already received, see
		# http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
		self.serial.flushInput()
		self.serial.setDTR(1)
		time.sleep(1)
		self.serial.write(b"\r\n\r\n")
		self._gcount = 0
		self._alarm  = True
		self.thread  = threading.Thread(target=self.serialIO)
		self.thread.start()
		return True

#	#----------------------------------------------------------------------
#	def close(self):
#		if self.serial is None: return
#		try:
#			self.stopRun()
#		except:
#			pass
#		self._runLines = 0
#		self.thread = None
#		time.sleep(1)
#		self.serial.close()
#		self.serial = None
#		CNC.vars["state"] = NOT_CONNECTED
#		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
#		try:
#			self.state.config(text=CNC.vars["state"],
#					background=CNC.vars["color"])
#		except TclError:
#			pass

	#----------------------------------------------------------------------
	# Send to grbl
	#----------------------------------------------------------------------
	def sendGrbl(self, cmd):
#		sys.stdout.write(">>> %s"%(cmd))
#		import traceback
#		traceback.print_stack()
		if self.serial and not self.running:
			self.queue.put(cmd)

	#----------------------------------------------------------------------
	def sendHex(self, hexcode):
		if self.serial is None: return
		self.serial.write(chr(int(hexcode,16)))
		self.serial.flush()

	#----------------------------------------------------------------------
	def hardReset(self):
		if self.serial is not None:
			self.openClose()
		self.openClose()

	#----------------------------------------------------------------------
	def softReset(self):
		if self.serial:
			self.serial.write(b"\030")

	def unlock(self):
		self._alarm = False
		self.sendGrbl("$X\n")

	def home(self):
		self._alarm = False
		self.sendGrbl("$H\n")

	#----------------------------------------------------------------------
	def viewSettings(self):
		self.sendGrbl("$$\n")

	def viewParameters(self):
		self.sendGrbl("$#\n$G\n")

	def viewState(self):
		self.sendGrbl("$G\n")

	def viewBuild(self):
		self.sendGrbl("$I\n")

	def viewStartup(self):
		self.sendGrbl("$N\n")

	def checkGcode(self):
		self.sendGrbl("$C\n")

	def grblhelp(self):
		self.sendGrbl("$\n")

	#----------------------------------------------------------------------
	def goto(self, x=None, y=None, z=None):
		cmd = "G90G0"
		if x is not None: cmd += "X%g"%(x)
		if y is not None: cmd += "Y%g"%(y)
		if z is not None: cmd += "Z%g"%(z)
		self.sendGrbl("%s\n"%(cmd))

	#----------------------------------------------------------------------
	def _wcsSet(self, x, y, z):
		p = WCS.index(CNC.vars["WCS"])
		if p<6:
			cmd = "G10L20P%d"%(p+1)
		elif p==6:
			cmd = "G28.1"
		elif p==7:
			cmd = "G30.1"
		elif p==8:
			cmd = "G92"

		if x is not None: cmd += "X"+str(x)
		if y is not None: cmd += "Y"+str(y)
		if z is not None: cmd += "Z"+str(z)
		self.sendGrbl(cmd+"\n$#\n")
		self.event_generate("<<Status>>",
			data="Set workspace %s to X%s Y%s Z%s"%(WCS[p],str(x),str(y),str(z)))
		self.event_generate("<<CanvasFocus>>")

	#----------------------------------------------------------------------
	def feedHold(self, event=None):
		if event is not None and not self.acceptKey(True): return
		if self.serial is None: return
		self.serial.write(b"!")
		self.serial.flush()
		self._pause = True

	#----------------------------------------------------------------------
	def resume(self, event=None):
		if event is not None and not self.acceptKey(True): return
		if self.serial is None: return
		self.serial.write(b"~")
		self.serial.flush()
		self._msg   = None
		self._alarm = False
		self._pause = False

	#----------------------------------------------------------------------
	def pause(self, event=None):
		if self.serial is None: return
		if self._pause:
			self.resume()
		else:
			self.feedHold()

	#----------------------------------------------------------------------
	# FIXME ????
	#----------------------------------------------------------------------
	def g28Command(self):
		self.sendGrbl("G28.1\n")

	#----------------------------------------------------------------------
	# FIXME ????
	#----------------------------------------------------------------------
	def g30Command(self):
		self.sendGrbl("G30.1\n")

	#----------------------------------------------------------------------
	def emptyQueue(self):
		while self.queue.qsize()>0:
			try:
				self.queue.get_nowait()
			except Empty:
				break

	#----------------------------------------------------------------------
	def initRun(self):
		self._quit   = 0
		self._pause  = False
		self._paths  = None
		self.running = True
		self.disable()
		self.emptyQueue()
		self.queue.put(self.tools["CNC"]["startup"]+"\n")
		time.sleep(1)

	#----------------------------------------------------------------------
	# Called when run is finished
	#----------------------------------------------------------------------
	def runEnded(self):
		self._runLines = 0
		self._quit     = 0
		self._pause    = False
		self.running   = False
		self._msg      = None
		self.enable()

	#----------------------------------------------------------------------
	# Stop the current run
	#----------------------------------------------------------------------
	def stopRun(self, event=None):
		self.feedHold()
		self._stop = True
		time.sleep(1)
		self.softReset()
		time.sleep(1)
		self.unlock()
		self.runEnded()

	#----------------------------------------------------------------------
	# thread performing I/O on serial line
	#----------------------------------------------------------------------
	def serialIO(self):
		cline  = []		# length of pipeline commands
		sline  = []		# pipeline commands
		wait   = False		# wait for commands to complete
		tosend = None		# next string to send
		status = False		# waiting for status <...> report
		tr = tg = time.time()	# last time a ? or $G was send to grbl

		while self.thread:
			t = time.time()

			# refresh machine position?
			if t-tr > SERIAL_POLL:
				# Send one ?
				self.serial.write(b"?")
				status = True
				#print ">S> ?"
				tr = t

			# Fetch new command to send if...
			if tosend is None and not wait and not self._pause and self.queue.qsize()>0:
				try:
					tosend = self.queue.get_nowait()
					#print "+++",repr(tosend)

					if isinstance(tosend, tuple):
						# Count executed commands as well
						self._gcount += 1
						# wait to empty the grbl buffer
						if tosend[0] == WAIT:
							wait = True
							#print "+++ WAIT ON"
						elif tosend[0] == PAUSE:
							if tosend[1] is not None:
								# show our message on machine status
								self._msg = tosend[1]
							# Feed hold
							# Maybe a M0 would be better?
							self.serial.write(b"!")
							#print ">S> !"
						elif tosend[0] == UPDATE:
							self._update = tosend[1]
						tosend = None

					elif not isinstance(tosend, str):
						try:
							tosend = self.gcode.evaluate(tosend)
#							if isinstance(tosend, list):
#								cline.append(len(tosend[0]))
#								sline.append(tosend[0])
#								self.log.put((True,tosend[0]))
							if isinstance(tosend,str):
								tosend += "\n"
							else:
								# Count executed commands as well
								self._gcount += 1
							#print "+++ eval=",repr(tosend),type(tosend)
						except:
							self.log.put((True,sys.exc_info()[1]))
							tosend = None
				except Empty:
					break

				if tosend is not None:
					# All modification in tosend should be
					# done before adding it to cline
					if isinstance(tosend, unicode):
						tosend = tosend.encode("ascii","replace")

					# FIXME should be smarter and apply the feed override
					# also on cards with out feed (the first time only)
					# I should track the feed rate for every card
					# and when it is changed apply a F### command
					# even if it is not there
					if CNC.vars["override"] != 100:
						pat = FEEDPAT.match(tosend)
						if pat is not None:
							try:
								tosend = "%sf%g%s\n" % \
									(pat.group(1),
									 float(pat.group(2))*CNC.vars["override"]/100.0,
									 pat.group(3))
							except:
								pass

					# Bookkeeping of the buffers
					sline.append(tosend)
					cline.append(len(tosend))
					self.log.put((True,tosend))

			# Anything to receive?
			if self.serial.inWaiting() or tosend is None:
				line = str(self.serial.readline()).strip()
				#print "<R<",repr(line)
				#print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
				if line:
					if line[0]=="<":
						pat = STATUSPAT.match(line)
						if pat:
							if not status: self.log.put((False, line+"\n"))
							status = False
							if not self._alarm:
								CNC.vars["state"] = pat.group(1)
							CNC.vars["mx"] = float(pat.group(2))
							CNC.vars["my"] = float(pat.group(3))
							CNC.vars["mz"] = float(pat.group(4))
							CNC.vars["wx"] = float(pat.group(5))
							CNC.vars["wy"] = float(pat.group(6))
							CNC.vars["wz"] = float(pat.group(7))
							self._posUpdate = True

							if pat.group(1) != "Hold" and self._msg:
								self._msg = None

							# Machine is Idle buffer is empty
							# stop waiting and go on
							#print "<<< WAIT=",wait,sline,pat.group(1),sum(cline)
							if wait and not cline and pat.group(1)=="Idle":
								wait = False
								#print "<<< NO MORE WAIT"
						else:
							self.log.put((False, line+"\n"))

					elif line[0]=="[":
						self.log.put((False, line+"\n"))
						pat = POSPAT.match(line)
						if pat:
							if pat.group(1) == "PRB":
								CNC.vars["prbx"] = float(pat.group(2))
								CNC.vars["prby"] = float(pat.group(3))
								CNC.vars["prbz"] = float(pat.group(4))
								if self.running:
									self.gcode.probe.add(
										 CNC.vars["prbx"]
										+CNC.vars["wx"]
										-CNC.vars["mx"],

										 CNC.vars["prby"]
										+CNC.vars["wy"]
										-CNC.vars["my"],

										 CNC.vars["prbz"]
										+CNC.vars["wz"]
										-CNC.vars["mz"])
								self._probeUpdate = True
							CNC.vars[pat.group(1)] = \
								[float(pat.group(2)),
								 float(pat.group(3)),
								 float(pat.group(4))]
						else:
							pat = TLOPAT.match(line)
							if pat:
								CNC.vars[pat.group(1)] = pat.group(2)
								self._probeUpdate = True
							else:
								CNC.vars["G"] = line[1:-1].split()
								CNC.updateG()
								self._gUpdate = True

					else:
						#print "<r<",repr(line)
						self.log.put((False, line+"\n"))
						uline = line.upper()
						if uline.find("ERROR")==0 or uline.find("ALARM")==0:
							self._gcount += 1
							if cline: del cline[0]
							if sline: CNC.vars["errline"] = sline.pop(0)
							if not self._alarm: self._posUpdate = True
							self._alarm = True
							CNC.vars["state"] = line
							if self.running:
								self.emptyQueue()
								# Dangerous calling state of Tk if not reentrant
								self.runEnded()
								tosend = None
								del cline[:]
								del sline[:]

						elif line.find("ok")>=0:
							self._gcount += 1
							if cline: del cline[0]
							if sline: del sline[0]

			# Received external message to stop
			if self._stop:
				self.emptyQueue()
				tosend = None
				del cline[:]
				del sline[:]
				self._stop = False

			#print "tosend='%s'"%(repr(tosend)),"stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
			if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
#				if isinstance(tosend, list):
#					self.serial.write(str(tosend.pop(0)))
#					if not tosend: tosend = None

				#print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
				self.serial.write(bytes(tosend))
#				self.serial.flush()

				tosend = None
				if not self.running and t-tg > G_POLL:
					tosend = b"$G\n"
					sline.append(tosend)
					cline.append(len(tosend))
					tg = t
Ejemplo n.º 11
0
class Sender:
    # Messages types for log Queue
    MSG_BUFFER = 0  # write to buffer one command
    MSG_SEND = 1  # send message
    MSG_RECEIVE = 2  # receive message from controller
    MSG_OK = 3  # ok response from controller, move top most command to terminal
    MSG_ERROR = 4  # error message or exception
    MSG_RUNEND = 5  # run ended
    MSG_CLEAR = 6  # clear buffer

    def __init__(self):
        # Global variables
        self.history = []
        self._historyPos = None
        CNC.loadConfig(Utils.config)
        self.gcode = GCode()
        self.cnc = self.gcode.cnc

        self.log = Queue()  # Log queue returned from GRBL
        self.queue = Queue()  # Command queue to be send to GRBL
        self.pendant = Queue()  # Command queue to be executed from Pendant
        self.serial = None
        self.thread = None
        self.controller = Utils.CONTROLLER["Grbl"]

        self._posUpdate = False  # Update position
        self._probeUpdate = False  # Update probe
        self._gUpdate = False  # Update $G
        self._update = None  # Generic update

        self.running = False
        self._runLines = 0
        self._quit = 0  # Quit counter to exit program
        self._stop = False  # Raise to stop current run
        self._pause = False  # machine is on Hold
        self._alarm = True  # Display alarm message if true
        self._msg = None
        self._sumcline = 0
        self._lastFeed = 0
        self._newFeed = 0

        self._onStart = ""
        self._onStop = ""

    #----------------------------------------------------------------------
    def quit(self, event=None):
        self.saveConfig()
        Pendant.stop()

    #----------------------------------------------------------------------
    def loadConfig(self):
        self.controller = Utils.CONTROLLER.get(
            Utils.getStr("Connection", "controller"), Utils.GRBL0)
        Pendant.port = Utils.getInt("Connection", "pendantport", Pendant.port)
        GCode.LOOP_MERGE = Utils.getBool("File", "dxfloopmerge")
        self.loadHistory()

    #----------------------------------------------------------------------
    def saveConfig(self):
        self.saveHistory()

    #----------------------------------------------------------------------
    def loadHistory(self):
        try:
            f = open(Utils.hisFile, "r")
        except:
            return
        self.history = [x.strip() for x in f]
        f.close()

    #----------------------------------------------------------------------
    def saveHistory(self):
        try:
            f = open(Utils.hisFile, "w")
        except:
            return
        f.write("\n".join(self.history))
        f.close()

    #----------------------------------------------------------------------
    # Evaluate a line for possible expressions
    # can return a python exception, needs to be catched
    #----------------------------------------------------------------------
    def evaluate(self, line):
        return self.gcode.evaluate(CNC.compileLine(line, True))

    #----------------------------------------------------------------------
    # Execute a line as gcode if pattern matches
    # @return True on success
    #	  False otherwise
    #----------------------------------------------------------------------
    def executeGcode(self, line):
        if isinstance(line, tuple) or \
           line[0] in ("$","!","~","?","(","@") or GPAT.match(line):
            self.sendGCode(line)
            return True
        return False

    #----------------------------------------------------------------------
    # Execute a single command
    #----------------------------------------------------------------------
    def executeCommand(self, line):
        #print
        #print "<<<",line
        #try:
        #	line = self.gcode.evaluate(CNC.compileLine(line,True))
        #except:
        #	return "Evaluation error", sys.exc_info()[1]
        #print ">>>",line

        if line is None: return

        oline = line.strip()
        line = oline.replace(",", " ").split()
        cmd = line[0].upper()

        # ABS*OLUTE: Set absolute coordinates
        if rexx.abbrev("ABSOLUTE", cmd, 3):
            self.sendGCode("G90")

        # HELP: open browser to display help
        elif cmd == "HELP":
            self.help()

        # HOME: perform a homing cycle
        elif cmd == "HOME":
            self.home()

        # LO*AD [filename]: load filename containing g-code
        elif rexx.abbrev("LOAD", cmd, 2):
            self.load(line[1])

        # OPEN: open serial connection to grbl
        # CLOSE: close serial connection to grbl
        elif cmd in ("OPEN", "CLOSE"):
            self.openClose()

        # QU*IT: quit program
        # EX*IT: exit program
        elif rexx.abbrev("QUIT", cmd, 2) or rexx.abbrev("EXIT", cmd, 2):
            self.quit()

        # PAUSE: pause cycle
        elif cmd == "PAUSE":
            self.pause()

        # RESUME: resume
        elif cmd == "RESUME":
            self.resume()

        # FEEDHOLD: feedhold
        elif cmd == "FEEDHOLD":
            self.feedHold()

        # REL*ATIVE: switch to relative coordinates
        elif rexx.abbrev("RELATIVE", cmd, 3):
            self.sendGCode("G91")

        # RESET: perform a soft reset to grbl
        elif cmd == "RESET":
            self.softReset()

        # RUN: run g-code
        elif cmd == "RUN":
            self.run()

        # SAFE [z]: safe z to move
        elif cmd == "SAFE":
            try:
                self.cnc.safe = float(line[1])
            except:
                pass
            self.statusbar["text"] = "Safe Z= %g" % (self.cnc.safe)

        # SA*VE [filename]: save to filename or to default name
        elif rexx.abbrev("SAVE", cmd, 2):
            if len(line) > 1:
                self.save(line[1])
            else:
                self.saveAll()

        # SENDHEX: send a hex-char in grbl
        elif cmd == "SENDHEX":
            self.sendHex(line[1])

        # SET [x [y [z]]]: set x,y,z coordinates to current workspace
        elif cmd == "SET":
            try:
                x = float(line[1])
            except:
                x = None
            try:
                y = float(line[2])
            except:
                y = None
            try:
                z = float(line[3])
            except:
                z = None
            self._wcsSet(x, y, z)

        elif cmd == "SET0":
            self._wcsSet(0., 0., 0.)

        elif cmd == "SETX":
            try:
                x = float(line[1])
            except:
                x = ""
            self._wcsSet(x, None, None)

        elif cmd == "SETY":
            try:
                y = float(line[1])
            except:
                y = ""
            self._wcsSet(None, y, None)

        elif cmd == "SETZ":
            try:
                z = float(line[1])
            except:
                z = ""
            self._wcsSet(None, None, z)

        # STOP: stop current run
        elif cmd == "STOP":
            self.stopRun()

        # UNL*OCK: unlock grbl
        elif rexx.abbrev("UNLOCK", cmd, 3):
            self.unlock()

        # Send commands to SMOOTHIE
        elif self.controller == Utils.SMOOTHIE:
            if line[0] in ("help", "version", "mem", "ls", "cd", "pwd", "cat",
                           "rm", "mv", "remount", "play", "progress", "abort",
                           "reset", "dfu", "break", "config-get", "config-set",
                           "get", "set_temp", "get", "get", "net", "load",
                           "save", "upload", "calc_thermistor", "thermistors",
                           "md5sum"):
                self.serial.write(oline + "\n")

        else:
            return _("unknown command"), _("Invalid command %s") % (oline)

    #----------------------------------------------------------------------
    def help(self, event=None):
        webbrowser.open(WIKI, new=2)

    #----------------------------------------------------------------------
    def loadRecent(self, recent):
        filename = Utils.getRecent(recent)
        if filename is None: return
        self.load(filename)

    #----------------------------------------------------------------------
    def _loadRecent0(self, event):
        self.loadRecent(0)

    def _loadRecent1(self, event):
        self.loadRecent(1)

    def _loadRecent2(self, event):
        self.loadRecent(2)

    def _loadRecent3(self, event):
        self.loadRecent(3)

    def _loadRecent4(self, event):
        self.loadRecent(4)

    def _loadRecent5(self, event):
        self.loadRecent(5)

    def _loadRecent6(self, event):
        self.loadRecent(6)

    def _loadRecent7(self, event):
        self.loadRecent(7)

    def _loadRecent8(self, event):
        self.loadRecent(8)

    def _loadRecent9(self, event):
        self.loadRecent(9)

    #----------------------------------------------------------------------
    def _saveConfigFile(self, filename=None):
        if filename is None:
            filename = self.gcode.filename
        Utils.setUtf("File", "dir", os.path.dirname(os.path.abspath(filename)))
        Utils.setUtf("File", "file", os.path.basename(filename))
        Utils.setUtf("File", "probe",
                     os.path.basename(self.gcode.probe.filename))

    #----------------------------------------------------------------------
    # Load a file into editor
    #----------------------------------------------------------------------
    def load(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe":
            if filename is not None:
                self.gcode.probe.filename = filename
                self._saveConfigFile()
            self.gcode.probe.load(filename)
        elif ext == ".orient":
            # save orientation file
            self.gcode.orient.load(filename)
        elif ext == ".stl":
            # FIXME: implements solid import???
            pass
        elif ext == ".dxf":
            self.gcode.init()
            self.gcode.importDXF(filename)
            self._saveConfigFile(filename)
        else:
            self.gcode.load(filename)
            self._saveConfigFile()
        Utils.addRecent(filename)

    #----------------------------------------------------------------------
    def save(self, filename):
        fn, ext = os.path.splitext(filename)
        ext = ext.lower()
        if ext == ".probe":
            # save probe
            if filename is not None:
                self.gcode.probe.filename = filename
                self._saveConfigFile()
            if not self.gcode.probe.isEmpty():
                self.gcode.probe.save()
        elif ext == ".orient":
            # save orientation file
            self.gcode.orient.save(filename)
        elif ext == ".stl":
            #save probe as STL
            self.gcode.probe.saveAsSTL(filename)
        elif ext == ".dxf":
            return self.gcode.saveDXF(filename)
        elif ext == ".txt":
            #save gcode as txt (only enable blocks and no bCNC metadata)
            return self.gcode.saveTXT(filename)
        else:
            if filename is not None:
                self.gcode.filename = filename
                self._saveConfigFile()
            Utils.addRecent(self.gcode.filename)
            return self.gcode.save()

    #----------------------------------------------------------------------
    def saveAll(self, event=None):
        if self.gcode.filename:
            self.save(self.gcode.filename)
            if self.gcode.probe.filename:
                self.save(self.gcode.probe.filename)
        return "break"

    #----------------------------------------------------------------------
    # Open serial port
    #----------------------------------------------------------------------
    def open(self, device, baudrate):
        self.serial = serial.Serial(device,
                                    baudrate,
                                    bytesize=serial.EIGHTBITS,
                                    parity=serial.PARITY_NONE,
                                    stopbits=serial.STOPBITS_ONE,
                                    timeout=0.1,
                                    xonxoff=False,
                                    rtscts=False)
        # Toggle DTR to reset Arduino
        try:
            self.serial.setDTR(0)
        except IOError:
            pass
        time.sleep(1)
        CNC.vars["state"] = CONNECTED
        CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
        #self.state.config(text=CNC.vars["state"],
        #		background=CNC.vars["color"])
        # toss any data already received, see
        # http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
        self.serial.flushInput()
        try:
            self.serial.setDTR(1)
        except IOError:
            pass
        time.sleep(1)
        self.serial.write(b"\n\n")
        self._gcount = 0
        self._alarm = True
        self.thread = threading.Thread(target=self.serialIO)
        self.thread.start()
        return True

    #----------------------------------------------------------------------
    # Close serial port
    #----------------------------------------------------------------------
    def close(self):
        if self.serial is None: return
        try:
            self.stopRun()
        except:
            pass
        self._runLines = 0
        self.thread = None
        time.sleep(1)
        self.serial.close()
        self.serial = None
        CNC.vars["state"] = NOT_CONNECTED
        CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]

    #----------------------------------------------------------------------
    # Send to controller a gcode or command
    # WARNING: it has to be a single line!
    #----------------------------------------------------------------------
    def sendGCode(self, cmd):
        if self.serial and not self.running:
            if isinstance(cmd, tuple):
                self.queue.put(cmd)
            else:
                self.queue.put(cmd + "\n")

    #----------------------------------------------------------------------
    def sendHex(self, hexcode):
        if self.serial is None: return
        self.serial.write(chr(int(hexcode, 16)))
        self.serial.flush()

    #----------------------------------------------------------------------
    def hardReset(self):
        self.busy()
        if self.serial is not None:
            if self.controller == Utils.SMOOTHIE:
                self.serial.write(b"reset\n")
            self.openClose()
            if self.controller == Utils.SMOOTHIE:
                time.sleep(6)
        self.openClose()
        self.stopProbe()
        self._alarm = False
        self.notBusy()

    #----------------------------------------------------------------------
    def softReset(self):
        if self.serial:
            #	if self.controller in (Utils.GRBL, Utils.GRBL1):
            self.serial.write(b"\030")
        #	elif self.controller == Utils.SMOOTHIE:
        #		self.serial.write(b"reset\n")
        self.stopProbe()
        self._alarm = False

    #----------------------------------------------------------------------
    def unlock(self):
        self._alarm = False
        self.sendGCode("$X")

    #----------------------------------------------------------------------
    def home(self, event=None):
        self._alarm = False
        self.sendGCode("$H")

    #----------------------------------------------------------------------
    def viewSettings(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$$")

    def viewParameters(self):
        self.sendGCode("$#")

    def viewState(self):
        self.sendGCode("$G")

    def viewBuild(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$I")
        elif self.controller == Utils.SMOOTHIE:
            self.serial.write(b"version\n")

    def viewStartup(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$N")

    def checkGcode(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$C")

    def grblHelp(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$")
        elif self.controller == Utils.SMOOTHIE:
            self.serial.write(b"help\n")

    def grblRestoreSettings(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$RST=$")

    def grblRestoreWCS(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$RST=#")

    def grblRestoreAll(self):
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            self.sendGCode("$RST=#")

    #----------------------------------------------------------------------
    def goto(self, x=None, y=None, z=None):
        cmd = "G90G0"
        if x is not None: cmd += "X%g" % (x)
        if y is not None: cmd += "Y%g" % (y)
        if z is not None: cmd += "Z%g" % (z)
        self.sendGCode("%s" % (cmd))

    #----------------------------------------------------------------------
    # FIXME Duplicate with ControlPage
    #----------------------------------------------------------------------
    def _wcsSet(self, x, y, z):
        p = WCS.index(CNC.vars["WCS"])
        if p < 6:
            cmd = "G10L20P%d" % (p + 1)
        elif p == 6:
            cmd = "G28.1"
        elif p == 7:
            cmd = "G30.1"
        elif p == 8:
            cmd = "G92"

        pos = ""
        if x is not None and abs(x) < 10000.0: pos += "X" + str(x)
        if y is not None and abs(y) < 10000.0: pos += "Y" + str(y)
        if z is not None and abs(z) < 10000.0: pos += "Z" + str(z)
        cmd += pos
        self.sendGCode(cmd)
        self.sendGCode("$#")
        self.event_generate("<<Status>>",
                            data=(_("Set workspace %s to %s") % (WCS[p], pos)))
        #data=(_("Set workspace %s to %s")%(WCS[p],pos)).encode("utf8"))
        self.event_generate("<<CanvasFocus>>")

    #----------------------------------------------------------------------
    def feedHold(self, event=None):
        if event is not None and not self.acceptKey(True): return
        if self.serial is None: return
        self.serial.write(b"!")
        self.serial.flush()
        self._pause = True

    #----------------------------------------------------------------------
    def resume(self, event=None):
        if event is not None and not self.acceptKey(True): return
        if self.serial is None: return
        self.serial.write(b"~")
        self.serial.flush()
        self._msg = None
        self._alarm = False
        self._pause = False

    #----------------------------------------------------------------------
    def pause(self, event=None):
        if self.serial is None: return
        if self._pause:
            self.resume()
        else:
            self.feedHold()

    #----------------------------------------------------------------------
    # FIXME ????
    #----------------------------------------------------------------------
    def g28Command(self):
        self.sendGCode("G28.1")

    #----------------------------------------------------------------------
    # FIXME ????
    #----------------------------------------------------------------------
    def g30Command(self):
        self.sendGCode("G30.1")

    #----------------------------------------------------------------------
    def emptyQueue(self):
        while self.queue.qsize() > 0:
            try:
                self.queue.get_nowait()
            except Empty:
                break

    #----------------------------------------------------------------------
    def stopProbe(self):
        if self.gcode.probe.start:
            self.gcode.probe.clear()

    #----------------------------------------------------------------------
    def getBufferFill(self):
        return self._sumcline * 100. / RX_BUFFER_SIZE

    #----------------------------------------------------------------------
    def initRun(self):
        self._quit = 0
        self._pause = False
        self._paths = None
        self.running = True
        self.disable()
        self.emptyQueue()
        time.sleep(1)

    #----------------------------------------------------------------------
    # Called when run is finished
    #----------------------------------------------------------------------
    def runEnded(self):
        if self.running:
            self.log.put((Sender.MSG_RUNEND, _("Run ended")))
            self.log.put((Sender.MSG_RUNEND, str(datetime.now())))
            self.log.put((Sender.MSG_RUNEND, str(CNC.vars["msg"])))
            if self._onStop:
                try:
                    os.system(self._onStop)
                except:
                    pass
        self._runLines = 0
        self._quit = 0
        self._msg = None
        self._pause = False
        self.running = False
        CNC.vars["running"] = False

    #----------------------------------------------------------------------
    # Purge the buffer of the controller. Unfortunately we have to perform
    # a reset to clear the buffer of the controller
    #---------------------------------------------------------------------
    def purgeController(self):
        time.sleep(1)
        # remember and send all G commands
        G = " ".join([x for x in CNC.vars["G"] if x[0] == "G"])  # remember $G
        TLO = CNC.vars["TLO"]
        self.softReset()  # reset controller
        if self.controller in (Utils.GRBL0, Utils.GRBL1):
            time.sleep(1)
            self.unlock()
        self.runEnded()
        self.stopProbe()
        if G: self.sendGCode(G)  # restore $G
        self.sendGCode("G43.1Z%s" % (TLO))  # restore TLO
        self.sendGCode("$G")

    #----------------------------------------------------------------------
    # Stop the current run
    #----------------------------------------------------------------------
    def stopRun(self, event=None):
        self.feedHold()
        self._stop = True
        # if we are in the process of submitting do not do anything
        if self._runLines != sys.maxint:
            self.purgeController()

    #----------------------------------------------------------------------
    # thread performing I/O on serial line
    #----------------------------------------------------------------------
    def serialIO(self):
        cline = []  # length of pipeline commands
        sline = []  # pipeline commands
        wait = False  # wait for commands to complete
        tosend = None  # next string to send
        status = False  # waiting for status <...> report
        tr = tg = time.time()  # last time a ? or $G was send to grbl

        while self.thread:
            t = time.time()

            # refresh machine position?
            if t - tr > SERIAL_POLL:
                self.serial.write(b"?")
                status = True
                tr = t

            # Fetch new command to send if...
            if tosend is None and not wait and not self._pause and self.queue.qsize(
            ) > 0:
                try:
                    tosend = self.queue.get_nowait()
                    #print "+++",repr(tosend)

                    if isinstance(tosend, tuple):
                        #print "gcount tuple=",self._gcount
                        # wait to empty the grbl buffer
                        if tosend[0] == WAIT:
                            # Don't count WAIT until we are idle!
                            wait = True
                            #print "+++ WAIT ON"
                            #print "gcount=",self._gcount, self._runLines
                        elif tosend[0] == MSG:
                            # Count executed commands as well
                            self._gcount += 1
                            if tosend[1] is not None:
                                # show our message on machine status
                                self._msg = tosend[1]
                        elif tosend[0] == UPDATE:
                            # Count executed commands as well
                            self._gcount += 1
                            self._update = tosend[1]
                        else:
                            # Count executed commands as well
                            self._gcount += 1
                        tosend = None

                    elif not isinstance(tosend, str) and not isinstance(
                            tosend, unicode):
                        try:
                            tosend = self.gcode.evaluate(tosend)
                            #							if isinstance(tosend, list):
                            #								cline.append(len(tosend[0]))
                            #								sline.append(tosend[0])
                            if isinstance(tosend, str) or isinstance(
                                    tosend, unicode):
                                tosend += "\n"
                            else:
                                # Count executed commands as well
                                self._gcount += 1
                                #print "gcount str=",self._gcount
                            #print "+++ eval=",repr(tosend),type(tosend)
                        except:
                            for s in str(sys.exc_info()[1]).splitlines():
                                self.log.put((Sender.MSG_ERROR, s))
                            self._gcount += 1
                            tosend = None
                except Empty:
                    break

                if tosend is not None:
                    # All modification in tosend should be
                    # done before adding it to cline
                    if isinstance(tosend, unicode):
                        tosend = tosend.encode("ascii", "replace")

                    #Keep track of last feed
                    pat = FEEDPAT.match(tosend)
                    if pat is not None:
                        self._lastFeed = pat.group(2)

                    #If Override change, attach feed
                    if CNC.vars["overrideChanged"]:
                        CNC.vars["overrideChanged"] = False
                        self._newFeed = float(
                            self._lastFeed) * CNC.vars["override"] / 100.0
                        if pat is None and self._newFeed != 0:
                            tosend = "f%g" % (self._newFeed) + tosend

                    #Apply override Feed
                    if CNC.vars["override"] != 100 and self._newFeed != 0:
                        pat = FEEDPAT.match(tosend)
                        if pat is not None:
                            try:
                                tosend = "%sf%g%s\n" % \
                                 (pat.group(1),
                                  self._newFeed,
                                  pat.group(3))
                            except:
                                pass

                    # Bookkeeping of the buffers
                    sline.append(tosend)
                    cline.append(len(tosend))

            # Anything to receive?
            if self.serial.inWaiting() or tosend is None:
                line = str(self.serial.readline()).strip()
                #print "<R<",repr(line)
                #print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
                if not line:
                    pass

                elif line[0] == "<":
                    if not status:
                        self.log.put((Sender.MSG_RECEIVE, line))

                    elif self.controller == Utils.GRBL1:
                        status = False
                        fields = line[1:-1].split("|")
                        #print fields
                        if not self._alarm:
                            CNC.vars["state"] = fields[0]
                        for field in fields[1:]:
                            word = SPLITPAT.split(field)
                            if word[0] == "MPos":
                                CNC.vars["mx"] = float(word[1])
                                CNC.vars["my"] = float(word[2])
                                CNC.vars["mz"] = float(word[3])
                                CNC.vars["wx"] = round(
                                    CNC.vars["mx"] - CNC.vars["wcox"],
                                    CNC.digits)
                                CNC.vars["wy"] = round(
                                    CNC.vars["my"] - CNC.vars["wcoy"],
                                    CNC.digits)
                                CNC.vars["wz"] = round(
                                    CNC.vars["mz"] - CNC.vars["wcoz"],
                                    CNC.digits)
                                self._posUpdate = True

                            elif word[0] == "F":
                                CNC.vars["curfeed"] = float(word[1])
                            elif word[0] == "Bf":
                                CNC.vars["planner"] = int(word[1])
                                CNC.vars["rxbytes"] = int(word[2])
                            elif word[0] == "Ov":
                                CNC.vars["Ovfeed"] = int(word[1])
                                CNC.vars["Ovrapid"] = int(word[2])
                                CNC.vars["Ovspindle"] = int(word[2])
                            elif word[0] == "WCO":
                                CNC.vars["wcox"] = float(word[1])
                                CNC.vars["wcoy"] = float(word[2])
                                CNC.vars["wcoz"] = float(word[3])

                    else:
                        status = False
                        pat = STATUSPAT.match(line)
                        if pat:
                            if not self._alarm:
                                CNC.vars["state"] = pat.group(1)
                            CNC.vars["mx"] = float(pat.group(2))
                            CNC.vars["my"] = float(pat.group(3))
                            CNC.vars["mz"] = float(pat.group(4))

                            CNC.vars["wx"] = float(pat.group(5))
                            CNC.vars["wy"] = float(pat.group(6))
                            CNC.vars["wz"] = float(pat.group(7))

                            CNC.vars["wcox"] = CNC.vars["mx"] - CNC.vars["wx"]
                            CNC.vars["wcoy"] = CNC.vars["my"] - CNC.vars["wy"]
                            CNC.vars["wcoz"] = CNC.vars["mz"] - CNC.vars["wz"]

                            self._posUpdate = True
                            if pat.group(1) != "Hold" and self._msg:
                                self._msg = None

                            # Machine is Idle buffer is empty
                            # stop waiting and go on
                            #print "<<< WAIT=",wait,sline,pat.group(1),sum(cline)
                            #print ">>>", line
                            if wait and not cline and pat.group(1) == "Idle":
                                #print ">>>",line
                                wait = False
                                #print "<<< NO MORE WAIT"
                                self._gcount += 1
                        else:
                            self.log.put((Sender.MSG_RECEIVE, line))

                elif line[0] == "[":
                    self.log.put((Sender.MSG_RECEIVE, line))
                    if self.controller == Utils.GRBL1:
                        word = SPLITPAT.split(line[1:-1])
                        #print word
                        if word[0] == "PRB":
                            CNC.vars["prbx"] = float(word[1])
                            CNC.vars["prby"] = float(word[2])
                            CNC.vars["prbz"] = float(word[3])
                            #if self.running:
                            self.gcode.probe.add(
                                CNC.vars["prbx"] - CNC.vars["wcox"],
                                CNC.vars["prby"] - CNC.vars["wcoy"],
                                CNC.vars["prbz"] - CNC.vars["wcoy"])
                            self._probeUpdate = True
                            CNC.vars[word[0]] = word[1:]
                        elif word[0] == "GC":
                            CNC.vars["G"] = word[1:]
                            CNC.updateG()
                            self._gUpdate = True
                        elif word[0] == "TLO":
                            CNC.vars[word[0]] = word[1]
                            self._probeUpdate = True
                        else:
                            CNC.vars[word[0]] = word[1:]
                    else:
                        pat = POSPAT.match(line)
                        if pat:
                            if pat.group(1) == "PRB":
                                CNC.vars["prbx"] = float(pat.group(2))
                                CNC.vars["prby"] = float(pat.group(3))
                                CNC.vars["prbz"] = float(pat.group(4))
                                #if self.running:
                                self.gcode.probe.add(
                                    CNC.vars["prbx"] + CNC.vars["wx"] -
                                    CNC.vars["mx"], CNC.vars["prby"] +
                                    CNC.vars["wy"] - CNC.vars["my"],
                                    CNC.vars["prbz"] + CNC.vars["wz"] -
                                    CNC.vars["mz"])
                                self._probeUpdate = True
                            CNC.vars[pat.group(1)] = \
                             [float(pat.group(2)),
                              float(pat.group(3)),
                              float(pat.group(4))]
                        else:
                            pat = TLOPAT.match(line)
                            if pat:
                                CNC.vars[pat.group(1)] = pat.group(2)
                                self._probeUpdate = True
                            elif DOLLARPAT.match(line):
                                CNC.vars["G"] = line[1:-1].split()
                                CNC.updateG()
                                self._gUpdate = True

                elif "error:" in line or "ALARM:" in line:
                    self.log.put((Sender.MSG_ERROR, line))
                    self._gcount += 1
                    #print "gcount ERROR=",self._gcount
                    if cline: del cline[0]
                    if sline: CNC.vars["errline"] = sline.pop(0)
                    if not self._alarm: self._posUpdate = True
                    self._alarm = True
                    CNC.vars["state"] = line
                    if self.running:
                        self._stop = True
                        self.runEnded()

                elif line[:4] == "Grbl":  # and self.running:
                    tg = time.time()
                    self.log.put((Sender.MSG_RECEIVE, line))
                    self._stop = True
                    del cline[:]  # After reset clear the buffer counters
                    del sline[:]
                    self.runEnded()
                    CNC.vars["version"] = line.split()[1]
                    # Detect controller
                    if self.controller in (Utils.GRBL0, Utils.GRBL1):
                        self.controller = int(CNC.vars["version"][0])

                elif line.find("ok") >= 0:
                    self.log.put((Sender.MSG_OK, line))
                    self._gcount += 1
                    if cline: del cline[0]
                    if sline: del sline[0]
                    #print "gcount OK=",self._gcount
                    #print "SLINE:",sline
                    if self._alarm and not self.running:
                        # turn off alarm for connected status once
                        # a valid gcode event occurs
                        self._alarm = False

                else:
                    self.log.put((Sender.MSG_RECEIVE, line))

            # Received external message to stop
            if self._stop:
                self.emptyQueue()
                tosend = None
                self.log.put((Sender.MSG_CLEAR, ""))
                # WARNING if maxint then it means we are still preparing/sending
                # lines from from bCNC.run(), so don't stop
                if self._runLines != sys.maxint:
                    self._stop = False

            #print "tosend='%s'"%(repr(tosend)),"stack=",sline,
            #	"sum=",sum(cline),"wait=",wait,"pause=",self._pause
            if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
                self._sumcline = sum(cline)
                #				if isinstance(tosend, list):
                #					self.serial.write(str(tosend.pop(0)))
                #					if not tosend: tosend = None

                #print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
                if self.controller == Utils.SMOOTHIE: tosend = tosend.upper()
                self.serial.write(bytes(tosend))
                #self.serial.write(tosend.encode("utf8"))
                #self.serial.flush()
                self.log.put((Sender.MSG_BUFFER, tosend))

                tosend = None
                if not self.running and t - tg > G_POLL:
                    tosend = b"$G\n"
                    sline.append(tosend)
                    cline.append(len(tosend))
                    tg = t
Ejemplo n.º 12
0
Archivo: Sender.py Proyecto: onekk/bCNC
class Sender:
	# Messages types for log Queue
	MSG_BUFFER  =  0	# write to buffer one command
	MSG_SEND    =  1	# send message
	MSG_RECEIVE =  2	# receive message from controller
	MSG_OK	    =  3	# ok response from controller, move top most command to terminal
	MSG_ERROR   =  4	# error message or exception
	MSG_RUNEND  =  5	# run ended
	MSG_CLEAR   =  6	# clear buffer

	def __init__(self):
		# Global variables
		self.history	 = []
		self._historyPos = None

		#self.mcontrol     = None
		self.controllers = {}
		self.controllerLoad()
		self.controllerSet("GRBL1")

		CNC.loadConfig(Utils.config)
		self.gcode = GCode()
		self.cnc   = self.gcode.cnc

		self.log	 = Queue()	# Log queue returned from GRBL
		self.queue	 = Queue()	# Command queue to be send to GRBL
		self.pendant	 = Queue()	# Command queue to be executed from Pendant
		self.serial	 = None
		self.thread	 = None

		self._posUpdate  = False	# Update position
		self._probeUpdate= False	# Update probe
		self._gUpdate	 = False	# Update $G
		self._update	 = None		# Generic update

		self.running	 = False
		self.runningPrev = None
		self.cleanAfter  = False
		self._runLines	 = 0
		self._quit	 = 0		# Quit counter to exit program
		self._stop	 = False	# Raise to stop current run
		self._pause	 = False	# machine is on Hold
		self._alarm	 = True		# Display alarm message if true
		self._msg	 = None
		self._sumcline	 = 0
		self._lastFeed	 = 0
		self._newFeed	 = 0

		self._onStart    = ""
		self._onStop     = ""


	#----------------------------------------------------------------------
	def controllerLoad(self):
		# Find plugins in the controllers directory and load them
		for f in glob.glob("%s/controllers/*.py"%(Utils.prgpath)):
			name,ext = os.path.splitext(os.path.basename(f))
			if name[0] == '_': continue
			#print("Loaded motion controller plugin: %s"%(name))
			try:
				exec("import %s"%(name))
				self.controllers[name] = eval("%s.Controller(self)"%(name))
			except (ImportError, AttributeError):
				typ, val, tb = sys.exc_info()
				traceback.print_exception(typ, val, tb)

	#----------------------------------------------------------------------
	def controllerList(self):
		#print("ctrlist")
		#self.controllers["GRBL1"].test()
		#if len(self.controllers.keys()) < 1: self.controllerLoad()
		return sorted(self.controllers.keys())

	#----------------------------------------------------------------------
	def controllerSet(self, ctl):
		#print("Activating motion controller plugin: %s"%(ctl))
		if ctl in self.controllers.keys():
			self.controller = ctl
			self.mcontrol = self.controllers[ctl]
			#self.mcontrol.test()


	#----------------------------------------------------------------------
	def quit(self, event=None):
		self.saveConfig()
		Pendant.stop()

	#----------------------------------------------------------------------
	def loadConfig(self):
		self.controllerSet(Utils.getStr("Connection", "controller"))
		Pendant.port	 = Utils.getInt("Connection","pendantport",Pendant.port)
		GCode.LOOP_MERGE = Utils.getBool("File","dxfloopmerge")
		self.loadHistory()

	#----------------------------------------------------------------------
	def saveConfig(self):
		self.saveHistory()

	#----------------------------------------------------------------------
	def loadHistory(self):
		try:
			f = open(Utils.hisFile,"r")
		except:
			return
		self.history = [x.strip() for x in f]
		f.close()

	#----------------------------------------------------------------------
	def saveHistory(self):
		try:
			f = open(Utils.hisFile,"w")
		except:
			return
		f.write("\n".join(self.history))
		f.close()

	#----------------------------------------------------------------------
	# Evaluate a line for possible expressions
	# can return a python exception, needs to be catched
	#----------------------------------------------------------------------
	def evaluate(self, line):
		return self.gcode.evaluate(CNC.compileLine(line,True))

	#----------------------------------------------------------------------
	# Execute a line as gcode if pattern matches
	# @return True on success
	#	  False otherwise
	#----------------------------------------------------------------------
	def executeGcode(self, line):
		if isinstance(line, tuple) or \
		   line[0] in ("$","!","~","?","(","@") or GPAT.match(line):
			self.sendGCode(line)
			return True
		return False

	#----------------------------------------------------------------------
	# Execute a single command
	#----------------------------------------------------------------------
	def executeCommand(self, line):
		#print
		#print "<<<",line
		#try:
		#	line = self.gcode.evaluate(CNC.compileLine(line,True))
		#except:
		#	return "Evaluation error", sys.exc_info()[1]
		#print ">>>",line

		if line is None: return

		oline = line.strip()
		line  = oline.replace(","," ").split()
		cmd   = line[0].upper()

		# ABS*OLUTE: Set absolute coordinates
		if rexx.abbrev("ABSOLUTE",cmd,3):
			self.sendGCode("G90")

		# HELP: open browser to display help
		elif cmd == "HELP":
			self.help()

		# HOME: perform a homing cycle
		elif cmd == "HOME":
			self.home()

		# LO*AD [filename]: load filename containing g-code
		elif rexx.abbrev("LOAD",cmd,2):
			self.load(line[1])

		# OPEN: open serial connection to grbl
		# CLOSE: close serial connection to grbl
		elif cmd in ("OPEN","CLOSE"):
			self.openClose()

		# QU*IT: quit program
		# EX*IT: exit program
		elif rexx.abbrev("QUIT",cmd,2) or rexx.abbrev("EXIT",cmd,2):
			self.quit()

		# PAUSE: pause cycle
		elif cmd == "PAUSE":
			self.pause()

		# RESUME: resume
		elif cmd == "RESUME":
			self.resume()

		# FEEDHOLD: feedhold
		elif cmd == "FEEDHOLD":
			self.feedHold()

		# REL*ATIVE: switch to relative coordinates
		elif rexx.abbrev("RELATIVE",cmd,3):
			self.sendGCode("G91")

		# RESET: perform a soft reset to grbl
		elif cmd == "RESET":
			self.softReset()

		# RUN: run g-code
		elif cmd == "RUN":
			self.run()

		# SAFE [z]: safe z to move
		elif cmd=="SAFE":
			try: CNC.vars["safe"] = float(line[1])
			except: pass
			self.statusbar["text"] = "Safe Z= %g"%(CNC.vars["safe"])

		# SA*VE [filename]: save to filename or to default name
		elif rexx.abbrev("SAVE",cmd,2):
			if len(line)>1:
				self.save(line[1])
			else:
				self.saveAll()

		# SENDHEX: send a hex-char in grbl
		elif cmd == "SENDHEX":
			self.sendHex(line[1])

		# SET [x [y [z]]]: set x,y,z coordinates to current workspace
		elif cmd == "SET":
			try: x = float(line[1])
			except: x = None
			try: y = float(line[2])
			except: y = None
			try: z = float(line[3])
			except: z = None
			self._wcsSet(x,y,z)

		elif cmd == "SET0":
			self._wcsSet(0.,0.,0.)

		elif cmd == "SETX":
			try: x = float(line[1])
			except: x = ""
			self._wcsSet(x,None,None)

		elif cmd == "SETY":
			try: y = float(line[1])
			except: y = ""
			self._wcsSet(None,y,None)

		elif cmd == "SETZ":
			try: z = float(line[1])
			except: z = ""
			self._wcsSet(None,None,z)

		# STOP: stop current run
		elif cmd == "STOP":
			self.stopRun()

		# UNL*OCK: unlock grbl
		elif rexx.abbrev("UNLOCK",cmd,3):
			self.unlock()

		# Send commands to SMOOTHIE
		elif self.mcontrol.executeCommand(oline, line, cmd):
			pass

		else:
			return _("unknown command"),_("Invalid command %s")%(oline)

	#----------------------------------------------------------------------
	def help(self, event=None):
		webbrowser.open(WIKI,new=2)

	#----------------------------------------------------------------------
	def loadRecent(self, recent):
		filename = Utils.getRecent(recent)
		if filename is None: return
		self.load(filename)

	#----------------------------------------------------------------------
	def _loadRecent0(self,event): self.loadRecent(0)

	# ----------------------------------------------------------------------
	def _loadRecent1(self,event): self.loadRecent(1)

	# ----------------------------------------------------------------------
	def _loadRecent2(self,event): self.loadRecent(2)

	# ----------------------------------------------------------------------
	def _loadRecent3(self,event): self.loadRecent(3)

	# ----------------------------------------------------------------------
	def _loadRecent4(self,event): self.loadRecent(4)

	# ----------------------------------------------------------------------
	def _loadRecent5(self,event): self.loadRecent(5)

	# ----------------------------------------------------------------------
	def _loadRecent6(self,event): self.loadRecent(6)

	# ----------------------------------------------------------------------
	def _loadRecent7(self,event): self.loadRecent(7)

	# ----------------------------------------------------------------------
	def _loadRecent8(self,event): self.loadRecent(8)

	# ----------------------------------------------------------------------
	def _loadRecent9(self,event): self.loadRecent(9)

	#----------------------------------------------------------------------
	def _saveConfigFile(self, filename=None):
		if filename is None:
			filename = self.gcode.filename
		Utils.setUtf("File", "dir",   os.path.dirname(os.path.abspath(filename)))
		Utils.setUtf("File", "file",  os.path.basename(filename))
		Utils.setUtf("File", "probe", os.path.basename(self.gcode.probe.filename))

	#----------------------------------------------------------------------
	# Load a file into editor
	#----------------------------------------------------------------------
	def load(self, filename):
		fn,ext = os.path.splitext(filename)
		ext = ext.lower()
		if ext==".probe":
			if filename is not None:
				self.gcode.probe.filename = filename
				self._saveConfigFile()
			self.gcode.probe.load(filename)
		elif ext == ".orient":
			# save orientation file
			self.gcode.orient.load(filename)
		elif ext == ".stl" or ext == ".ply":
			# FIXME: implements solid import???
			import tkMessageBox
			tkMessageBox.showinfo("Open 3D Mesh", "Importing of 3D mesh files in .STL and .PLY format is supported by SliceMesh plugin.\nYou can find it in Tools->SliceMesh.")
		elif ext==".dxf":
			self.gcode.init()
			self.gcode.importDXF(filename)
			self._saveConfigFile(filename)
		elif ext==".svg":
			self.gcode.init()
			self.gcode.importSVG(filename)
			self._saveConfigFile(filename)
		else:
			self.gcode.load(filename)
			self._saveConfigFile()
		Utils.addRecent(filename)

	#----------------------------------------------------------------------
	def save(self, filename):
		fn,ext = os.path.splitext(filename)
		ext = ext.lower()
		if ext == ".probe" or ext == ".xyz":
			# save probe
			if not self.gcode.probe.isEmpty():
				self.gcode.probe.save(filename)
			if filename is not None:
				self._saveConfigFile()
		elif ext == ".orient":
			# save orientation file
			self.gcode.orient.save(filename)
		elif ext == ".stl":
			#save probe as STL
			self.gcode.probe.saveAsSTL(filename)
		elif ext == ".dxf":
			return self.gcode.saveDXF(filename)
		elif ext == ".svg":
			return self.gcode.saveSVG(filename)
		elif ext == ".txt":
			#save gcode as txt (only enabled blocks and no bCNC metadata)
			return self.gcode.saveTXT(filename)
		else:
			if filename is not None:
				self.gcode.filename = filename
				self._saveConfigFile()
			Utils.addRecent(self.gcode.filename)
			return self.gcode.save()

	#----------------------------------------------------------------------
	def saveAll(self, event=None):
		if self.gcode.filename:
			self.save(self.gcode.filename)
			if self.gcode.probe.filename:
				self.save(self.gcode.probe.filename)
		return "break"

	#----------------------------------------------------------------------
	# Open serial port
	#----------------------------------------------------------------------
	def open(self, device, baudrate):
		#self.serial = serial.Serial(
		self.serial = serial.serial_for_url(
						device,
						baudrate,
						bytesize=serial.EIGHTBITS,
						parity=serial.PARITY_NONE,
						stopbits=serial.STOPBITS_ONE,
						timeout=SERIAL_TIMEOUT,
						xonxoff=False,
						rtscts=False)
		# Toggle DTR to reset Arduino
		try:
			self.serial.setDTR(0)
		except IOError:
			pass
		time.sleep(1)
		CNC.vars["state"] = CONNECTED
		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]
		#self.state.config(text=CNC.vars["state"],
		#		background=CNC.vars["color"])
		# toss any data already received, see
		# http://pyserial.sourceforge.net/pyserial_api.html#serial.Serial.flushInput
		self.serial.flushInput()
		try:
			self.serial.setDTR(1)
		except IOError:
			pass
		time.sleep(1)
		self.serial.write(b"\n\n")
		self._gcount = 0
		self._alarm  = True
		self.thread  = threading.Thread(target=self.serialIO)
		self.thread.start()
		return True

	#----------------------------------------------------------------------
	# Close serial port
	#----------------------------------------------------------------------
	def close(self):
		if self.serial is None: return
		try:
			self.stopRun()
		except:
			pass
		self._runLines = 0
		self.thread = None
		time.sleep(1)
		try:
			self.serial.close()
		except:
			pass
		self.serial = None
		CNC.vars["state"] = NOT_CONNECTED
		CNC.vars["color"] = STATECOLOR[CNC.vars["state"]]

	#----------------------------------------------------------------------
	# Send to controller a gcode or command
	# WARNING: it has to be a single line!
	#----------------------------------------------------------------------
	def sendGCode(self, cmd):
		if self.serial and not self.running:
			if isinstance(cmd,tuple):
				self.queue.put(cmd)
			else:
				self.queue.put(cmd+"\n")

	#----------------------------------------------------------------------
	def sendHex(self, hexcode):
		if self.serial is None: return
		self.serial.write(chr(int(hexcode,16)))
		self.serial.flush()

	#----------------------------------------------------------------------
	# FIXME: legacy wrappers. try to call mcontrol directly instead:
	#----------------------------------------------------------------------
	def hardReset(self):			self.mcontrol.hardReset()
	def softReset(self, clearAlarm=True):	self.mcontrol.softReset(clearAlarm)
	def unlock(self, clearAlarm=True):	self.mcontrol.unlock(clearAlarm)
	def home(self, event=None):		self.mcontrol.home(event)
	def viewSettings(self):			self.mcontrol.viewSettings()
	def viewParameters(self):		self.mcontrol.viewParameters()
	def viewState(self):			self.mcontrol.viewState()
	def viewBuild(self):			self.mcontrol.viewBuild()
	def viewStartup(self):			self.mcontrol.viewStartup()
	def checkGcode(self):			self.mcontrol.checkGcode()
	def grblHelp(self):			self.mcontrol.grblHelp()
	def grblRestoreSettings(self):		self.mcontrol.grblRestoreSettings()
	def grblRestoreWCS(self):		self.mcontrol.grblRestoreWCS()
	def grblRestoreAll(self):		self.mcontrol.grblRestoreAll()
	def goto(self, x=None, y=None, z=None):	self.mcontrol.goto(x,y,z)
	def _wcsSet(self, x, y, z):		self.mcontrol._wcsSet(x,y,z) # FIXME Duplicate with ControlPage
	def feedHold(self, event=None):		self.mcontrol.feedHold(event)
	def resume(self, event=None):		self.mcontrol.resume(event)
	def pause(self, event=None):		self.mcontrol.pause(event)
	def purgeController(self):		self.mcontrol.purgeController()
	def g28Command(self):			self.sendGCode("G28.1") #FIXME: ???
	def g30Command(self):			self.sendGCode("G30.1") #FIXME: ???

	#----------------------------------------------------------------------
	def emptyQueue(self):
		while self.queue.qsize()>0:
			try:
				self.queue.get_nowait()
			except Empty:
				break

	#----------------------------------------------------------------------
	def stopProbe(self):
		if self.gcode.probe.start:
			self.gcode.probe.clear()

	#----------------------------------------------------------------------
	def getBufferFill(self):
		return self._sumcline * 100. / RX_BUFFER_SIZE

	#----------------------------------------------------------------------
	def initRun(self):
		self._quit   = 0
		self._pause  = False
		self._paths  = None
		self.running = True
		self.disable()
		self.emptyQueue()
		time.sleep(1)

	#----------------------------------------------------------------------
	# Called when run is finished
	#----------------------------------------------------------------------
	def runEnded(self):
		if self.running:
			self.log.put((Sender.MSG_RUNEND,_("Run ended")))
			self.log.put((Sender.MSG_RUNEND, str(datetime.now())))
			self.log.put((Sender.MSG_RUNEND, str(CNC.vars["msg"])))
			if self._onStop:
				try:
					os.system(self._onStop)
				except:
					pass
		self._runLines = 0
		self._quit     = 0
		self._msg      = None
		self._pause    = False
		self.running   = False
		CNC.vars["running"] = False


	#----------------------------------------------------------------------
	# Stop the current run
	#----------------------------------------------------------------------
	def stopRun(self, event=None):
		self.feedHold()
		self._stop = True
		# if we are in the process of submitting do not do anything
		if self._runLines != sys.maxsize:
			self.purgeController()

	#----------------------------------------------------------------------
	# This should be called everytime that milling of g-code file is finished
	# So we can purge the controller for the next job
	# See https://github.com/vlachoudis/bCNC/issues/1035
	#----------------------------------------------------------------------
	def jobDone(self):
		print("Job done. Purging the controller. (Running: %s)"%(self.running))
		self.purgeController()

	#----------------------------------------------------------------------
	# This is called everytime that motion controller changes the state
	# YOU SHOULD PASS ONLY REAL HW STATE TO THIS, NOT BCNC STATE
	# Right now the primary idea of this is to detect when job stopped running
	#----------------------------------------------------------------------
	def controllerStateChange(self, state):
		print("Controller state changed to: %s (Running: %s)"%(state, self.running))
		if self.cleanAfter == True and self.running == False and state in ("Idle"):
			self.cleanAfter = False
			self.jobDone()

	#----------------------------------------------------------------------
	# thread performing I/O on serial line
	#----------------------------------------------------------------------
	def serialIO(self):
		self.sio_wait   = False		# wait for commands to complete (status change to Idle)
		self.sio_status = False		# waiting for status <...> report
		cline  = []		# length of pipeline commands
		sline  = []			# pipeline commands
		tosend = None			# next string to send
		tr = tg = time.time()		# last time a ? or $G was send to grbl

		while self.thread:
			t = time.time()
			# refresh machine position?
			if t-tr > SERIAL_POLL:
				self.serial.write(b"?")
				self.sio_status = True
				tr = t

				#If Override change, attach feed
				if CNC.vars["_OvChanged"]:
					self.mcontrol.overrideSet()

			# Fetch new command to send if...
			if tosend is None and not self.sio_wait and not self._pause and self.queue.qsize()>0:
				try:
					tosend = self.queue.get_nowait()
					#print "+++",repr(tosend)
					if isinstance(tosend, tuple):
						#print "gcount tuple=",self._gcount
						# wait to empty the grbl buffer and status is Idle
						if tosend[0] == WAIT:
							# Don't count WAIT until we are idle!
							self.sio_wait = True
							#print "+++ WAIT ON"
							#print "gcount=",self._gcount, self._runLines
						elif tosend[0] == MSG:
							# Count executed commands as well
							self._gcount += 1
							if tosend[1] is not None:
								# show our message on machine status
								self._msg = tosend[1]
						elif tosend[0] == UPDATE:
							# Count executed commands as well
							self._gcount += 1
							self._update = tosend[1]
						else:
							# Count executed commands as well
							self._gcount += 1
						tosend = None

					elif not isinstance(tosend,str) and not isinstance(tosend,unicode):
						try:
							tosend = self.gcode.evaluate(tosend)
#							if isinstance(tosend, list):
#								cline.append(len(tosend[0]))
#								sline.append(tosend[0])
							if isinstance(tosend,str) or isinstance(tosend,unicode):
								tosend += "\n"
							else:
								# Count executed commands as well
								self._gcount += 1
								#print "gcount str=",self._gcount
							#print "+++ eval=",repr(tosend),type(tosend)
						except:
							for s in str(sys.exc_info()[1]).splitlines():
								self.log.put((Sender.MSG_ERROR,s))
							self._gcount += 1
							tosend = None
				except Empty:
					break

				if tosend is not None:
					# All modification in tosend should be
					# done before adding it to cline
					if isinstance(tosend, unicode):
						tosend = tosend.encode("ascii","replace")

					# Keep track of last feed
					pat = FEEDPAT.match(tosend)
					if pat is not None:
						self._lastFeed = pat.group(2)

					# Modify sent g-code to reflect overrided feed for controllers without override support
					if not self.mcontrol.has_override:
						if CNC.vars["_OvChanged"]:
							CNC.vars["_OvChanged"] = False
							self._newFeed = float(self._lastFeed)*CNC.vars["_OvFeed"]/100.0
							if pat is None and self._newFeed!=0 \
							   and not tosend.startswith("$"):
								tosend = "f%g%s" % (self._newFeed, tosend)

						# Apply override Feed
						if CNC.vars["_OvFeed"] != 100 and self._newFeed != 0:
							pat = FEEDPAT.match(tosend)
							if pat is not None:
								try:
									tosend = "%sf%g%s\n" % \
										(pat.group(1),
										 self._newFeed,
										 pat.group(3))
								except:
									pass

					# Bookkeeping of the buffers
					sline.append(tosend)
					cline.append(len(tosend))

			# Anything to receive?
			if self.serial.inWaiting() or tosend is None:
				try:
					line = str(self.serial.readline()).strip()
				except:
					self.log.put((Sender.MSG_RECEIVE, str(sys.exc_info()[1])))
					self.emptyQueue()
					self.close()
					return

				#print "<R<",repr(line)
				#print "*-* stack=",sline,"sum=",sum(cline),"wait=",wait,"pause=",self._pause
				if not line:
					pass
				elif self.mcontrol.parseLine(line, cline, sline):
					pass
				else:
					self.log.put((Sender.MSG_RECEIVE, line))

			# Received external message to stop
			if self._stop:
				self.emptyQueue()
				tosend = None
				self.log.put((Sender.MSG_CLEAR, ""))
				# WARNING if runLines==maxint then it means we are
				# still preparing/sending lines from from bCNC.run(),
				# so don't stop
				if self._runLines != sys.maxsize:
					self._stop = False

			#print "tosend='%s'"%(repr(tosend)),"stack=",sline,
			#	"sum=",sum(cline),"wait=",wait,"pause=",self._pause
			if tosend is not None and sum(cline) < RX_BUFFER_SIZE:
				self._sumcline = sum(cline)
#				if isinstance(tosend, list):
#					self.serial.write(str(tosend.pop(0)))
#					if not tosend: tosend = None

				#print ">S>",repr(tosend),"stack=",sline,"sum=",sum(cline)
				if self.mcontrol.gcode_case > 0: tosend = tosend.upper()
				if self.mcontrol.gcode_case < 0: tosend = tosend.lower()
				self.serial.write(bytes(tosend))
				#self.serial.write(tosend.encode("utf8"))
				#self.serial.flush()
				self.log.put((Sender.MSG_BUFFER,tosend))

				tosend = None
				if not self.running and t-tg > G_POLL:
					tosend = b"$G\n"
					sline.append(tosend)
					cline.append(len(tosend))
					tg = t