Beispiel #1
0
class AnimalShelter:
    def __init__(self):
        self.dog_queue = Queue()
        self.cat_queue = Queue()

    def enqueue(self, animal):
        animal.time_stamp = time.time()
        if animal.type == "dog":
            self.dog_queue.add(animal)
        if animal.type == "cat":
            self.cat_queue.add(animal)

    def dequeue_any(self):
        if self.dog_queue.is_empty():
            return self.cat_queue.remove()
        if self.cat_queue.is_empty():
            return self.dog_queue.remove()
        if self.dog_queue.peek().time_stamp < self.cat_queue.peek().time_stamp:
            return self.dog_queue.remove()
        else:
            return self.cat_queue.remove()

    def dequeue_dog(self):
        return self.dog_queue.remove()

    def dequeue_cat(self):
        return self.cat_queue.remove()
Beispiel #2
0
class TestQueue(unittest.TestCase):
    def setUp(self):
        self.queue = Queue()

    def queue_add_small(self):
        self.queue.add(1)
        self.queue.add(2)
        self.queue.add(3)

    def sanity_check(self, input_data):
        self.assertEqual(self.queue.data, input_data)

    def test_init(self):
        self.sanity_check([])

    def test_add(self):
        self.queue.add("hi")
        self.sanity_check(["hi"])
        self.queue.add("hello")
        self.sanity_check(["hi", "hello"])

    def test_peek(self):
        self.queue_add_small()
        self.assertEqual(self.queue.peek(), 1)

    def test_remove(self):
        self.queue_add_small()
        output = self.queue.remove()
        self.assertEqual(output, 1)
        self.sanity_check([2, 3])
        output = self.queue.remove()
        self.assertEqual(output, 2)
        self.sanity_check([3])
        output = self.queue.remove()
        self.assertEqual(output, 3)
        self.sanity_check([])

    def test_peek_error(self):
        self.assertRaises(Exception, lambda: self.queue.peek())

    def test_pop_error(self):
        self.assertRaises(Exception, lambda: self.queue.remove())
