Beispiel #1
0
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"
Beispiel #2
0
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"
Beispiel #3
0
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"
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"
Beispiel #5
0
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"
Beispiel #6
0
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"