def build(self): Window.maximize() interface = FloatLayout() self.data = Data() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' self.data.comport = self.config.get('Makesmith Settings', 'COMport') self.data.gcodeFile = self.config.get('Makesmith Settings', 'openFile') self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialzie() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) return interface
def build(self): Window.maximize() interface = FloatLayout() self.data = Data() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' self.config.set('Advanced Settings', 'truncate', 0) self.config.set('Advanced Settings', 'digits', 4) self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX,offsetY] self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus = self.push_settings_to_machine) self.data.pushSettings = self.push_settings_to_machine self.push_settings_to_machine() return interface
class GroundControlApp(App): json = ''' [ { "type": "string", "title": "Serial Connection", "desc": "Select the COM port to connect to machine", "section": "Makesmith Settings", "key": "COMport" }, { "type": "string", "title": "X-Axis Pitch", "desc": "The number of mm moved per rotation", "section": "Makesmith Settings", "key": "xPitch" }, { "type": "string", "title": "Open File", "desc": "The path to the open file", "section": "Makesmith Settings", "key": "openFile" } ] ''' def build(self): Window.maximize() interface = FloatLayout() self.data = Data() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' self.data.comport = self.config.get('Makesmith Settings', 'COMport') self.data.gcodeFile = self.config.get('Makesmith Settings', 'openFile') self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialzie() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) return interface def build_config(self, config): """ Set the default values for the configs sections. """ config.setdefaults('Makesmith Settings', { 'COMport': 'COM5', 'xPitch': 20, 'openFile': " " }) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel('Makesmith Settings', self.config, data=self.json) def on_config_change(self, config, section, key, value): """ Respond to changes in the configuration. """ if section == "Makesmith Settings": if key == "COMport": self.data.comport = value elif key == 'xPitch': print "xPitch changed" def close_settings(self, settings): """ Close settings panel """ super(GroundControlApp, self).close_settings(settings) ''' Update Functions ''' def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty( ): #if there is new data to be read message = self.data.message_queue.get() if message[0:2] == "pz": self.setPosOnScreen(message) elif message[0:2] == "pt": self.setTargetOnScreen(message) elif message[0:8] == "Message:": self.data.uploadFlag = 0 content = NotificationPopup(cancel=self.dismiss_popup, text=message[9:]) self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size_hint=(0.25, 0.25)) self._popup.open() else: try: newText = self.frontpage.consoleText[-3000:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def dismiss_popup(self): ''' Close The Pop-up ''' self._popup.dismiss() self.data.uploadFlag = 1 def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('(') startpt = startpt + 1 endpt = message.find(')') numz = message[startpt:endpt] units = message[endpt + 1:endpt + 3] valz = numz.split(",") xval = float(valz[0]) yval = float(valz[1]) zval = float(valz[2]) except: print "bad data" return self.frontpage.setPosReadout(xval, yval, zval, units) self.frontpage.gcodecanvas.positionIndicator.setPos( xval, yval, self.data.units) def setTargetOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('(') startpt = startpt + 1 endpt = message.find(')') numz = message[startpt:endpt] units = message[endpt + 1:endpt + 3] valz = numz.split(",") xval = float(valz[0]) yval = float(valz[1]) zval = float(valz[2]) self.frontpage.gcodecanvas.targetIndicator.setPos( xval, yval, self.data.units) except: print "unable to convert to number"
class GroundControlApp(App): def get_application_config(self): return super(GroundControlApp, self).get_application_config( '~/%(appname)s.ini') json = ''' [ { "type": "string", "title": "Serial Connection", "desc": "Select the COM port to connect to machine", "section": "Maslow Settings", "key": "COMport" }, { "type": "string", "title": "Distance Between Motors", "desc": "The horizontal distance between the center of the motor shafts in MM.", "section": "Maslow Settings", "key": "motorSpacingX" }, { "type": "string", "title": "Work Area Width in MM", "desc": "The width of the machine working area (normally 8 feet).", "section": "Maslow Settings", "key": "bedWidth" }, { "type": "string", "title": "Work Area Height in MM", "desc": "The Height of the machine working area (normally 4 feet).", "section": "Maslow Settings", "key": "bedHeight" }, { "type": "string", "title": "Motor Offset Height in MM", "desc": "The vertical distance from the edge of the work area to the level of the motors.", "section": "Maslow Settings", "key": "motorOffsetY" }, { "type": "string", "title": "Distance Between Sled Mounting Points", "desc": "The horizontal distance between the points where the chains mount to the sled.", "section": "Maslow Settings", "key": "sledWidth" }, { "type": "string", "title": "Vertical Distance Sled Mounts to Cutter", "desc": "The vertical distance between where the chains mount on the sled to the cutting tool.", "section": "Maslow Settings", "key": "sledHeight" }, { "type": "string", "title": "Center Of Gravity", "desc": "How far below the cutting bit is the center of gravity. This can be found by resting the sled on a round object and observing where it balances.", "section": "Maslow Settings", "key": "sledCG" }, { "type": "bool", "title": "z-axis installed", "desc": "Does the machine have an automatic z-axis?", "section": "Maslow Settings", "key": "zAxis" }, { "type": "string", "title": "Z-Axis Pitch", "desc": "The number of mm moved per rotation of the z-axis", "section": "Maslow Settings", "key": "zDistPerRot" }, { "type": "string", "title": "Open File", "desc": "The path to the open file", "section": "Maslow Settings", "key": "openFile" }, { "type": "string", "title": "Macro 1", "desc": "User defined gcode bound to the Macro 1 button", "section": "Maslow Settings", "key": "macro1" }, { "type": "string", "title": "Macro 2", "desc": "User defined gcode bound to the Macro 2 button", "section": "Maslow Settings", "key": "macro2" } ] ''' advanced = ''' [ { "type": "string", "title": "Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the left or right motor", "section": "Advanced Settings", "key": "encoderSteps" }, { "type": "string", "title": "Gear Teeth", "desc": "The number of teeth on the gear of the left or right motor", "section": "Advanced Settings", "key": "gearTeeth" }, { "type": "string", "title": "Chain Pitch", "desc": "The distance between chain roller centers", "section": "Advanced Settings", "key": "chainPitch" }, { "type": "string", "title": "Z-Axis Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the z-axis", "section": "Advanced Settings", "key": "zEncoderSteps" }, { "type": "string", "title": "Home Position X Coordinate", "desc": "The X coordinate of the home position", "section": "Advanced Settings", "key": "homeX" }, { "type": "string", "title": "Home Position Y Coordinate", "desc": "The X coordinate of the home position", "section": "Advanced Settings", "key": "homeY" }, { "type": "bool", "title": "Truncate Floating Point Numbers", "desc": "Truncate floating point numbers at the specified number of decimal places", "section": "Advanced Settings", "key": "truncate" }, { "type": "string", "title": "Floating Point Precision", "desc": "If truncate floating point numbers is enabled, the number of digits after the decimal place to preserve", "section": "Advanced Settings", "key": "digits" }, { "type": "options", "title": "Kinematics Type", "desc": "Switch between trapezoidal and triangular kinematics", "options": ["Quadrilateral", "Triangular"], "section": "Advanced Settings", "key": "kinematicsType" }, { "type": "string", "title": "Rotation Radius for Triangular Kinematics", "desc": "The distance between where the chains attach and the center of the router bit in mm", "section": "Advanced Settings", "key": "rotationRadius" }, { "type": "bool", "title": "Enable Custom Positional PID Values", "desc": "Enable using custom values for the positional PID controller. Turning this off will return to the default values", "section": "Advanced Settings", "key": "enablePosPIDValues" }, { "type": "string", "title": "Kp Position", "desc": "The proportional constant for the position PID controller", "section": "Advanced Settings", "key": "KpPos" }, { "type": "string", "title": "Ki Position", "desc": "The integral constant for the position PID controller", "section": "Advanced Settings", "key": "KiPos" }, { "type": "string", "title": "Kd Position", "desc": "The derivative constant for the position PID controller", "section": "Advanced Settings", "key": "KdPos" }, { "type": "bool", "title": "Enable Custom Velocity PID Values", "desc": "Enable using custom values for the Velocity PID controller. Turning this off will return to the default values", "section": "Advanced Settings", "key": "enableVPIDValues" }, { "type": "string", "title": "Kp Velocity", "desc": "The proportional constant for the velocity PID controller", "section": "Advanced Settings", "key": "KpV" }, { "type": "string", "title": "Ki Velocity", "desc": "The integral constant for the velocity PID controller", "section": "Advanced Settings", "key": "KiV" }, { "type": "string", "title": "Kd Velocity", "desc": "The derivative constant for the velocity PID controller", "section": "Advanced Settings", "key": "KdV" } ] ''' gcsettings = ''' [ { "type": "string", "title": "Zoom In", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.", "section": "Ground Control Settings", "key": "zoomIn" }, { "type": "string", "title": "Zoom Out", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.", "section": "Ground Control Settings", "key": "zoomOut" }, { "type": "string", "title": "Valid File Extensions", "desc": "Valid file extensions for Ground Control to open. Comma separated list.", "section": "Ground Control Settings", "disabled": "True", "key": "validExtensions" } ] ''' def build(self): Window.maximize() interface = FloatLayout() self.data = Data() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' self.config.set('Advanced Settings', 'truncate', 0) self.config.set('Advanced Settings', 'digits', 4) self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX,offsetY] self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus = self.push_settings_to_machine) self.data.pushSettings = self.push_settings_to_machine self.push_settings_to_machine() return interface def build_config(self, config): """ Set the default values for the config sections. """ config.setdefaults('Maslow Settings', {'COMport' : '', 'zAxis' : 0, 'zDistPerRot' : 3.17, 'bedWidth' : 2438.4, 'bedHeight' : 1219.2, 'motorOffsetY' : 463, 'motorSpacingX' : 2978.4, 'sledWidth' : 310, 'sledHeight' : 139, 'sledCG' : 79, 'openFile' : " ", 'macro1' : "", 'macro2' : ""}) config.setdefaults('Advanced Settings', {'encoderSteps' : 8148.0, 'gearTeeth' : 10, 'chainPitch' : 6.35, 'zEncoderSteps' : 7560.0, 'homeX' : 0.0, 'homeY' : 0.0, 'truncate' : 0, 'digits' : 4, 'kinematicsType' : 'Quadrilateral', 'rotationRadius' : '100', 'enablePosPIDValues' : 0, 'KpPos' : 400, 'KiPos' : 5, 'KdPos' : 10, 'enableVPIDValues' : 0, 'KpV' : 20, 'KiV' : 1, 'KdV' : 0}) config.setdefaults('Ground Control Settings', {'zoomIn': "pageup", 'validExtensions':".nc, .ngc, .text, .gcode", 'zoomOut': "pagedown"}) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel('Maslow Settings', self.config, data=self.json) settings.add_json_panel('Advanced Settings', self.config, data=self.advanced) settings.add_json_panel('Ground Control Settings', self.config, data=self.gcsettings) def on_config_change(self, config, section, key, value): """ Respond to changes in the configuration. """ if section == "Maslow Settings": self.push_settings_to_machine() if key == "COMport": self.data.comport = value if (key == "bedHeight" or key == "bedWidth"): self.frontpage.gcodecanvas.drawWorkspace() if section == "Advanced Settings": self.push_settings_to_machine() if (key == "truncate") or (key == "digits"): self.frontpage.gcodecanvas.reloadGcode() def close_settings(self, settings): """ Close settings panel """ super(GroundControlApp, self).close_settings(settings) def push_settings_to_machine(self, *args): cmdString = ("B03" +" A" + str(self.data.config.get('Maslow Settings', 'bedWidth')) +" C" + str(self.data.config.get('Maslow Settings', 'bedHeight')) +" Q" + str(self.data.config.get('Maslow Settings', 'motorSpacingX')) +" E" + str(self.data.config.get('Maslow Settings', 'motorOffsetY')) +" F" + str(self.data.config.get('Maslow Settings', 'sledWidth')) +" R" + str(self.data.config.get('Maslow Settings', 'sledHeight')) +" H" + str(self.data.config.get('Maslow Settings', 'sledCG')) +" I" + str(self.data.config.get('Maslow Settings', 'zAxis')) +" J" + str(self.data.config.get('Advanced Settings', 'encoderSteps')) +" K" + str(self.data.config.get('Advanced Settings', 'gearTeeth')) +" M" + str(self.data.config.get('Advanced Settings', 'chainPitch')) +" N" + str(self.data.config.get('Maslow Settings' , 'zDistPerRot')) +" P" + str(self.data.config.get('Advanced Settings', 'zEncoderSteps')) + " " ) self.data.gcode_queue.put(cmdString) #Split the settings push into two so that it doesn't exceed the maximum line length if int(self.data.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: KpPos = float(self.data.config.get('Advanced Settings', 'KpPos')) KiPos = float(self.data.config.get('Advanced Settings', 'KiPos')) KdPos = float(self.data.config.get('Advanced Settings', 'KdPos')) else: KpPos = 400 KiPos = 5 KdPos = 10 if int(self.data.config.get('Advanced Settings', 'enableVPIDValues')) == 1: KpV = float(self.data.config.get('Advanced Settings', 'KpV')) KiV = float(self.data.config.get('Advanced Settings', 'KiV')) KdV = float(self.data.config.get('Advanced Settings', 'KdV')) else: KpV = 20 KiV = 1 KdV = 0 if self.data.config.get('Advanced Settings', 'kinematicsType') == 'Quadrilateral': kinematicsType = 1 print "quadrilateral recognized" else: kinematicsType = 2 print "triangular kinematics recognized" cmdString = ("B03" +" S" + str(KpPos) +" T" + str(KiPos) +" U" + str(KdPos) +" V" + str(KpV) +" W" + str(KiV) +" X" + str(KdV) +" Y" + str(kinematicsType) +" Z" + str(self.data.config.get('Advanced Settings', 'rotationRadius')) + " " ) self.data.gcode_queue.put(cmdString) ''' Update Functions ''' def writeToTextConsole(self, message): try: newText = self.frontpage.consoleText[-500:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty(): #if there is new data to be read message = self.data.message_queue.get() self.data.logger.writeToLog(message) if message[0] == "<": self.setPosOnScreen(message) elif message[0] == "[": if message[1:4] == "PE:": self.setErrorOnScreen(message) elif message[1:8] == "Measure": print "measure seen" print message measuredDist = float(message[9:len(message)-3]) self.data.measureRequest(measuredDist) elif message[0:8] == "Message:": self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup(continueOn = self.dismiss_popup_continue, text = message[9:]) self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size_hint=(0.35, 0.35)) self._popup.open() elif message[0:8] == "Firmware": self.writeToTextConsole("Ground Control " + str(self.data.version) + "\r\n" + message + "\r\n") elif message == "ok\r\n": pass #displaying all the 'ok' messages clutters up the display else: self.writeToTextConsole(message) def dismiss_popup_continue(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.quick_queue.put("~") #send cycle resume command to unpause the machine self.data.uploadFlag = self.previousUploadStatus #resume cutting if the machine was cutting before def dismiss_popup_hold(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = 0 #stop cutting def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('MPos:') + 5 endpt = message.find('WPos:') numz = message[startpt:endpt] units = "mm" #message[endpt+1:endpt+3] valz = numz.split(",") self.xval = float(valz[0]) self.yval = float(valz[1]) self.zval = float(valz[2]) if math.isnan(self.xval): self.writeToTextConsole("Unable to resolve x Kinematics.") self.xval = 0 if math.isnan(self.yval): self.writeToTextConsole("Unable to resolve y Kinematics.") self.yval = 0 if math.isnan(self.zval): self.writeToTextConsole("Unable to resolve z Kinematics.") self.zval = 0 self.frontpage.setPosReadout(self.xval,self.yval,self.zval) self.frontpage.gcodecanvas.positionIndicator.setPos(self.xval,self.yval,self.data.units) except: print "One Machine Position Report Command Misread" return def setErrorOnScreen(self, message): try: startpt = message.find(':')+1 endpt = message.find(',', startpt) leftErrorValueAsString = message[startpt:endpt] leftErrorValueAsFloat = float(leftErrorValueAsString) startpt = endpt + 1 endpt = message.find(',', startpt) rightErrorValueAsString = message[startpt:endpt] rightErrorValueAsFloat = float(rightErrorValueAsString) if self.data.units == "INCHES": rightErrorValueAsFloat = rightErrorValueAsFloat/25.4 leftErrorValueAsFloat = leftErrorValueAsFloat/25.4 avgError = (abs(leftErrorValueAsFloat) + abs(rightErrorValueAsFloat))/2 self.frontpage.gcodecanvas.positionIndicator.setError(0, self.data.units) self.data.logger.writeErrorValueToLog(avgError) self.frontpage.gcodecanvas.targetIndicator.setPos(self.xval - .5*rightErrorValueAsFloat + .5*leftErrorValueAsFloat, self.yval - .5*rightErrorValueAsFloat - .5*leftErrorValueAsFloat,self.data.units) except: print "Machine Position Report Command Misread Happened Once"
from flask_mobility.decorators import mobile_template from werkzeug import secure_filename from Background.UIProcessor import UIProcessor # do this after socketio is declared from Background.LogStreamer import LogStreamer # do this after socketio is declared from Background.WebMCPProcessor import WebMCPProcessor from Background.WebMCPProcessor import ConsoleProcessor from DataStructures.data import Data from Connection.nonVisibleWidgets import NonVisibleWidgets from WebPageProcessor.webPageProcessor import WebPageProcessor from os import listdir from os.path import isfile, join import sys app.data = Data() app.nonVisibleWidgets = NonVisibleWidgets() app.nonVisibleWidgets.setUpData(app.data) app.data.config.computeSettings(None, None, None, True) app.data.config.parseFirmwareVersions() version = sys.version_info # this is for python newer than 3.5 if version[:2] > (3, 5): app.data.pythonVersion35 = False # set data flag print("Using routines for Python > 3.5") else: app.data.pythonVersion35 = True # set data flag print("Using routines for Python == 3.5") app.data.units = app.data.config.getValue("Computed Settings", "units") app.data.tolerance = app.data.config.getValue("Computed Settings", "tolerance") app.data.distToMove = app.data.config.getValue("Computed Settings", "distToMove") app.data.distToMoveZ = app.data.config.getValue("Computed Settings",
class GroundControlApp(App): def get_application_config(self): return super(GroundControlApp, self).get_application_config('~/%(appname)s.ini') def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47, .47, .47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0, 0, 0] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1, 1, 1] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'DarkGreyBlue': self.data.iconPath = './Images/Icons/darkgreyblue/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0.06, 0.10, 0.2, 1) self.data.posIndicatorColor = [0.51, 0.93, 0.97] self.data.targetInicatorColor = [1, 0, 0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' # force create an ini no matter what. self.config.write() if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put( "Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update." ) self.config.set('Advanced Settings', 'encoderSteps', '8113.73') #up the maximum feedrate if self.config.get('Advanced Settings', 'maxFeedrate') == '700': self.data.message_queue.put( "Message: This update will increase the maximum feedrate of your machine. You can adjust this value under the Advanced settings." ) self.config.set('Advanced Settings', 'maxFeedrate', '800') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.baudRate = int(self.config.get('Maslow Settings', 'baudRate')) self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX, offsetY] self.data.config = self.config self.config.add_callback(self.configSettingChange) # Background image setup self.data.backgroundFile = self.config.get('Background Settings', 'backgroundFile') self.data.backgroundManualReg = json.loads( self.config.get('Background Settings', 'manualReg')) if self.data.backgroundFile != "": BackgroundMenu(self.data).processBackground() ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus=self.requestMachineSettings) self.data.pushSettings = self.requestMachineSettings return interface def build_config(self, config): """ Set the default values for the config sections. """ # Calculate computed settings on load config.add_callback(self.computeSettings) config.setdefaults( 'Computed Settings', maslowSettings.getDefaultValueSection('Computed Settings')) config.setdefaults( 'Maslow Settings', maslowSettings.getDefaultValueSection('Maslow Settings')) config.setdefaults( 'Advanced Settings', maslowSettings.getDefaultValueSection('Advanced Settings')) config.setdefaults( 'Ground Control Settings', maslowSettings.getDefaultValueSection('Ground Control Settings')) config.setdefaults( 'Background Settings', maslowSettings.getDefaultValueSection('Background Settings')) config.remove_callback(self.computeSettings) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel( 'Maslow Settings', self.config, data=maslowSettings.getJSONSettingSection('Maslow Settings')) settings.add_json_panel( 'Advanced Settings', self.config, data=maslowSettings.getJSONSettingSection('Advanced Settings')) settings.add_json_panel('Ground Control Settings', self.config, data=maslowSettings.getJSONSettingSection( "Ground Control Settings")) def computeSettings(self, section, key, value): # Update Computed settings if key == 'kinematicsType': if value == 'Quadrilateral': self.config.set('Computed Settings', 'kinematicsTypeComputed', "1") else: self.config.set('Computed Settings', 'kinematicsTypeComputed', "2") elif (key == 'gearTeeth' or key == 'chainPitch') and self.config.has_option( 'Advanced Settings', 'gearTeeth') and self.config.has_option( 'Advanced Settings', 'chainPitch'): distPerRot = float( self.config.get('Advanced Settings', 'gearTeeth')) * float( self.config.get('Advanced Settings', 'chainPitch')) self.config.set('Computed Settings', "distPerRot", str(distPerRot)) elif key == 'enablePosPIDValues': for key in ('KpPos', 'KiPos', 'KdPos', 'propWeight'): if int( self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue( 'Advanced Settings', key) self.config.set('Computed Settings', key + "Main", value) #updated computed values for z-axis for key in ('KpPosZ', 'KiPosZ', 'KdPosZ', 'propWeightZ'): if int( self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue( 'Advanced Settings', key) self.config.set('Computed Settings', key, value) elif key == 'enableVPIDValues': for key in ('KpV', 'KiV', 'KdV'): if int( self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue( 'Advanced Settings', key) self.config.set('Computed Settings', key + "Main", value) #updated computed values for z-axis for key in ('KpVZ', 'KiVZ', 'KdVZ'): if int( self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue( 'Advanced Settings', key) self.config.set('Computed Settings', key, value) elif key == 'chainOverSprocket': if value == 'Top': self.config.set('Computed Settings', 'chainOverSprocketComputed', 1) else: self.config.set('Computed Settings', 'chainOverSprocketComputed', 2) elif key == 'fPWM': if value == '31,000Hz': self.config.set('Computed Settings', 'fPWMComputed', 1) elif value == '4,100Hz': self.config.set('Computed Settings', 'fPWMComputed', 2) else: self.config.set('Computed Settings', 'fPWMComputed', 3) def configSettingChange(self, section, key, value): """ Respond to changes in the configuration. """ # Update GC things if section == "Maslow Settings": if key == "COMport": self.data.comport = value if key == "baudRate": self.data.baudRate = int(value) if (key == "bedHeight" or key == "bedWidth"): self.frontpage.gcodecanvas.drawWorkspace() if (key == "macro1_title") or (key == "macro2_title"): self.frontpage.update_macro_titles() if section == "Advanced Settings": if (key == "truncate") or (key == "digits"): self.frontpage.gcodecanvas.reloadGcode() if (key == "spindleAutomate"): if (value == "Servo"): value = 1 elif (value == "Relay_High"): value = 2 elif (value == "Relay_Low"): value = 3 else: value = 0 # Update Computed Settings self.computeSettings(section, key, value) # Write the settings change to the Disk self.data.config.write() # only run on live connection if self.data.connectionStatus != 1: return # Push settings that can be directly written to machine firmwareKey = maslowSettings.getFirmwareKey(section, key) if firmwareKey is not None: self.data.gcode_queue.put("$" + str(firmwareKey) + "=" + str(value)) def requestMachineSettings(self, *args): ''' Requests the machine to report all settings. This will implicitly cause a sync of the machine settings because if GroundControl sees a reported setting which does match its expected value, GC will push the correct setting to the machine. ''' if self.data.connectionStatus == 1: self.data.gcode_queue.put("$$") def receivedSetting(self, message): ''' This parses a settings report from the machine, usually received in response to a $$ request. If the value received does not match the expected value. ''' parameter, position = self.parseFloat(message, 0) value, position = self.parseFloat(message, position) if (parameter is not None and value is not None): maslowSettings.syncFirmwareKey(int(parameter), value, self.data) def parseFloat(self, text, position=0): ''' Takes a string and parses out the float found at position default to 0 returning a list of the matched float and the ending position of the float ''' # This regex comes from a python docs recommended regex = re.compile("[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?") match = regex.search(text[position:]) if match: return (float(match.group(0)), match.end(0)) else: return (None, position) ''' Update Functions ''' def writeToTextConsole(self, message): try: newText = self.frontpage.consoleText[-2000:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty( ): #if there is new data to be read message = self.data.message_queue.get() if message[0] == "<": self.setPosOnScreen(message) elif message[0] == "$": self.receivedSetting(message) elif message[0] == "[": if message[1:4] == "PE:": self.setErrorOnScreen(message) elif message[1:8] == "Measure": measuredDist = float(message[9:len(message) - 3]) try: self.data.measureRequest(measuredDist) except: print "No function has requested a measurement" elif message[0:13] == "Maslow Paused": self.data.uploadFlag = 0 self.writeToTextConsole(message) elif message[0:8] == "Message:": if self.data.calibrationInProcess and message[ 0: 15] == "Message: Unable": #this suppresses the annoying messages about invalid chain lengths during the calibration process break self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup( continueOn=self.dismiss_popup_continue, text=message[9:]) if sys.platform.startswith('darwin'): self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(.3, .3)) else: self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(None, None)) self._popup.open() if global_variables._keyboard: global_variables._keyboard.bind( on_key_down=self.keydown_popup) self._popup.bind(on_dismiss=self.ondismiss_popup) elif message[0:6] == "ALARM:": self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup( continueOn=self.dismiss_popup_continue, text=message[7:]) if sys.platform.startswith('darwin'): self._popup = Popup(title="Alarm Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(.3, .3)) else: self._popup = Popup(title="Alarm Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(None, None)) self._popup.open() if global_variables._keyboard: global_variables._keyboard.bind( on_key_down=self.keydown_popup) self._popup.bind(on_dismiss=self.ondismiss_popup) elif message[0:8] == "Firmware": self.data.logger.writeToLog("Ground Control Version " + str(self.data.version) + "\n") self.writeToTextConsole("Ground Control " + str(self.data.version) + "\r\n" + message + "\r\n") #Check that version numbers match if float(message[-7:]) < float(self.data.version): self.data.message_queue.put( "Message: Warning, your firmware is out of date and may not work correctly with this version of Ground Control\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) if float(message[-7:]) > float(self.data.version): self.data.message_queue.put( "Message: Warning, your version of Ground Control is out of date and may not work with this firmware version\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) elif message == "ok\r\n": pass #displaying all the 'ok' messages clutters up the display else: self.writeToTextConsole(message) def ondismiss_popup(self, event): if global_variables._keyboard: global_variables._keyboard.unbind(on_key_down=self.keydown_popup) def keydown_popup(self, keyboard, keycode, text, modifiers): if (keycode[1] == 'enter') or (keycode[1] == 'numpadenter') or (keycode[1] == 'escape'): self.dismiss_popup_continue() return True # always swallow keypresses since this is a modal dialog def dismiss_popup_continue(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.quick_queue.put( "~") #send cycle resume command to unpause the machine self.data.uploadFlag = self.previousUploadStatus #resume cutting if the machine was cutting before def dismiss_popup_hold(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = 0 #stop cutting def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('MPos:') + 5 endpt = message.find('WPos:') numz = message[startpt:endpt] units = "mm" #message[endpt+1:endpt+3] valz = numz.split(",") self.xval = float(valz[0]) self.yval = float(valz[1]) self.zval = float(valz[2]) if math.isnan(self.xval): self.writeToTextConsole("Unable to resolve x Kinematics.") self.xval = 0 if math.isnan(self.yval): self.writeToTextConsole("Unable to resolve y Kinematics.") self.yval = 0 if math.isnan(self.zval): self.writeToTextConsole("Unable to resolve z Kinematics.") self.zval = 0 except: print "One Machine Position Report Command Misread" return self.frontpage.setPosReadout(self.xval, self.yval, self.zval) self.frontpage.gcodecanvas.positionIndicator.setPos( self.xval, self.yval, self.data.units) def setErrorOnScreen(self, message): try: startpt = message.find(':') + 1 endpt = message.find(',', startpt) leftErrorValueAsString = message[startpt:endpt] leftErrorValueAsFloat = float(leftErrorValueAsString) startpt = endpt + 1 endpt = message.find(',', startpt) rightErrorValueAsString = message[startpt:endpt] rightErrorValueAsFloat = float(rightErrorValueAsString) if self.data.units == "INCHES": rightErrorValueAsFloat = rightErrorValueAsFloat / 25.4 leftErrorValueAsFloat = leftErrorValueAsFloat / 25.4 avgError = (abs(leftErrorValueAsFloat) + abs(rightErrorValueAsFloat)) / 2 self.frontpage.gcodecanvas.positionIndicator.setError( 0, self.data.units) self.data.logger.writeErrorValueToLog(avgError) self.frontpage.gcodecanvas.targetIndicator.setPos( self.xval - .5 * rightErrorValueAsFloat + .5 * leftErrorValueAsFloat, self.yval - .5 * rightErrorValueAsFloat - .5 * leftErrorValueAsFloat, self.data.units) except: print "Machine Position Report Command Misread Happened Once"
def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47, .47, .47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0, 0, 0] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1, 1, 1] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'DarkGreyBlue': self.data.iconPath = './Images/Icons/darkgreyblue/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0.06, 0.10, 0.2, 1) self.data.posIndicatorColor = [0.51, 0.93, 0.97] self.data.targetInicatorColor = [1, 0, 0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' # force create an ini no matter what. self.config.write() if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put( "Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update." ) self.config.set('Advanced Settings', 'encoderSteps', '8113.73') #up the maximum feedrate if self.config.get('Advanced Settings', 'maxFeedrate') == '700': self.data.message_queue.put( "Message: This update will increase the maximum feedrate of your machine. You can adjust this value under the Advanced settings." ) self.config.set('Advanced Settings', 'maxFeedrate', '800') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.baudRate = int(self.config.get('Maslow Settings', 'baudRate')) self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX, offsetY] self.data.config = self.config self.config.add_callback(self.configSettingChange) # Background image setup self.data.backgroundFile = self.config.get('Background Settings', 'backgroundFile') self.data.backgroundManualReg = json.loads( self.config.get('Background Settings', 'manualReg')) if self.data.backgroundFile != "": BackgroundMenu(self.data).processBackground() ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus=self.requestMachineSettings) self.data.pushSettings = self.requestMachineSettings return interface
def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47,.47,.47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0,0,0] self.data.targetInicatorColor = [1,0,0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1,1,1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1,1,1] self.data.targetInicatorColor = [1,0,0] elif self.config.get('Maslow Settings', 'colorScheme') == 'DarkGreyBlue': self.data.iconPath = './Images/Icons/darkgreyblue/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1,1,1] Window.clearcolor = (0.06, 0.10, 0.2, 1) self.data.posIndicatorColor = [0.51,0.93,0.97] self.data.targetInicatorColor = [1,0,0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' # force create an ini no matter what. self.config.write() if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put("Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update.") self.config.set('Advanced Settings', 'encoderSteps', '8113.73') #up the maximum feedrate if self.config.get('Advanced Settings', 'maxFeedrate') == '700': self.data.message_queue.put("Message: This update will increase the maximum feedrate of your machine. You can adjust this value under the Advanced settings.") self.config.set('Advanced Settings', 'maxFeedrate', '800') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX,offsetY] self.data.config = self.config self.config.add_callback(self.configSettingChange) # Background image setup self.data.backgroundFile = self.config.get('Background Settings', 'backgroundFile') self.data.backgroundManualReg = json.loads( self.config.get('Background Settings', 'manualReg')) if self.data.backgroundFile != "": BackgroundMenu(self.data).processBackground() ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus = self.requestMachineSettings) self.data.pushSettings = self.requestMachineSettings return interface
class GroundControlApp(App): def get_application_config(self): return super(GroundControlApp, self).get_application_config( '~/%(appname)s.ini') def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47,.47,.47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0,0,0] self.data.targetInicatorColor = [1,0,0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1,1,1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1,1,1] self.data.targetInicatorColor = [1,0,0] elif self.config.get('Maslow Settings', 'colorScheme') == 'DarkGreyBlue': self.data.iconPath = './Images/Icons/darkgreyblue/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1,1,1] Window.clearcolor = (0.06, 0.10, 0.2, 1) self.data.posIndicatorColor = [0.51,0.93,0.97] self.data.targetInicatorColor = [1,0,0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' # force create an ini no matter what. self.config.write() if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put("Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update.") self.config.set('Advanced Settings', 'encoderSteps', '8113.73') #up the maximum feedrate if self.config.get('Advanced Settings', 'maxFeedrate') == '700': self.data.message_queue.put("Message: This update will increase the maximum feedrate of your machine. You can adjust this value under the Advanced settings.") self.config.set('Advanced Settings', 'maxFeedrate', '800') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX,offsetY] self.data.config = self.config self.config.add_callback(self.configSettingChange) # Background image setup self.data.backgroundFile = self.config.get('Background Settings', 'backgroundFile') self.data.backgroundManualReg = json.loads( self.config.get('Background Settings', 'manualReg')) if self.data.backgroundFile != "": BackgroundMenu(self.data).processBackground() ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus = self.requestMachineSettings) self.data.pushSettings = self.requestMachineSettings return interface def build_config(self, config): """ Set the default values for the config sections. """ # Calculate computed settings on load config.add_callback(self.computeSettings) config.setdefaults('Computed Settings', maslowSettings.getDefaultValueSection('Computed Settings')) config.setdefaults('Maslow Settings', maslowSettings.getDefaultValueSection('Maslow Settings')) config.setdefaults('Advanced Settings', maslowSettings.getDefaultValueSection('Advanced Settings')) config.setdefaults('Ground Control Settings', maslowSettings.getDefaultValueSection('Ground Control Settings')) config.setdefaults('Background Settings', maslowSettings.getDefaultValueSection('Background Settings')) config.remove_callback(self.computeSettings) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel('Maslow Settings', self.config, data=maslowSettings.getJSONSettingSection('Maslow Settings')) settings.add_json_panel('Advanced Settings', self.config, data=maslowSettings.getJSONSettingSection('Advanced Settings')) settings.add_json_panel('Ground Control Settings', self.config, data=maslowSettings.getJSONSettingSection("Ground Control Settings")) def computeSettings(self, section, key, value): # Update Computed settings if key == 'kinematicsType': if value == 'Quadrilateral': self.config.set('Computed Settings', 'kinematicsTypeComputed', "1") else: self.config.set('Computed Settings', 'kinematicsTypeComputed', "2") elif (key == 'gearTeeth' or key == 'chainPitch') and self.config.has_option('Advanced Settings', 'gearTeeth') and self.config.has_option('Advanced Settings', 'chainPitch'): distPerRot = float(self.config.get('Advanced Settings', 'gearTeeth')) * float(self.config.get('Advanced Settings', 'chainPitch')) self.config.set('Computed Settings', "distPerRot", str(distPerRot)) elif key == 'enablePosPIDValues': for key in ('KpPos', 'KiPos', 'KdPos', 'propWeight'): if int(self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue('Advanced Settings', key) self.config.set('Computed Settings', key + "Main", value) #updated computed values for z-axis for key in ('KpPosZ', 'KiPosZ', 'KdPosZ', 'propWeightZ'): if int(self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue('Advanced Settings', key) self.config.set('Computed Settings', key, value) elif key == 'enableVPIDValues': for key in ('KpV', 'KiV', 'KdV'): if int(self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue('Advanced Settings', key) self.config.set('Computed Settings', key + "Main", value) #updated computed values for z-axis for key in ('KpVZ', 'KiVZ', 'KdVZ'): if int(self.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: value = float(self.config.get('Advanced Settings', key)) else: value = maslowSettings.getDefaultValue('Advanced Settings', key) self.config.set('Computed Settings', key, value) elif key == 'chainOverSprocket': if value == 'Top': self.config.set('Computed Settings', 'chainOverSprocketComputed', 1) else: self.config.set('Computed Settings', 'chainOverSprocketComputed', 2) elif key == 'fPWM': if value == '31,000Hz': self.config.set('Computed Settings', 'fPWMComputed', 1) elif value == '4,100Hz': self.config.set('Computed Settings', 'fPWMComputed', 2) else: self.config.set('Computed Settings', 'fPWMComputed', 3) def configSettingChange(self, section, key, value): """ Respond to changes in the configuration. """ # Update GC things if section == "Maslow Settings": if key == "COMport": self.data.comport = value if (key == "bedHeight" or key == "bedWidth"): self.frontpage.gcodecanvas.drawWorkspace() if (key == "macro1_title") or (key == "macro2_title"): self.frontpage.update_macro_titles() if section == "Advanced Settings": if (key == "truncate") or (key == "digits"): self.frontpage.gcodecanvas.reloadGcode() if (key == "spindleAutomate"): if (value == "Servo"): value = 1 elif (value == "Relay_High"): value = 2 elif (value == "Relay_Low"): value = 3 else: value = 0 # Update Computed Settings self.computeSettings(section, key, value) # Write the settings change to the Disk self.data.config.write() # only run on live connection if self.data.connectionStatus != 1: return # Push settings that can be directly written to machine firmwareKey = maslowSettings.getFirmwareKey(section, key) if firmwareKey is not None: self.data.gcode_queue.put("$" + str(firmwareKey) + "=" + str(value)) def requestMachineSettings(self, *args): ''' Requests the machine to report all settings. This will implicitly cause a sync of the machine settings because if GroundControl sees a reported setting which does match its expected value, GC will push the correct setting to the machine. ''' if self.data.connectionStatus == 1: self.data.gcode_queue.put("$$") def receivedSetting(self, message): ''' This parses a settings report from the machine, usually received in response to a $$ request. If the value received does not match the expected value. ''' parameter, position = self.parseFloat(message, 0) value, position = self.parseFloat(message, position) if (parameter is not None and value is not None): maslowSettings.syncFirmwareKey(int(parameter), value, self.data) def parseFloat(self, text, position=0): ''' Takes a string and parses out the float found at position default to 0 returning a list of the matched float and the ending position of the float ''' # This regex comes from a python docs recommended regex = re.compile("[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?") match = regex.search(text[position:]) if match: return (float(match.group(0)), match.end(0)) else: return (None, position) ''' Update Functions ''' def writeToTextConsole(self, message): try: newText = self.frontpage.consoleText[-2000:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty(): #if there is new data to be read message = self.data.message_queue.get() if message[0] == "<": self.setPosOnScreen(message) elif message[0] == "$": self.receivedSetting(message) elif message[0] == "[": if message[1:4] == "PE:": self.setErrorOnScreen(message) elif message[1:8] == "Measure": measuredDist = float(message[9:len(message)-3]) try: self.data.measureRequest(measuredDist) except: print "No function has requested a measurement" elif message[0:13] == "Maslow Paused": self.data.uploadFlag = 0 self.writeToTextConsole(message) elif message[0:8] == "Message:": if self.data.calibrationInProcess and message[0:15] == "Message: Unable": #this suppresses the annoying messages about invalid chain lengths during the calibration process break self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup(continueOn = self.dismiss_popup_continue, text = message[9:]) if sys.platform.startswith('darwin'): self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360,240), size_hint=(.3, .3)) else: self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360,240), size_hint=(None, None)) self._popup.open() if global_variables._keyboard: global_variables._keyboard.bind(on_key_down=self.keydown_popup) self._popup.bind(on_dismiss=self.ondismiss_popup) elif message[0:6] == "ALARM:": self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup(continueOn = self.dismiss_popup_continue, text = message[7:]) if sys.platform.startswith('darwin'): self._popup = Popup(title="Alarm Notification: ", content=content, auto_dismiss=False, size=(360,240), size_hint=(.3, .3)) else: self._popup = Popup(title="Alarm Notification: ", content=content, auto_dismiss=False, size=(360,240), size_hint=(None, None)) self._popup.open() if global_variables._keyboard: global_variables._keyboard.bind(on_key_down=self.keydown_popup) self._popup.bind(on_dismiss=self.ondismiss_popup) elif message[0:8] == "Firmware": self.data.logger.writeToLog("Ground Control Version " + str(self.data.version) + "\n") self.writeToTextConsole("Ground Control " + str(self.data.version) + "\r\n" + message + "\r\n") #Check that version numbers match if float(message[-7:]) < float(self.data.version): self.data.message_queue.put("Message: Warning, your firmware is out of date and may not work correctly with this version of Ground Control\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) if float(message[-7:]) > float(self.data.version): self.data.message_queue.put("Message: Warning, your version of Ground Control is out of date and may not work with this firmware version\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) elif message == "ok\r\n": pass #displaying all the 'ok' messages clutters up the display else: self.writeToTextConsole(message) def ondismiss_popup(self, event): if global_variables._keyboard: global_variables._keyboard.unbind(on_key_down=self.keydown_popup) def keydown_popup(self, keyboard, keycode, text, modifiers): if (keycode[1] == 'enter') or (keycode[1] =='numpadenter') or (keycode[1] == 'escape'): self.dismiss_popup_continue() return True # always swallow keypresses since this is a modal dialog def dismiss_popup_continue(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.quick_queue.put("~") #send cycle resume command to unpause the machine self.data.uploadFlag = self.previousUploadStatus #resume cutting if the machine was cutting before def dismiss_popup_hold(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = 0 #stop cutting def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('MPos:') + 5 endpt = message.find('WPos:') numz = message[startpt:endpt] units = "mm" #message[endpt+1:endpt+3] valz = numz.split(",") self.xval = float(valz[0]) self.yval = float(valz[1]) self.zval = float(valz[2]) if math.isnan(self.xval): self.writeToTextConsole("Unable to resolve x Kinematics.") self.xval = 0 if math.isnan(self.yval): self.writeToTextConsole("Unable to resolve y Kinematics.") self.yval = 0 if math.isnan(self.zval): self.writeToTextConsole("Unable to resolve z Kinematics.") self.zval = 0 except: print "One Machine Position Report Command Misread" return self.frontpage.setPosReadout(self.xval, self.yval, self.zval) self.frontpage.gcodecanvas.positionIndicator.setPos(self.xval,self.yval,self.data.units) def setErrorOnScreen(self, message): try: startpt = message.find(':')+1 endpt = message.find(',', startpt) leftErrorValueAsString = message[startpt:endpt] leftErrorValueAsFloat = float(leftErrorValueAsString) startpt = endpt + 1 endpt = message.find(',', startpt) rightErrorValueAsString = message[startpt:endpt] rightErrorValueAsFloat = float(rightErrorValueAsString) if self.data.units == "INCHES": rightErrorValueAsFloat = rightErrorValueAsFloat/25.4 leftErrorValueAsFloat = leftErrorValueAsFloat/25.4 avgError = (abs(leftErrorValueAsFloat) + abs(rightErrorValueAsFloat))/2 self.frontpage.gcodecanvas.positionIndicator.setError(0, self.data.units) self.data.logger.writeErrorValueToLog(avgError) self.frontpage.gcodecanvas.targetIndicator.setPos(self.xval - .5*rightErrorValueAsFloat + .5*leftErrorValueAsFloat, self.yval - .5*rightErrorValueAsFloat - .5*leftErrorValueAsFloat,self.data.units) except: print "Machine Position Report Command Misread Happened Once"
class GroundControlApp(App): def get_application_config(self): return super(GroundControlApp, self).get_application_config('~/%(appname)s.ini') json = ''' [ { "type": "string", "title": "Serial Connection", "desc": "Select the COM port to connect to machine", "section": "Maslow Settings", "key": "COMport" }, { "type": "string", "title": "Distance Between Motors", "desc": "The horizontal distance between the center of the motor shafts in MM.", "section": "Maslow Settings", "key": "motorSpacingX" }, { "type": "string", "title": "Work Area Width in MM", "desc": "The width of the machine working area (normally 8 feet).", "section": "Maslow Settings", "key": "bedWidth" }, { "type": "string", "title": "Work Area Height in MM", "desc": "The Height of the machine working area (normally 4 feet).", "section": "Maslow Settings", "key": "bedHeight" }, { "type": "string", "title": "Motor Offset Height in MM", "desc": "The vertical distance from the edge of the work area to the level of the motors.", "section": "Maslow Settings", "key": "motorOffsetY" }, { "type": "string", "title": "Distance Between Sled Mounting Points", "desc": "The horizontal distance between the points where the chains mount to the sled.", "section": "Maslow Settings", "key": "sledWidth" }, { "type": "string", "title": "Vertical Distance Sled Mounts to Cutter", "desc": "The vertical distance between where the chains mount on the sled to the cutting tool.", "section": "Maslow Settings", "key": "sledHeight" }, { "type": "string", "title": "Center Of Gravity", "desc": "How far below the cutting bit is the center of gravity. This can be found by resting the sled on a round object and observing where it balances.", "section": "Maslow Settings", "key": "sledCG" }, { "type": "bool", "title": "z-axis installed", "desc": "Does the machine have an automatic z-axis?", "section": "Maslow Settings", "key": "zAxis" }, { "type": "string", "title": "Z-Axis Pitch", "desc": "The number of mm moved per rotation of the z-axis", "section": "Maslow Settings", "key": "zDistPerRot" }, { "type": "string", "title": "Open File", "desc": "The path to the open file", "section": "Maslow Settings", "key": "openFile" } ] ''' advanced = ''' [ { "type": "string", "title": "Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the left or right motor", "section": "Advanced Settings", "key": "encoderSteps" }, { "type": "string", "title": "Gear Teeth", "desc": "The number of teeth on the gear of the left or right motor", "section": "Advanced Settings", "key": "gearTeeth" }, { "type": "string", "title": "Chain Pitch", "desc": "The distance between chain roller centers", "section": "Advanced Settings", "key": "chainPitch" }, { "type": "string", "title": "Z-Axis Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the z-axis", "section": "Advanced Settings", "key": "zEncoderSteps" } ] ''' gcsettings = ''' [ { "type": "string", "title": "Zoom In", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.", "section": "Ground Control Settings", "key": "zoomIn" }, { "type": "string", "title": "Zoom Out", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.", "section": "Ground Control Settings", "key": "zoomOut" }, { "type": "string", "title": "Valid File Extensions", "desc": "Valid file extensions for Ground Control to open. Comma separated list.", "section": "Ground Control Settings", "key": "validExtensions" } ] ''' def build(self): Window.maximize() interface = FloatLayout() self.data = Data() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus=self.push_settings_to_machine) self.data.pushSettings = self.push_settings_to_machine return interface def build_config(self, config): """ Set the default values for the config sections. """ config.setdefaults( 'Maslow Settings', { 'COMport': '', 'zAxis': 0, 'zDistPerRot': 3.17, 'bedWidth': 2438.4, 'bedHeight': 1219.2, 'motorOffsetY': 463, 'motorSpacingX': 2978.4, 'sledWidth': 310, 'sledHeight': 139, 'sledCG': 79, 'openFile': " " }) config.setdefaults( 'Advanced Settings', { 'encoderSteps': 8148.0, 'gearTeeth': 10, 'chainPitch': 6.35, 'zEncoderSteps': 7560.0 }) config.setdefaults( 'Ground Control Settings', { 'zoomIn': "pageup", 'validExtensions': ".nc, .ngc, .text, .gcode", 'zoomOut': "pagedown" }) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel('Maslow Settings', self.config, data=self.json) settings.add_json_panel('Advanced Settings', self.config, data=self.advanced) settings.add_json_panel('Ground Control Settings', self.config, data=self.gcsettings) def on_config_change(self, config, section, key, value): """ Respond to changes in the configuration. """ if section == "Maslow Settings": if key == "COMport": self.data.comport = value self.push_settings_to_machine() if (key == "bedHeight" or key == "bedWidth"): self.frontpage.gcodecanvas.drawWorkspace() def close_settings(self, settings): """ Close settings panel """ super(GroundControlApp, self).close_settings(settings) def push_settings_to_machine(self, *args): cmdString = ( "B03" + " A" + str(self.data.config.get('Maslow Settings', 'bedWidth')) + " C" + str(self.data.config.get('Maslow Settings', 'bedHeight')) + " Q" + str(self.data.config.get('Maslow Settings', 'motorSpacingX')) + " E" + str(self.data.config.get('Maslow Settings', 'motorOffsetY')) + " F" + str(self.data.config.get('Maslow Settings', 'sledWidth')) + " R" + str(self.data.config.get('Maslow Settings', 'sledHeight')) + " H" + str(self.data.config.get('Maslow Settings', 'sledCG')) + " I" + str(self.data.config.get('Maslow Settings', 'zAxis')) + " J" + str(self.data.config.get('Advanced Settings', 'encoderSteps')) + " K" + str(self.data.config.get('Advanced Settings', 'gearTeeth')) + " M" + str(self.data.config.get('Advanced Settings', 'chainPitch')) + " N" + str(self.data.config.get('Maslow Settings', 'zDistPerRot')) + " P" + str(self.data.config.get('Advanced Settings', 'zEncoderSteps')) + " ") self.data.gcode_queue.put(cmdString) ''' Update Functions ''' def writeToTextConsole(self, message): try: newText = self.frontpage.consoleText[-3000:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty( ): #if there is new data to be read message = self.data.message_queue.get() self.data.logger.writeToLog(message) if message[0] == "<": self.setPosOnScreen(message) elif message[0] == "[": if message[1:10] == "PosError:": self.setErrorOnScreen(message) elif message[1:8] == "Measure": print "measure seen" print message measuredDist = float(message[9:len(message) - 3]) self.data.measureRequest(measuredDist) elif message[0:8] == "Message:": self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup( continueOn=self.dismiss_popup_continue, hold=self.dismiss_popup_hold, text=message[9:]) self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size_hint=(0.35, 0.35)) self._popup.open() else: self.writeToTextConsole(message) def dismiss_popup_continue(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = self.previousUploadStatus #resume cutting if the machine was cutting before def dismiss_popup_hold(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = 0 #stop cutting def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('MPos:') + 5 endpt = message.find('WPos:') numz = message[startpt:endpt] units = "mm" #message[endpt+1:endpt+3] valz = numz.split(",") xval = float(valz[0]) yval = float(valz[1]) zval = float(valz[2]) if math.isnan(xval): self.writeToTextConsole("Unable to resolve x Kinematics.") xval = 0 if math.isnan(yval): self.writeToTextConsole("Unable to resolve y Kinematics.") yval = 0 if math.isnan(zval): self.writeToTextConsole("Unable to resolve z Kinematics.") zval = 0 except: print "bad data" return self.frontpage.setPosReadout(xval, yval, zval) self.frontpage.gcodecanvas.positionIndicator.setPos( xval, yval, self.data.units) def setErrorOnScreen(self, message): try: startpt = message.find(':') + 1 endpt = message.find(',', startpt) errorValueAsString = message[startpt:endpt] errorValueAsFloat = float(errorValueAsString) self.frontpage.gcodecanvas.positionIndicator.setError( errorValueAsFloat) self.data.logger.writeErrorValueToLog(errorValueAsFloat) except: print "unable to read error value"
class GroundControlApp(App): def get_application_config(self): return super(GroundControlApp, self).get_application_config('~/%(appname)s.ini') json = ''' [ { "type": "string", "title": "Serial Connection", "desc": "Select the COM port to connect to machine", "section": "Maslow Settings", "key": "COMport" }, { "type": "string", "title": "Distance Between Motors", "desc": "The horizontal distance between the center of the motor shafts in MM.\\ndefault setting: %s", "section": "Maslow Settings", "key": "motorSpacingX" }, { "type": "string", "title": "Work Area Width in MM", "desc": "The width of the machine working area (normally 8 feet).\\ndefault setting: %s", "section": "Maslow Settings", "key": "bedWidth" }, { "type": "string", "title": "Work Area Height in MM", "desc": "The Height of the machine working area (normally 4 feet).\\ndefault setting: %s", "section": "Maslow Settings", "key": "bedHeight" }, { "type": "string", "title": "Motor Offset Height in MM", "desc": "The vertical distance from the edge of the work area to the level of the motors.\\ndefault setting: %s", "section": "Maslow Settings", "key": "motorOffsetY" }, { "type": "string", "title": "Distance Between Sled Mounting Points", "desc": "The horizontal distance between the points where the chains mount to the sled.\\ndefault setting: %s", "section": "Maslow Settings", "key": "sledWidth" }, { "type": "string", "title": "Vertical Distance Sled Mounts to Cutter", "desc": "The vertical distance between where the chains mount on the sled to the cutting tool.\\ndefault setting: %s", "section": "Maslow Settings", "key": "sledHeight" }, { "type": "string", "title": "Center Of Gravity", "desc": "How far below the cutting bit is the center of gravity. This can be found by resting the sled on a round object and observing where it balances.\\ndefault setting: %s", "section": "Maslow Settings", "key": "sledCG" }, { "type": "bool", "title": "z-axis installed", "desc": "Does the machine have an automatic z-axis?\\ndefault setting: %s", "section": "Maslow Settings", "key": "zAxis" }, { "type": "string", "title": "Z-Axis Pitch", "desc": "The number of mm moved per rotation of the z-axis\\ndefault setting: %s", "section": "Maslow Settings", "key": "zDistPerRot" }, { "type": "options", "title": "Color Scheme", "desc": "Switch between the light and dark color schemes. Restarting GC is needed for this change to take effect\\ndefault setting: %s", "options": ["Light", "Dark"], "section": "Maslow Settings", "key": "colorScheme" }, { "type": "string", "title": "Open File", "desc": "The path to the open file\\ndefault setting: your home directory", "section": "Maslow Settings", "key": "openFile" }, { "type": "string", "title": "Macro 1", "desc": "User defined gcode bound to the Macro 1 button", "section": "Maslow Settings", "key": "macro1" }, { "type": "string", "title": "Macro 1 Title", "desc": "User defined title for the Macro 1 button", "section": "Maslow Settings", "key": "macro1_title" }, { "type": "string", "title": "Macro 2", "desc": "User defined gcode bound to the Macro 2 button", "section": "Maslow Settings", "key": "macro2" }, { "type": "string", "title": "Macro 2 Title", "desc": "User defined title for the Macro 2 button", "section": "Maslow Settings", "key": "macro2_title" } ] ''' % ( # global_variables._COMport, global_variables._motorSpacingX, global_variables._bedWidth, global_variables._bedHeight, global_variables._motorOffsetY, global_variables._sledWidth, global_variables._sledHeight, global_variables._sledCG, global_variables._zAxis, global_variables._zDistPerRot, global_variables._colorScheme) advanced = ''' [ { "type": "string", "title": "Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the left or right motor\\ndefault setting: %s", "section": "Advanced Settings", "key": "encoderSteps" }, { "type": "string", "title": "Gear Teeth", "desc": "The number of teeth on the gear of the left or right motor\\ndefault setting: %s", "section": "Advanced Settings", "key": "gearTeeth" }, { "type": "string", "title": "Chain Pitch", "desc": "The distance between chain roller centers\\ndefault setting: %s", "section": "Advanced Settings", "key": "chainPitch" }, { "type": "string", "title": "Z-Axis Encoder Steps per Revolution", "desc": "The number of encoder steps per revolution of the z-axis\\ndefault setting: %s", "section": "Advanced Settings", "key": "zEncoderSteps" }, { "type": "bool", "title": "Spindle Automation", "desc": "Should the spindle start and stop automatically based on gcode? Leave off for default stepper control.\\ndefault setting: %s", "section": "Advanced Settings", "key": "zAxisAuto" }, { "type": "string", "title": "Home Position X Coordinate", "desc": "The X coordinate of the home position\\ndefault setting: %s", "section": "Advanced Settings", "key": "homeX" }, { "type": "string", "title": "Home Position Y Coordinate", "desc": "The X coordinate of the home position\\ndefault setting: %s", "section": "Advanced Settings", "key": "homeY" }, { "type": "bool", "title": "Truncate Floating Point Numbers", "desc": "Truncate floating point numbers at the specified number of decimal places\\ndefault setting: %s", "section": "Advanced Settings", "key": "truncate" }, { "type": "string", "title": "Floating Point Precision", "desc": "If truncate floating point numbers is enabled, the number of digits after the decimal place to preserve\\ndefault setting: %s", "section": "Advanced Settings", "key": "digits" }, { "type": "options", "title": "Kinematics Type", "desc": "Switch between trapezoidal and triangular kinematics\\ndefault setting: %s", "options": ["Quadrilateral", "Triangular"], "section": "Advanced Settings", "key": "kinematicsType" }, { "type": "string", "title": "Rotation Radius for Triangular Kinematics", "desc": "The distance between where the chains attach and the center of the router bit in mm\\ndefault setting: %s", "section": "Advanced Settings", "key": "rotationRadius" }, { "type": "bool", "title": "Enable Custom Positional PID Values", "desc": "Enable using custom values for the positional PID controller. Turning this off will return to the default values\\ndefault setting: %s", "section": "Advanced Settings", "key": "enablePosPIDValues" }, { "type": "string", "title": "Kp Position", "desc": "The proportional constant for the position PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KpPos" }, { "type": "string", "title": "Ki Position", "desc": "The integral constant for the position PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KiPos" }, { "type": "string", "title": "Kd Position", "desc": "The derivative constant for the position PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KdPos" }, { "type": "string", "title": "Proportional Weighting", "desc": "The ratio of Proportional on Error (1) to Proportional on Measure (0)\\ndefault setting: %s", "section": "Advanced Settings", "key": "propWeight" }, { "type": "bool", "title": "Enable Custom Velocity PID Values", "desc": "Enable using custom values for the Velocity PID controller. Turning this off will return to the default values\\ndefault setting: %s", "section": "Advanced Settings", "key": "enableVPIDValues" }, { "type": "string", "title": "Kp Velocity", "desc": "The proportional constant for the velocity PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KpV" }, { "type": "string", "title": "Ki Velocity", "desc": "The integral constant for the velocity PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KiV" }, { "type": "string", "title": "Kd Velocity", "desc": "The derivative constant for the velocity PID controller\\ndefault setting: %s", "section": "Advanced Settings", "key": "KdV" } ] ''' % (global_variables._encoderSteps, global_variables._gearTeeth, global_variables._chainPitch, global_variables._zEncoderSteps, global_variables._zAxisAuto, global_variables._homeX, global_variables._homeY, global_variables._truncate, global_variables._digits, global_variables._kinematicsType, global_variables._rotationRadius, global_variables._enablePosPIDValues, global_variables._KpPos, global_variables._KiPos, global_variables._KdPos, global_variables._propWeight, global_variables._enableVPIDValues, global_variables._KpV, global_variables._KiV, global_variables._KdV) gcsettings = ''' [ { "type": "bool", "title": "Center Canvas on Window Resize", "desc": "When resizing the window, automatically reset the Gcode canvas to be centered and zoomed out. Program must be restarted to take effect.\\ndefault setting: %s", "section": "Ground Control Settings", "key": "centerCanvasOnResize" }, { "type": "string", "title": "Zoom In", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.\\ndefault setting: %s", "section": "Ground Control Settings", "key": "zoomIn" }, { "type": "string", "title": "Zoom Out", "desc": "Pressing this key will zoom in. Note combinations of keys like \'shift\' + \'=\' may not work as expected. Program must be restarted to take effect.\\ndefault setting: %s", "section": "Ground Control Settings", "key": "zoomOut" }, { "type": "string", "title": "Valid File Extensions", "desc": "Valid file extensions for Ground Control to open. Comma separated list.\\ndefault setting: %s", "section": "Ground Control Settings", "key": "validExtensions" } ] ''' % (global_variables._centerCanvasOnResize, global_variables._zoomIn, global_variables._zoomOut, global_variables._validExtensions) def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47, .47, .47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0, 0, 0] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1, 1, 1] self.data.targetInicatorColor = [1, 0, 0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put( "Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update." ) self.config.set('Advanced Settings', 'encoderSteps', '8113.73') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX, offsetY] self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus=self.push_settings_to_machine) self.data.pushSettings = self.push_settings_to_machine return interface def build_config(self, config): """ Set the default values for the config sections. """ config.setdefaults( 'Maslow Settings', { 'COMport': global_variables._COMport, 'zAxis': global_variables._zAxis, 'zDistPerRot': global_variables._zDistPerRot, 'bedWidth': global_variables._bedWidth, 'bedHeight': global_variables._bedHeight, 'motorOffsetY': global_variables._motorOffsetY, 'motorSpacingX': global_variables._motorSpacingX, 'sledWidth': global_variables._sledWidth, 'sledHeight': global_variables._sledHeight, 'sledCG': global_variables._sledCG, 'colorScheme': global_variables._colorScheme, 'openFile': global_variables._openFile, 'macro1': global_variables._macro1, 'macro1_title': global_variables._macro1_title, 'macro2': global_variables._macro2, 'macro2_title': global_variables._macro2_title }) config.setdefaults( 'Advanced Settings', { 'encoderSteps': global_variables._encoderSteps, 'gearTeeth': global_variables._gearTeeth, 'chainPitch': global_variables._chainPitch, 'zEncoderSteps': global_variables._zEncoderSteps, 'zAxisAuto': global_variables._zAxisAuto, 'homeX': global_variables._homeX, 'homeY': global_variables._homeY, 'truncate': global_variables._truncate, 'digits': global_variables._digits, 'kinematicsType': global_variables._kinematicsType, 'rotationRadius': global_variables._rotationRadius, 'enablePosPIDValues': global_variables._enablePosPIDValues, 'KpPos': global_variables._KpPos, 'KiPos': global_variables._KiPos, 'KdPos': global_variables._KdPos, 'propWeight': global_variables._propWeight, 'enableVPIDValues': global_variables._enableVPIDValues, 'KpV': global_variables._KpV, 'KiV': global_variables._KiV, 'KdV': global_variables._KdV }) config.setdefaults( 'Ground Control Settings', { 'centerCanvasOnResize': global_variables._centerCanvasOnResize, 'zoomIn': global_variables._zoomIn, 'zoomOut': global_variables._zoomOut, 'validExtensions': global_variables._validExtensions, }) def build_settings(self, settings): """ Add custom section to the default configuration object. """ settings.add_json_panel('Maslow Settings', self.config, data=self.json) settings.add_json_panel('Advanced Settings', self.config, data=self.advanced) settings.add_json_panel('Ground Control Settings', self.config, data=self.gcsettings) def on_config_change(self, config, section, key, value): """ Respond to changes in the configuration. """ if section == "Maslow Settings": self.push_settings_to_machine() if key == "COMport": self.data.comport = value if (key == "bedHeight" or key == "bedWidth"): self.frontpage.gcodecanvas.drawWorkspace() if (key == "macro1_title") or (key == "macro2_title"): self.frontpage.update_macro_titles() if section == "Advanced Settings": self.push_settings_to_machine() if (key == "truncate") or (key == "digits"): self.frontpage.gcodecanvas.reloadGcode() def push_settings_to_machine(self, *args): #Push motor configuration settings to machine if self.data.connectionStatus != 1: return # only run on connection true if int(self.data.config.get('Advanced Settings', 'enablePosPIDValues')) == 1: KpPos = float(self.data.config.get('Advanced Settings', 'KpPos')) KiPos = float(self.data.config.get('Advanced Settings', 'KiPos')) KdPos = float(self.data.config.get('Advanced Settings', 'KdPos')) propWeight = float( self.data.config.get('Advanced Settings', 'propWeight')) else: KpPos = 1300 KiPos = 0 KdPos = 34 propWeight = 1 if int(self.data.config.get('Advanced Settings', 'enableVPIDValues')) == 1: KpV = float(self.data.config.get('Advanced Settings', 'KpV')) KiV = float(self.data.config.get('Advanced Settings', 'KiV')) KdV = float(self.data.config.get('Advanced Settings', 'KdV')) else: KpV = 7 KiV = 0 KdV = .28 # Be pretty dumb about this, just push settings without checking to # see what machine has self.data.gcode_queue.put( "$16=" + str(self.data.config.get('Maslow Settings', 'zAxis'))) self.data.gcode_queue.put( "$12=" + str(self.data.config.get('Advanced Settings', 'encoderSteps'))) distPerRot = float( self.data.config.get('Advanced Settings', 'gearTeeth')) * float( self.data.config.get('Advanced Settings', 'chainPitch')) self.data.gcode_queue.put("$13=" + str(distPerRot)) self.data.gcode_queue.put( "$19=" + str(self.data.config.get('Maslow Settings', 'zDistPerRot'))) self.data.gcode_queue.put( "$20=" + str(self.data.config.get('Advanced Settings', 'zEncoderSteps'))) #main axes self.data.gcode_queue.put("$21=" + str(KpPos)) self.data.gcode_queue.put("$22=" + str(KiPos)) self.data.gcode_queue.put("$23=" + str(KdPos)) self.data.gcode_queue.put("$24=" + str(propWeight)) self.data.gcode_queue.put("$25=" + str(KpV)) self.data.gcode_queue.put("$26=" + str(KiV)) self.data.gcode_queue.put("$27=" + str(KdV)) self.data.gcode_queue.put("$28=1.0") #z axis self.data.gcode_queue.put("$29=" + str(KpPos)) self.data.gcode_queue.put("$30=" + str(KiPos)) self.data.gcode_queue.put("$31=" + str(KdPos)) self.data.gcode_queue.put("$32=" + str(propWeight)) self.data.gcode_queue.put("$33=" + str(KpV)) self.data.gcode_queue.put("$34=" + str(KiV)) self.data.gcode_queue.put("$35=" + str(KdV)) self.data.gcode_queue.put("$36=1.0") self.data.gcode_queue.put( "$17=" + str(self.data.config.get('Advanced Settings', 'zAxisAuto'))) #Push kinematics settings to machine if self.data.config.get('Advanced Settings', 'kinematicsType') == 'Quadrilateral': kinematicsType = 1 else: kinematicsType = 2 self.data.gcode_queue.put( "$0=" + str(self.data.config.get('Maslow Settings', 'bedWidth'))) self.data.gcode_queue.put( "$1=" + str(self.data.config.get('Maslow Settings', 'bedHeight'))) self.data.gcode_queue.put( "$2=" + str(self.data.config.get('Maslow Settings', 'motorSpacingX'))) self.data.gcode_queue.put( "$3=" + str(self.data.config.get('Maslow Settings', 'motorOffsetY'))) self.data.gcode_queue.put( "$4=" + str(self.data.config.get('Maslow Settings', 'sledWidth'))) self.data.gcode_queue.put( "$5=" + str(self.data.config.get('Maslow Settings', 'sledHeight'))) self.data.gcode_queue.put( "$6=" + str(self.data.config.get('Maslow Settings', 'sledCG'))) self.data.gcode_queue.put("$7=" + str(kinematicsType)) self.data.gcode_queue.put( "$8=" + str(self.data.config.get('Advanced Settings', 'rotationRadius'))) # Force kinematics recalibration self.data.gcode_queue.put("$K") ''' Update Functions ''' def writeToTextConsole(self, message): try: newText = self.frontpage.consoleText[-500:] + message self.frontpage.consoleText = newText self.frontpage.textconsole.gotToBottom() except: self.frontpage.consoleText = "text not displayed correctly" def runPeriodically(self, *args): ''' this block should be handled within the appropriate widget ''' while not self.data.message_queue.empty( ): #if there is new data to be read message = self.data.message_queue.get() if message[0] == "<": self.setPosOnScreen(message) elif message[0] == "[": if message[1:4] == "PE:": self.setErrorOnScreen(message) elif message[1:8] == "Measure": print "measure seen" print message measuredDist = float(message[9:len(message) - 3]) self.data.measureRequest(measuredDist) elif message[0:13] == "Maslow Paused": self.data.uploadFlag = 0 self.writeToTextConsole(message) elif message[0:8] == "Message:": self.previousUploadStatus = self.data.uploadFlag self.data.uploadFlag = 0 try: self._popup.dismiss() #close any open popup except: pass #there wasn't a popup to close content = NotificationPopup( continueOn=self.dismiss_popup_continue, text=message[9:]) if sys.platform.startswith('darwin'): self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(.3, .3)) else: self._popup = Popup(title="Notification: ", content=content, auto_dismiss=False, size=(360, 240), size_hint=(None, None)) self._popup.open() if global_variables._keyboard: global_variables._keyboard.bind( on_key_down=self.keydown_popup) self._popup.bind(on_dismiss=self.ondismiss_popup) elif message[0:8] == "Firmware": self.data.logger.writeToLog("Ground Control Version " + str(self.data.version) + "\n") self.writeToTextConsole("Ground Control " + str(self.data.version) + "\r\n" + message + "\r\n") #Check that version numbers match if float(message[-7:]) < float(self.data.version): self.data.message_queue.put( "Message: Warning, your firmware is out of date and may not work correctly with this version of Ground Control\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) if float(message[-7:]) > float(self.data.version): self.data.message_queue.put( "Message: Warning, your version of Ground Control is out of date and may not work with this firmware version\n\n" + "Ground Control Version " + str(self.data.version) + "\r\n" + message) elif message == "ok\r\n": pass #displaying all the 'ok' messages clutters up the display else: self.writeToTextConsole(message) def ondismiss_popup(self, event): if global_variables._keyboard: global_variables._keyboard.unbind(on_key_down=self.keydown_popup) def keydown_popup(self, keyboard, keycode, text, modifiers): if (keycode[1] == 'enter') or (keycode[1] == 'numpadenter') or (keycode[1] == 'escape'): self.dismiss_popup_continue() return True # always swallow keypresses since this is a modal dialog def dismiss_popup_continue(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.quick_queue.put( "~") #send cycle resume command to unpause the machine self.data.uploadFlag = self.previousUploadStatus #resume cutting if the machine was cutting before def dismiss_popup_hold(self): ''' Close The Pop-up and continue cut ''' self._popup.dismiss() self.data.uploadFlag = 0 #stop cutting def setPosOnScreen(self, message): ''' This should be moved into the appropriate widget ''' try: startpt = message.find('MPos:') + 5 endpt = message.find('WPos:') numz = message[startpt:endpt] units = "mm" #message[endpt+1:endpt+3] valz = numz.split(",") self.xval = float(valz[0]) self.yval = float(valz[1]) self.zval = float(valz[2]) if math.isnan(self.xval): self.writeToTextConsole("Unable to resolve x Kinematics.") self.xval = 0 if math.isnan(self.yval): self.writeToTextConsole("Unable to resolve y Kinematics.") self.yval = 0 if math.isnan(self.zval): self.writeToTextConsole("Unable to resolve z Kinematics.") self.zval = 0 self.frontpage.setPosReadout(self.xval, self.yval, self.zval) self.frontpage.gcodecanvas.positionIndicator.setPos( self.xval, self.yval, self.data.units) except: print "One Machine Position Report Command Misread" return def setErrorOnScreen(self, message): try: startpt = message.find(':') + 1 endpt = message.find(',', startpt) leftErrorValueAsString = message[startpt:endpt] leftErrorValueAsFloat = float(leftErrorValueAsString) startpt = endpt + 1 endpt = message.find(',', startpt) rightErrorValueAsString = message[startpt:endpt] rightErrorValueAsFloat = float(rightErrorValueAsString) if self.data.units == "INCHES": rightErrorValueAsFloat = rightErrorValueAsFloat / 25.4 leftErrorValueAsFloat = leftErrorValueAsFloat / 25.4 avgError = (abs(leftErrorValueAsFloat) + abs(rightErrorValueAsFloat)) / 2 self.frontpage.gcodecanvas.positionIndicator.setError( 0, self.data.units) self.data.logger.writeErrorValueToLog(avgError) self.frontpage.gcodecanvas.targetIndicator.setPos( self.xval - .5 * rightErrorValueAsFloat + .5 * leftErrorValueAsFloat, self.yval - .5 * rightErrorValueAsFloat - .5 * leftErrorValueAsFloat, self.data.units) except: print "Machine Position Report Command Misread Happened Once"
def build(self): interface = FloatLayout() self.data = Data() if self.config.get('Maslow Settings', 'colorScheme') == 'Light': self.data.iconPath = './Images/Icons/normal/' self.data.fontColor = '[color=7a7a7a]' self.data.drawingColor = [.47, .47, .47] Window.clearcolor = (1, 1, 1, 1) self.data.posIndicatorColor = [0, 0, 0] self.data.targetInicatorColor = [1, 0, 0] elif self.config.get('Maslow Settings', 'colorScheme') == 'Dark': self.data.iconPath = './Images/Icons/highvis/' self.data.fontColor = '[color=000000]' self.data.drawingColor = [1, 1, 1] Window.clearcolor = (0, 0, 0, 1) self.data.posIndicatorColor = [1, 1, 1] self.data.targetInicatorColor = [1, 0, 0] Window.maximize() self.frontpage = FrontPage(self.data, name='FrontPage') interface.add_widget(self.frontpage) self.nonVisibleWidgets = NonVisibleWidgets() ''' Load User Settings ''' if self.config.get('Advanced Settings', 'encoderSteps') == '8148.0': self.data.message_queue.put( "Message: This update will adjust the the number of encoder pulses per rotation from 8,148 to 8,113 in your settings which improves the positional accuracy.\n\nPerforming a calibration will help you get the most out of this update." ) self.config.set('Advanced Settings', 'encoderSteps', '8113.73') self.config.write() self.data.comport = self.config.get('Maslow Settings', 'COMport') self.data.gcodeFile = self.config.get('Maslow Settings', 'openFile') offsetX = float(self.config.get('Advanced Settings', 'homeX')) offsetY = float(self.config.get('Advanced Settings', 'homeY')) self.data.gcodeShift = [offsetX, offsetY] self.data.config = self.config ''' Initializations ''' self.frontpage.setUpData(self.data) self.nonVisibleWidgets.setUpData(self.data) self.frontpage.gcodecanvas.initialize() ''' Scheduling ''' Clock.schedule_interval(self.runPeriodically, .01) ''' Push settings to machine ''' self.data.bind(connectionStatus=self.push_settings_to_machine) self.data.pushSettings = self.push_settings_to_machine return interface