Beispiel #3
0
class TwoAxisStage:
    """
    Test Description
    """
    def __init__(self, port, baud, startupfile):
        self.s = None
        self.window = tk.Toplevel()
        self.window.title('Plasma n Lasers')
        self.pos = [0., 0.]
        self.currentpos = 'X0 Y0'
        self.rate = 1
        self.grid = [5, 9]
        self.rowLen = 5
        self.colLen = 9
        self.port = port
        self.baud = baud
        self.startupfile = startupfile
        self.connected = False
        self.queue = None
        self.tempFile = None
        self.temprunning = False
        self.filename = None
        self.buttonx = 12
        self.buttony = 6
        self.feedrate = 0
        self.shotnum = 0
        self.datafile = None
        self.datafilename = None
        self.parameters = {
            'stepPulseLength': [0, None],
            'stepIdleDelay': [1, None],
            'axisDirection': [3, None],
            'statusReport': [10, None],
            'feedbackUnits': [13, None],
            'xSteps/mm': [100, None],
            'ySteps/mm': [101, None],
            'xMaxRate': [110, None],
            'yMaxRate': [111, None],
            'xMaxAcc': [120, None],
            'yMaxAcc': [121, None],
        }

        self.param_number = 2  ###########

        # draws all on-screen controls and assigns their event commands
        self.rowarr = list(i for i in range(self.grid[0]))
        self.colarr = list(i for i in range(self.grid[1]))

        self.window.rowconfigure(self.rowarr, minsize=50, weight=1)
        self.window.columnconfigure(self.colLen, minsize=50, weight=1)

        # On screen control grid
        self.btn_w = tk.Button(master=self.window,
                               text="\u219F",
                               command=lambda: self.jogY(self.rate))
        self.btn_w['font'] = font.Font(size=18)
        self.btn_w.grid(row=1, column=2, sticky="nsew")

        self.btn_a = tk.Button(master=self.window,
                               text="\u219E",
                               command=lambda: self.jogX(-1 * self.rate))
        self.btn_a['font'] = font.Font(size=18)
        self.btn_a.grid(row=2, column=1, sticky="nsew")

        self.btn_s = tk.Button(master=self.window,
                               text="\u21A1",
                               command=lambda: self.jogY(-1 * self.rate))
        self.btn_s['font'] = font.Font(size=18)
        self.btn_s.grid(row=2, column=2, sticky="nsew")

        self.btn_d = tk.Button(master=self.window,
                               text="\u21A0",
                               command=lambda: self.jogX(self.rate))
        self.btn_d['font'] = font.Font(size=18)
        self.btn_d.grid(row=2, column=3, sticky="nsew")

        # serial connect button
        self.connect_btn = tk.Button(
            master=self.window,
            text='connect',
            fg='red',
            command=lambda: self.initSerial(self.port, self.baud, self.
                                            startupfile))
        self.connect_btn.grid(row=1, column=5, sticky='nsew')

        # 10 button
        self.btn_10 = tk.Button(master=self.window,
                                text='10',
                                command=lambda: self.switchRate(10))
        self.btn_10.grid(row=3, column=1, sticky='nsew')
        self.btn_10.configure(width=self.buttonx, height=self.buttony)
        # 1 button
        self.btn_1 = tk.Button(master=self.window,
                               text='1',
                               command=lambda: self.switchRate(1))
        self.btn_1.grid(row=3, column=2, sticky='nsew')
        self.btn_1.configure(width=self.buttonx, height=self.buttony)

        # 0.1 button
        self.btn_01 = tk.Button(master=self.window,
                                text='0.1',
                                command=lambda: self.switchRate(0.1))
        self.btn_01.grid(row=3, column=3, sticky='nsew')
        self.btn_01.configure(width=self.buttonx, height=self.buttony)
        # 0.01 button
        self.btn_001 = tk.Button(master=self.window,
                                 text='0.01',
                                 command=lambda: self.switchRate(0.01))
        self.btn_001.grid(row=3, column=4, sticky='nsew')
        self.btn_001.configure(width=self.buttonx, height=self.buttony)

        self.btn_0001 = tk.Button(master=self.window,
                                  text='0.001',
                                  command=lambda: self.switchRate(0.001))
        self.btn_0001.grid(row=3, column=5, sticky='nsew')
        self.btn_0001.configure(width=self.buttonx, height=self.buttony)

        # DRO frame
        self.lbl_frame = tk.Frame(master=self.window,
                                  relief=tk.RAISED,
                                  borderwidth=3)
        self.lbl_pos = tk.Label(master=self.lbl_frame,
                                text='X: %1.3f, Y:%1.3f, Feedrate: %d' %
                                (self.pos[0], self.pos[1], self.feedrate))
        self.lbl_pos['font'] = font.Font(size=18)

        self.lbl_frame.grid(row=0,
                            column=0,
                            columnspan=self.colLen + 1,
                            sticky='ew')
        self.lbl_pos.pack()

        # gcode input box
        self.gcode_entry = tk.Entry(master=self.window)
        self.gcode_entry.configure(width=40)
        self.gcode_entry.grid(row=4, column=6, sticky='wn')

        # gcode button
        self.gcode_send = tk.Button(master=self.window,
                                    text='Send',
                                    command=lambda: self.sendCommand(
                                        self.gcode_entry.get().rstrip() + '\n',
                                        resetarg=True,
                                        entry=self.gcode_entry))
        self.gcode_send.grid(row=4, column=9, sticky='ewn')
        self.gcode_send.configure(width=5)

        # file input box
        self.file_entry = tk.Entry(master=self.window)
        self.file_entry.grid(row=4, columnspan=3, sticky='new')

        # file input button
        self.file_input = tk.Button(
            master=self.window,
            text='Get File',
            command=lambda: self.getFile(self.file_entry.get()))
        self.file_input.grid(row=4, column=3, sticky='new')
        #self.file_input.configure(width=self.buttonx, height=self.buttony)

        # file run button
        self.file_run = tk.Button(master=self.window,
                                  text='Run',
                                  command=lambda: self.runFile())
        self.file_run.grid(row=4, column=4, sticky='new')
        self.file_run.configure(width=self.buttonx)

        self.kill_btn = tk.Button(master=self.window,
                                  text='Kill',
                                  command=self.killSwitch)
        self.kill_btn.grid(row=4, column=5, sticky='new')
        self.kill_btn.configure(width=self.buttonx)

        self.increment_btn = tk.Button(master=self.window,
                                       text='G91',
                                       command=self.setG91)
        self.increment_btn.grid(row=1, column=1, sticky='nsew')
        self.increment_btn.configure(width=self.buttonx, height=self.buttony)

        self.absolute_btn = tk.Button(master=self.window,
                                      text='G90',
                                      command=self.setG90)
        self.absolute_btn.grid(row=1, column=3, sticky='nsew')
        self.absolute_btn.configure(width=self.buttonx, height=self.buttony)

        # go home button
        self.home_btn = tk.Button(
            master=self.window,
            text='Go\nHome',
            command=lambda: self.sendCommand('G90 X0 Y0 F' + str(
                self.parameters['xMaxRate'][1])))
        self.home_btn.grid(row=2, column=4, sticky='nsew')
        self.home_btn.configure(width=self.buttonx, height=self.buttony)

        # set home button
        self.set_home = tk.Button(
            master=self.window,
            text='Set\nHome',
            command=lambda: self.sendCommand('G92 X0 Y0'))
        self.set_home.grid(row=1, column=4, sticky='nsew')
        self.set_home.configure(width=self.buttonx, height=self.buttony)

        self.start_from_death_btn = tk.Button(master=self.window,
                                              text='No temp\nfile found',
                                              command=self.__startFromDeath)
        self.start_from_death_btn.grid(row=2, column=5, sticky='nesw')
        self.start_from_death_btn['font'] = font.Font(size=10)
        self.start_from_death_btn.configure(width=self.buttonx,
                                            height=self.buttony)

        self.output = tk.Listbox(master=self.window)
        self.output.grid(columnspan=4,
                         rowspan=self.grid[1] - 6,
                         row=1,
                         column=6,
                         sticky='nsew')
        self.output.configure(bg='white')
        self.window.bind('<Return>', (lambda x: self.sendCommand(
            self.gcode_entry.get(), entry=self.gcode_entry, resetarg=True)))

        self.__setTempFile()
        self.setKeybinds()
        self.Refresh()
        # self.window.protocol("WM_DELETE_WINDOW", self.__on_closing)
        self.initSerial(self.port, self.baud, self.startupfile)

    def start(self):
        self.window.mainloop()

    def stop(self):
        self.window.destroy()

    def __on_closing(self):
        if messagebox.askokcancel("Quit", "Quit?"):
            self.s.close()
            self.window.destroy()

    def setKeybinds(self):
        """
        binds keypress event to the onKeyPress function

        :return: None
        """
        self.window.bind('<KeyPress>', self.onKeyPress)

    def Refresh(self):
        """
        Sets recurring event to update GUI every 50ms

        :return: None
        """
        self.lbl_pos.configure(text='X: %1.3f, Y:%1.3f, Feedrate: %d' %
                               (self.pos[0], self.pos[1], self.feedrate))
        self.window.after(5, self.Refresh)

    def onKeyPress(self, event, wasd=False):
        """
        Allows for stage control via WASD - Not sure if keeping implementation

        :param event: onKeyPress event
        :param wasd: if True, wasd controls the stage
        :return: None
        """
        if wasd:
            if event.char.lower() == 'w':
                self.jogY(self.rate)
            elif event.char.lower() == 'a':
                self.jogX(-1 * self.rate)
            elif event.char.lower() == 's':
                self.jogY(-1 * self.rate)
            elif event.char.lower() == 'd':
                self.jogX(self.rate)

    def jogX(self, v):
        """
        Jogs the X stage by the specified rate within the GUI

        :param v: float rate
        :return: None
        """
        if not self.temprunning:
            c = 'G91 x' + str(v) + '\n'
            self.sendCommand(c)

    def jogY(self, v):
        """
        Jogs the Y stage by the specified rate within the GUI

        :param v: float rate
        :return: None
        """
        if not self.temprunning:
            c = 'G91 y' + str(v) + '\n'
            self.sendCommand(c)

    def switchRate(self, v):
        """
        Switches current jog rate to specified input

        :param v: float jog rate
        :return:
        """
        # used to switch the rate's order of magnitude corresponding to pressed button
        self.rate = v

    def readOut(self):
        """
        Reads out the reply from GRBL

        :return: None
        """
        # implement own method?
        out = self.s.readline()  # Wait for grbl response with carriage return
        #print('> ' + out.strip().decode('UTF-8'))
        #self.output['text'] += '\n>' + out.decode('UTF-8').strip()
        self.output.insert('end', '\n' + '> ' + out.decode('UTF-8').rstrip())
        self.output.yview(tk.END)

    def sendCommand(self, gcode, resetarg=False, entry=None):
        """
        Sends command to GRBL. Checks if it is a comment, then sends command and updates DRO position accordingly

        :param gcode: command to be sent
        :param resetarg: used to detect if a command was sent via the input box so it knows to clear it
        :param entry: entry box instance to clear
        :return: None
        """
        # check if it's a comment
        if self.s:
            if gcode.strip() == '' or gcode.strip()[0] == ';':
                return
            t = gcode.rstrip().lower().split(' ')
            # check if it's a manual entry to clear entry box
            if resetarg and entry is not None:
                entry.delete(0, 'end')

            if 'g90' in t:
                self.setG90(cmd=False)
            if 'g91' in t:
                self.setG91(cmd=False)
            for i in t:
                if i.lower().strip()[0] == 'f':
                    self.__setFeed(int(i.strip()[1:]))

            self.output.insert('end', '\n' + '~> ' + gcode.rstrip())
            self.output.yview(tk.END)

            gcode = gcode.rstrip() + '\n'
            self.s.write(gcode.encode('UTF-8'))
            self.readOut()
            # self.__writeData([time.time_ns(), gcode.rstrip()])
            self.setPos(gcode)

    def initSerial(self, port, baud, filename):
        """
        Initalizes serial connection with grbl doohickey. Parses all startup commands from a text file input

        :param port: USB port board is plugged into
        :param baud: communication baud rate
        :param filename: startup filename containing startup grbl code
        :return: None
        """
        if not self.connected:
            try:
                self.s = serial.Serial(port, baud)
            except Exception as e:
                self.stop()
                raise (e)
            else:
                # Wake up grbl
                self.s.write(b"\r\n\r\n")
                # allow grbl to initialize
                time.sleep(2)
                # flush startup from serial
                self.s.flushInput()
                # open startup file
                with open(filename, 'r') as f:
                    for line in f:
                        # strip EOL chars
                        l = line.strip()
                        self.sendCommand(l + '\r')
                print('Connected to GRBL')
                self.connect_btn.configure(fg='green', text='Connected')
                self.connected = True
                self.__parseParameters()

        else:
            self.connected = False
            self.s.close()
            self.s = None
            self.queue = None
            print('Connection closed')
            self.connect_btn.configure(fg='red', text='Connect')

    def setG91(self, cmd=True):
        """
        Sets relative movements active
        :param cmd: If true, send the G91 command, regardless, switch button text color to match

        :return: None
        """
        if self.s:
            if cmd:
                self.sendCommand('G91')
            self.increment_btn.configure(fg='green')
            self.absolute_btn.configure(fg='black')

    def setG90(self, cmd=True):
        """
        Sets absolute movements active

        :param cmd: If true, send the G90 command, regardless, switch button text color to match
        :return: None
        """
        if self.s:
            if cmd:
                self.sendCommand('G90')
            self.increment_btn.configure(fg='black')
            self.absolute_btn.configure(fg='green')

    def setPos(self, cmd):
        """
        sets position of table on DRO

        :param cmd: gcode command containing new location
        :return: None
        """
        # g91 iterates, not sets
        if cmd[:3].lower() == 'g91':
            for i in cmd.split(' '):
                if i.lower()[0] == 'x':
                    self.pos[0] += float(i[1:])
                if i.lower()[0] == 'y':
                    self.pos[1] += float(i[1:])
        else:
            for i in cmd.split(' '):
                if i.lower()[0] == 'x':
                    self.pos[0] = float(i[1:])
                if i.lower()[0] == 'y':
                    self.pos[1] = float(i[1:])

    def getFile(self, filename):
        """
        Opens file containing gcode. Does not parse for correctness.
        Inputs all non blank lines / comments into a queue for usage

        :param filename: File to open
        :return: None
        """
        # queue with timing calculation for next move
        # wipe queue
        self.queue = None
        try:
            self.filename = filename
            f = open(filename, 'r')
            self.queue = Queue()
            for line in f:
                if line != '' and line[0] != ';':
                    self.queue.enqueue(line)
            self.output.insert('end', '\n' + '> ' + ' Loaded ' + filename)
            self.output.yview(tk.END)
        except FileNotFoundError:
            self.file_entry.delete(0, 'end')
            self.output.insert('end', '\n' + '!> ' + ' File does not exist')
            self.output.yview(tk.END)

    def runFile(self):
        """
        Used to run a file obained with getFile()

        :return: None
        """
        if self.s:
            if not self.temprunning:
                self.temprunning = True
                self.__saveTempData()

            nextpos = self.queue.dequeue()
            self.sendCommand(nextpos)

            if self.queue.size() > 0:
                self.window.after(self.calcDelay(self.currentpos, nextpos),
                                  self.runFile)
                self.currentpos = nextpos

            elif self.queue.size() == 0:
                self.window.after(self.calcDelay(self.currentpos, nextpos),
                                  self.finishRun)

            else:
                self.temprunning = False
                return

    def finishRun(self):
        """
        Runs after file completion, removes contingency file since it is not needed.

        :return: None
        """
        print('File run complete')
        self.__removeTempFile()

    def killSwitch(self):
        """
        effectively kills current gcode run by clearing queue. Note this isn't instantaneous

        :return: None
        """
        if self.s:
            self.queue.clear()
            self.temprunning = False
            self.output.insert('end', '\n' + '!> ' + 'Current motion killed')
            self.output.yview(tk.END)

    def calcDelay(self, currentpos, nextpos):
        """
        Calculates a lower end of the required delay between moved for the queue command system

        :param currentpos: Current position
        :param nextpos: Next position
        :return: time delay in ms
        """
        #   todo: parse feeds & speeds from startup file
        #         calculate (approximate?) time delay until next step
        #         circular motion

        ipos = self.__parsePosition(currentpos)
        fpos = self.__parsePosition(nextpos)
        assert len(ipos) == len(fpos), 'Input arrays must be same length'

        v = float(self.parameters['xMaxRate'][1]) / 60
        a = float(self.parameters['xMaxRate']
                  [1])  # becomes very choppy changing to xMaxAcc... idk wtf

        delta = list(ipos[i] - fpos[i] for i in range(len(ipos)))
        d = np.sqrt(sum(i**2 for i in delta))
        deltaT = ((2 * v) / a) + ((d - (v**2 / a)) / v)
        print('next move in ' + str(int(np.floor(deltaT * 1000))) + 'ms')
        return int(np.floor(deltaT * 1000))  # in ms

    def __parsePosition(self, ipos):
        """
        Parses position for use with DRO

        :param ipos: input position
        :return: [x, y] list of current position
        """
        pos = [0, 0]
        for i in ipos.split(' '):
            if i.lower()[0] == 'x':
                pos[0] = float(i[1:])
            if i.lower()[0] == 'y':
                pos[1] = float(i[1:])
        return pos

    def __parseParameters(self):
        """
        Parses parameters from a given input file into the parameters dictionary for usage

        :return: None
        """
        tmp = []
        with open(self.startupfile, 'r') as f:
            for line in f:
                if line != '' and line[0] == '$':
                    tmp.append(line.strip().strip('$'))
        f.close()
        for i in tmp:
            for key, value in self.parameters.items():
                if i.split('=')[0] == str(value[0]):
                    self.parameters[key] = [i.split('=')[0], i.split('=')[1]]

        self.feedrate = int(self.parameters['xMaxRate'][1])
        print(self.feedrate)
        print(self.parameters['xMaxRate'])
        print(self.parameters['xMaxAcc'])

    def __setTempFile(self):
        """
        Sets the temporary file name, unless it exists

        :return: None
        """
        if os.path.exists('temp.npy') or self.tempFile is not None:
            self.start_from_death_btn.configure(text='Load\nData?', fg='red')
            self.__blinkButton(self.start_from_death_btn, 'red', 'blue', 1000)
        else:
            self.tempFile = 'temp.npy'

    def __saveTempData(self):
        """
        Saves temp data to temp file, if program is currently running it calls itself every 1000ms

        :return: None
        """
        if self.temprunning:
            if self.queue.size() > 0:
                np.save(self.tempFile, [
                    self.queue.peek(),
                    time.asctime(), self.filename, self.shotnum
                ])
                self.window.after(1000, self.__saveTempData)
                return True
        else:
            return False

    def __retrieveTempData(self):
        """
        Retrieves temp data from temp.npy if it exists and user calls it. Needs to be updated
        to reflect final changes in temp data stored once integrated into laser system

        :return: Last line that runfile stored before unexpected power loss
        """
        self.tempFile = 'temp.npy'
        currentline, t, self.filename, self.shotnum = np.load(self.tempFile)
        print(currentline, t, self.filename)
        return currentline

    def __startFromDeath(self):
        """
        Starts from an unexpected power loss. Retreives last known position from temp file.
        Issues: Target stage *could* be manually moved on us prior to reviving. User initiative to ensure
        nothing moves.

        :return: None
        """
        if self.s:
            self.queue = None
            currentline = self.__retrieveTempData()
            try:
                f = open(self.filename, 'r')
                self.queue = Queue()
                positionFound = False
                for line in f:
                    if line == currentline:
                        positionFound = True
                    if positionFound:
                        if line != '' and line[0] != ';':
                            self.queue.enqueue(line)
                print('Loaded ' + self.filename)
                self.start_from_death_btn.configure(text='Start?',
                                                    fg='green',
                                                    command=self.runFile)
            except FileNotFoundError:
                self.file_entry.delete(0, 'end')
                self.file_entry.insert(0, 'File does not exist')
        else:
            self.start_from_death_btn.configure(text='Not\nconnected')
            self.window.after(
                5000, lambda: self.start_from_death_btn.configure(
                    text='Load\nData?'))

    def __removeTempFile(self):
        """
        removes temp file at end of program run

        :return: None
        """
        os.remove(self.tempFile)
        self.tempFile = None

    def __blinkButton(self, button, c1, c2, delay):
        """
        Blinks a button between two colors, c1 and c2. Has logic for specific buttons to cease switching given a
        specific string as the text.

        :param button: target button instance
        :param c1: First color to switch to, string
        :param c2: Second color to switch to, string
        :param delay: Delay in ms
        :return: None
        """
        if button['text'] == 'Start?':
            return
        else:
            if button['fg'] == c1:
                button.configure(fg=c2)
            else:
                button.configure(fg=c1)
            self.window.after(
                delay, lambda: self.__blinkButton(button, c1, c2, delay))

    def __createDataFile(self):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single class
        for cohesion

        :return: None
        """
        self.datafilename = str('-'.join(
            list(i.replace(':', '-')
                 for i in time.asctime().split(' ')))) + '.hdf5'
        self.datafile = HDF5File(self.datafilename)

    def __createGroup(self, name):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single
        class for cohesion

        :return: None
        """
        self.datafile.createGroup(name)

    def __setMetadataFromFile(self, mdfile, path='/'):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single
        class for cohesion

        :return: None
        """
        metadata = self.__parseMetadataFile(mdfile)
        for key, value in metadata.items():
            self.datafile.setMetadata(key, value, path=path)

    def __setMetadata(self, key, value, path='/'):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single
        class for cohesion

        :return: None
        """
        self.datafile.setMetadata(key, value, path=path)

    def __createDataSet(self, Group, Data):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single
        class for cohesion

        :return: None
        """
        self.datafile.create_dataset(Group, Data, Data.shape)

    def __parseMetadataFile(self, filename):
        """
        Preliminary HDF5 methods prior to implementation of HDF5Methods.py. Likely will remove and integrate single
        class for cohesion

        :return: None
        """
        md = {}
        try:
            with open(filename, 'r') as f:
                for i in f.readlines():
                    j = i.rstrip().split(';')
                    md[j[0]] = j[1]
            return md

        except:
            print('metadata file broken, fix and try again')

    def __writeData(self, data):
        """
        Writes data to the class instances datafile

        :param data: Data to write
        :return: None
        """
        self.datafile.append(data)

    def __setFeed(self, feedrate):
        """
        Sets feedrate based on input

        :param feedrate: New feedrate in mm/min
        :return: None
        """
        self.feedrate = feedrate