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"
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