class ToolBar(Frame): def __init__(self, root, printer, settings, logger, *arg): self.app = root self.printer = printer self.settings = settings self.logger = logger self.app.printing = False self.app.connected = False self.app.paused = False Frame.__init__(self, root, *arg) topBar = Frame(self) topBar.grid(row=1, column=1, columnspan=3, sticky=W) speedBar = Frame(self) speedBar.grid(row=1, column=5, sticky=E) bottomBar = Frame(self) bottomBar.grid(row=2, column=1, columnspan=6, sticky=W+E) self.bPort = Button(topBar, text="Port", width=BWIDTH, command=self.doPort) self.bPort.pack(side=LEFT, padx=2, pady=2) ports = self.scanSerial() self.spPort = Spinbox(topBar, values=ports, state="readonly") self.spPort.pack(side=LEFT, padx=2, pady=2) l = Label(topBar, text=" @ ") l.pack(side=LEFT, padx=2, pady=2) self.spBaud = Spinbox(topBar, values=baudChoices) self.spBaud.pack(side=LEFT, padx=2, pady=2) self.spBaud.delete(0, END) self.spBaud.insert(0, 115200) self.spBaud.config(state="readonly") self.bConnectMode = CM_CONNECT self.bConnect = Button(topBar, text=connectText[CM_CONNECT], width=BWIDTH, command=self.doConnect) self.bConnect.pack(side=LEFT, padx=2, pady=2) if len(ports) == 0: self.bConnect.config(state=DISABLED) else: self.bConnect.config(state=NORMAL) self.bReset = Button(topBar, text="Reset", width=BWIDTH, command=self.doReset, state=DISABLED) self.bReset.pack(side=LEFT, padx=2, pady=2) l = Label(speedBar, text="Speed:", justify=RIGHT) l.grid(row=1, column=1, sticky=E) self._speedJob = None self.currentSpeed = self.app.FeedMultiply self.scSpeed = Scale(speedBar, from_=MINSPEED, to=MAXSPEED, orient=HORIZONTAL, command=self.updateSpeedCommand) self.scSpeed.grid(row=1, column=2) self.scSpeed.set(self.currentSpeed); l = Label(speedBar, text="Fan:", width=10, anchor=E, justify=RIGHT) l.grid(row=1, column=3, sticky=E) self._fanJob = None self.currentFanSpeed = self.app.FanSpeed self.scFan = Scale(speedBar, from_=0, to=255, orient=HORIZONTAL, command=self.updateFanSpeedCommand) self.scFan.grid(row=1, column=4) self.scFan.set(self.currentFanSpeed); if self.settings.speedcommand is not None: self.cbvAssertFan = IntVar() if self.settings.forcefanspeed: self.cbvAssertFan.set(1) else: self.cbvAssertFan.set(0) self.cbAssertFan = Checkbutton(speedBar, text="Force", variable=self.cbvAssertFan, command=self.clickAssertFan) self.cbAssertFan.grid(row=1, column=5) self.bSliceMode = SM_SLICE self.bSlice = Button(bottomBar, text=sliceText[SM_SLICE], width=BWIDTH*2, command=self.doSlice) self.bSlice.pack(side=LEFT, padx=2, pady=2) self.bLoad = Button(bottomBar, text="Load GCode", width=BWIDTH, command=self.doLoad) self.bLoad.pack(side=LEFT, padx=2, pady=2) self.setSliceText() self.bSD = Button(bottomBar, text="SD", width=BWIDTH, command=self.doSD, state=DISABLED) self.bSD.pack(side=LEFT, padx=2, pady=2) self.bPrintMode = PR_PRINT self.bPrint = Button(bottomBar, text=printText[PR_PRINT], width=BWIDTH, command=self.doPrint, state=DISABLED) self.bPrint.pack(side=LEFT, padx=2, pady=2) self.bPauseMode = PM_PAUSE self.bPause = Button(bottomBar, text=pauseText[PM_PAUSE], width=BWIDTH, command=self.doPause, state=DISABLED) self.bPause.pack(side=LEFT, padx=2, pady=2) self.bAbandon = Button(bottomBar, text="Abandon SD Print", width=BWIDTH+8, command=self.doAbandon, state=DISABLED) self.bAbandon.pack(side=LEFT, padx=2, pady=2) self.cbvLiftOnPause = IntVar() if self.settings.liftonpause: self.cbvLiftOnPause.set(1) else: self.cbvLiftOnPause.set(0) self.cbLiftOnPause = Checkbutton(bottomBar, text="Lift Head/Retract on Pause", variable=self.cbvLiftOnPause, command=self.clickLiftOnPause) self.cbLiftOnPause.pack(side=LEFT, padx=2) self.cbvResumeAtPause = IntVar() if self.settings.resumeatpausepoint: self.cbvResumeAtPause.set(1) else: self.cbvResumeAtPause.set(0) self.cbResumeAtPause = Checkbutton(bottomBar, text="Resume print at pause point", variable=self.cbvResumeAtPause, command=self.clickResumeAtPause) self.cbResumeAtPause.pack(side=LEFT, padx=2) def clickAssertFan(self): if self.cbvAssertFan.get() == 1: self.settings.forcefanspeed = True self.settings.setModified() else: self.settings.forcefanspeed = False self.settings.setModified() def clickLiftOnPause(self): if self.cbvLiftOnPause.get() == 1: self.settings.liftonpause = True self.settings.setModified() else: self.settings.liftonpause = False self.settings.setModified() def clickResumeAtPause(self): if self.cbvResumeAtPause.get() == 1: self.settings.resumeatpausepoint = True self.settings.setModified() else: self.settings.resumeatpausepoint = False self.settings.setModified() def setSliceText(self): if self.settings.slicer == SLIC3R: sl = "slic3r:%s" % self.app.slic3r.getProfile() else: sl = "skeinforge:%s" % self.app.skeinforge.getProfile() sliceText[SM_SLICE] = "Slice (%s)" % sl if self.bSliceMode == SM_SLICE: self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) lt = len(sliceText[SM_SLICE])+2 if lt < BWIDTH: lt = BWIDTH self.bSlice.config(text=sliceText[SM_SLICE], width=lt) def updateSpeedCommand(self, *arg): if self._speedJob: self.app.master.after_cancel(self._speedJob) self._speedJob = self.app.master.after(500, self.updateSpeed) def updateSpeed(self, *arg): v = self.scSpeed.get() self.setSpeed(v) def setSpeed(self, v): if v < MINSPEED or v > MAXSPEED: self.logger.logMsg("Attempt to change speed outside of allowable range (%d-%d)" % (MINSPEED, MAXSPEED)) elif int(v) != self.currentSpeed: if self.app.connected: self.currentSpeed = int(v) self.logger.logMsg("changing speed percentage to %d%%" % self.currentSpeed) cmd = "M220 S%d" % self.currentSpeed self.printer.send_now(cmd) else: self.logger.logMsg("Printer is off-line") self.scSpeed.set(self.currentSpeed) def updateFanSpeedCommand(self, *arg): if self._fanJob: self.app.master.after_cancel(self._fanJob) self._fanJob = self.app.master.after(500, self.updateFanSpeed) def updateFanSpeed(self, *arg): v = self.scFan.get() self.setFanSpeed(v) self.app.FanSpeed = v def forceFanSpeed(self, v): self.currentFanSpeed = -1 self.setFanSpeed(v) def setFanSpeed(self, v): if int(v) != self.currentFanSpeed: if self.app.connected: self.currentFanSpeed = int(v) cmd = "M106 S%d" % self.currentFanSpeed self.printer.send_now(cmd) else: self.logger.logMsg("Printer is off-line") self.scFan.set(self.currentFanSpeed) def syncSpeeds(self): self.currentSpeed = self.app.FeedMultiply self.scSpeed.set(self.currentSpeed) self.currentFanSpeed = self.app.FanSpeed self.scFan.set(self.currentFanSpeed) def initializeToolbar(self): self.bReset.config(state=DISABLED) self.bSliceMode = SM_SLICE self.bSlice.config(text=sliceText[SM_SLICE]) self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) if not self.app.sdprinting and not self.app.sdpaused: self.bPrintMode = PR_PRINT self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPauseMode = PM_PAUSE self.bPause.config(text=pauseText[PM_PAUSE], state=DISABLED) def setSDPrint(self): self.bPause.config(text=pauseText[PM_PAUSE], state=NORMAL) self.bPauseMode = PM_PAUSE self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT self.bAbandon.config(state=NORMAL) def clearSDPrint(self): self.bPause.config(text=pauseText[PM_PAUSE], state=DISABLED) self.bPauseMode = PM_PAUSE self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT self.bAbandon.config(state=DISABLED) self.checkAllowPrint() def setCancelMode(self): self.bSliceMode = SM_CANCEL self.bSlice.config(text=sliceText[SM_CANCEL], width=BWIDTH) self.bLoad.config(state=DISABLED) self.app.allowLoadGCodeMenu(False) self.app.allowSliceMenu(False) def setLoading(self, flag): if flag: self.bLoad.config(state=DISABLED) self.bSlice.config(state=DISABLED) self.app.allowLoadGCodeMenu(False) self.app.allowSliceMenu(False) else: self.bLoad.config(state=NORMAL) self.bSlice.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) self.app.allowSliceMenu(True) def clearCancelMode(self): self.bSliceMode = SM_SLICE lt = len(sliceText[SM_SLICE])+2 if lt < BWIDTH: lt = BWIDTH self.bSlice.config(text=sliceText[SM_SLICE], width=lt) self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) self.app.allowSliceMenu(True) def doConnect(self): if self.bConnectMode == CM_CONNECT: port = self.spPort.get() baud = int(self.spBaud.get()) self.printer.onlinecb = self.onlinecb try: self.printer.connect(port, baud) except SerialException: self.logger.logMsg("Unable to open printer port %s" % port) else: if self.app.printing: self.logger.logMsg("Please wait until printing has finished or is paused") else: self.printer.disconnect() self.printer.onlinecb = None self.app.printerConnected(False) # self.app.connected = False self.bConnectMode = CM_CONNECT self.bConnect.config(text=connectText[CM_CONNECT]) self.bReset.config(state=DISABLED) self.bSD.config(state=DISABLED) if self.app.paused: self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT self.bPause.config(text=pauseText[PM_PAUSE], state=DISABLED) self.bPauseMode = PM_PAUSE self.app.printing = False self.app.paused = False def doReset(self): if tkMessageBox.askyesno("Reset?", "Are you sure you want to reset the printer?", parent=self.app): self.printer.reset() self.printer.printing = 0 self.app.printing = False self.bSlice.config(state=NORMAL) self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) self.app.allowSliceMenu(True) self.bPrintMode = PR_PRINT self.bPrint.config(text=printText[PR_PRINT], state=NORMAL) if self.app.paused: self.printer.paused = 0 self.bPause.config(text=pauseText[PM_PAUSE], state=DISABLED) self.bPauseMode = PM_PAUSE self.app.paused = False def onlinecb(self): self.logger.logMsg("Printer is on-line") self.app.printerConnected(True) # self.app.connected = True self.bConnectMode = CM_DISCONNECT self.bConnect.config(text=connectText[CM_DISCONNECT]) self.bReset.config(state=NORMAL) self.bSD.config(state=NORMAL) self.checkAllowPrint() def checkAllowPrint(self): if self.app.connected and len(self.app.gcode) != 0 and not self.app.printing and not self.app.sdprinting: self.bPrint.config(text=printText[PR_PRINT], state=NORMAL) self.bPrintMode = PR_PRINT def printComplete(self): self.app.endTime = time.time() self.app.elapsedTime = self.app.endTime - self.app.startTime self.logger.logMsg("Printing completed at %s" % time.strftime('%H:%M:%S', time.localtime())) self.logger.logMsg("Total elapsed time: %s" % formatElapsed(self.app.elapsedTime)) self.bPrintMode = PR_PRINT self.bPrint.config(text=printText[PR_PRINT], state=NORMAL) self.bPauseMode = PM_PAUSE self.bPause.config(text=pauseText[PM_PAUSE], state=DISABLED) self.app.printing = False self.bSlice.config(state=NORMAL) self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) self.app.allowSliceMenu(True) self.app.paused = False self.app.gc.updatePrintProgress(0) self.app.closeAllReports() if self.settings.endprintmacro is not None: self.app.doMacro(name=self.settings.endprintmacro, silent=True) def doPause(self): if self.bPauseMode == PM_PAUSE: if self.app.printing: self.app.paused = True self.printer.pause() elif self.app.sdprinting: self.app.sdpaused = True self.printer.send_now("M25") self.app.sdprinting = False self.bPause.config(text=pauseText[PM_RESUME]) self.bPauseMode = PM_RESUME self.bPrint.config(text=printText[PR_RESTART], state=NORMAL) self.bPrintMode = PR_RESTART else: if self.app.sdpaused: self.printer.send_now("M24") self.app.sdpaused = False self.app.sdprinting = True self.bPause.config(text=pauseText[PM_PAUSE]) self.bPauseMode = PM_PAUSE self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT else: self.hitPrint = False if self.settings.resumeatpausepoint: self.printer.send_now("G1 X%s Y%s F%s" % (self.app.pausePoint[XAxis], self.app.pausePoint[YAxis], self.settings.xyfeed)) self.printer.send_now("G1 Z%s F%s" % (self.app.pausePoint[ZAxis], self.settings.zfeed)) self.printer.send_now("G92 E%s" % self.app.pausePoint[EAxis]) self.printer.send_now("G1 F%s" % self.settings.xyfeed) self.printer.startcb = self.startcb self.printer.resume() def doAbandon(self): self.printer.send_now("M25") self.app.sdpaused = False self.app.sdprinting = False self.clearSDPrint() def doSlice(self): if self.bSliceMode == SM_SLICE: self.bLoad.config(state=DISABLED) self.app.allowLoadGCodeMenu(False) self.app.openSTLFile() else: self.app.slicerCancel = True self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) def doLoad(self): self.app.openGCodeFile() def doPrint(self): if self.app.sdpaused: self.printer.send_now("M26 S0") self.printer.send_now("M24") self.app.sdpaused = False self.app.sdprinting = True self.bPause.config(text=pauseText[PM_PAUSE]) self.bPauseMode = PM_PAUSE self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT else: if len(self.app.gcode) == 0: self.logger.logMsg("Nothing to print") else: #if not self.app.paused: self.app.gc.updatePrintProgress(0, restart=True) self.bPauseMode = PM_PAUSE self.bPause.config(text=pauseText[PM_PAUSE], state=NORMAL) self.hitPrint = True self.printer.startcb = self.startcb self.printer.startprint(self.app.gcode) def startcb(self): self.printer.startcb = None if not self.app.paused: self.app.startTime = time.time() self.logger.logMsg("Printing Started at %s" % time.strftime('%H:%M:%S', time.localtime(self.app.startTime))) self.app.printing = True self.bSlice.config(state=DISABLED) self.bLoad.config(state=DISABLED) self.app.allowLoadGCodeMenu(False) self.app.allowSliceMenu(False) self.bPrint.config(text=printText[PR_RESTART], state=DISABLED) self.bPrintMode = PR_RESTART self.printer.endcb = self.endcb else: if self.hitPrint: self.app.startTime = time.time() self.logger.logMsg("Printing restarted at %s" % time.strftime('%H:%M:%S', time.localtime())) else: self.logger.logMsg("Printing resumed at %s" % time.strftime('%H:%M:%S', time.localtime())) self.bPause.config(text=pauseText[PM_PAUSE]) self.bPauseMode = PM_PAUSE self.app.printing = True self.bSlice.config(state=DISABLED) self.bLoad.config(state=DISABLED) self.app.allowLoadGCodeMenu(False) self.app.allowSliceMenu(False) self.app.paused = False self.bPrint.config(state=DISABLED) self.printer.endcb = self.endcb def endcb(self): self.printer.endcb = None if not self.app.paused: self.printComplete() if self.app.sduploading: self.app.sduploading = False self.printer.send_now("M29") else: self.app.event_generate(MWM_REQUESTPOSITIONREPORT) # record the current printer position and how many intervals were willing to wait fo the response self.maxWait114 = MAXWAIT114; self.waitPos = self.printer.queueindex self.check114Response() def check114Response(self) : # tick off 1 interval, and make sure we haven't either received the report or we've waited max intervals self.maxWait114 -= 1 if self.app.m114count == 0 or self.maxWait114 <= 0: # one way or the other we're done waiting here if self.maxWait114 <= 0: self.app.m114count = 0 self.logger.logMsg("Location report not received - proceeding") self.app.pausePoint[XAxis] = self.app.location[XAxis] self.app.pausePoint[YAxis] = self.app.location[YAxis] self.app.pausePoint[ZAxis] = self.app.location[ZAxis] self.app.pausePoint[EAxis] = self.app.location[EAxis] self.logger.logMsg("Pause location: X:%f Y:%f Z:%f E:%f" % (self.app.pausePoint[XAxis], self.app.pausePoint[YAxis], self.app.pausePoint[ZAxis], self.app.pausePoint[EAxis])) if self.settings.liftonpause: self.printer.send_now("G1 E%s F%s" % (self.app.pausePoint[EAxis]-2, self.settings.efeed)) self.printer.send_now("G1 Z%s F%s" % (self.app.pausePoint[ZAxis]+10, self.settings.zfeed)) self.bPause.config(text=pauseText[PM_RESUME]) self.bPauseMode = PM_RESUME self.app.printing = False self.bSlice.config(state=NORMAL) self.bLoad.config(state=NORMAL) self.app.allowLoadGCodeMenu(True) self.app.allowSliceMenu(True) if self.app.sduploading: self.bPrint.config(text=printText[PR_PRINT], state=DISABLED) self.bPrintMode = PR_PRINT else: self.bPrint.config(text=printText[PR_RESTART], state=NORMAL) self.bPrintMode = PR_RESTART else: # we still are waiting for the report, but reset everything if the printer is still moving if self.waitPos != self.printer.queueindex: self.waitPos = self.printer.queueindex self.maxWait114 = MAXWAIT114 self.app.master.after(500, self.check114Response) def doPort(self): l = self.scanSerial() self.spPort.config(values=l) if len(l) == 0: self.bConnect.config(state=DISABLED) else: self.bConnect.config(state=NORMAL) def scanSerial(self): """scan for available ports. return a list of device names.""" baselist=[] if os.name=="nt": try: key=_winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM") i=0 while(1): baselist+=[_winreg.EnumValue(key,i)[1]] i+=1 except: pass return baselist+glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') +glob.glob("/dev/tty.*")+glob.glob("/dev/cu.*")+glob.glob("/dev/rfcomm*") def doSD(self): self.app.doSD()
class JobParameters(Toplevel): def __init__(self, parent=None, **kwargs): Toplevel.__init__(self, parent) self.title('Job Parameters') self.parent = parent self.main_frame = Frame(self) self.input_directory_gui = DirectorySelector(self.main_frame, 'Input Directory:') self.output_directory_gui = DirectorySelector(self.main_frame, 'Output Directory:', get_default=lambda: self.input_directory_gui.get_directory() + '/output/') self.input_directory_gui.set_notify(self.notify) self.beginning_year = kwargs['beginning_year'] if 'beginning_year' in kwargs else 1950 self.ending_year = kwargs['ending_year'] if 'ending_year' in kwargs else 2100 self.beginning_year_selector = YearSelector(self.main_frame, text="Beginning Year:", min_value=self.beginning_year, max_value=self.ending_year) self.ending_year_selector = YearSelector(self.main_frame, text="Ending Year:", min_value=self.beginning_year, max_value=self.ending_year) self.one_decade_range_var = IntVar() self.one_decade_range = Checkbutton(self.main_frame, text='Calculate 10 Year Range', variable=self.one_decade_range_var, command=self.on_change) self.two_decade_range_var = IntVar() self.two_decade_range = Checkbutton(self.main_frame, text='Calculate 20 Year Range', variable=self.two_decade_range_var, command=self.on_change) self.custom_range_var = IntVar() self.custom_range = Checkbutton(self.main_frame, text='Calculate Custom Year Range', variable=self.custom_range_var, command=self.on_change) self.custom_range_val = StringVar() self.custom_range_input = Spinbox(self.main_frame, from_=30, to=100, textvariable=self.custom_range_val, command=self.on_change, width=5) # Initialize widget values self.beginning_year_selector.set_value(self.beginning_year) self.beginning_year_selector.set_args(check_other_func=self.ending_year_selector.get_less_than) self.ending_year_selector.set_value(self.ending_year) self.ending_year_selector.set_args(check_other_func=self.beginning_year_selector.get_greater_than) self.one_decade_range_var.set(1) self.two_decade_range_var.set(1) self.custom_range_var.set(0) self.custom_range_input.config(state=Tkinter.DISABLED) self.button_frame = Frame(self.main_frame) self.ok_button = Button(self.button_frame, text='OK', command=self.on_submit) self.cancel_button = Button(self.button_frame, text='Cancel', command=self.on_close) self.job_id = kwargs['job_id'] if 'job_id' in kwargs else None self.entry = kwargs['entry'] if 'entry' in kwargs else None self.grab_set() self.resizable(width=False, height=False) def notify(self): self.output_directory_gui.notify() self.lift() def set_grid(self): # Layout child widgets self.main_frame.grid() self.input_directory_gui.set_grid(row=0, padx=6) self.output_directory_gui.set_grid(row=1, padx=6) self.beginning_year_selector.set_grid(row=2, padx=6) self.ending_year_selector.set_grid(row=3, padx=6) self.one_decade_range.grid(row=4, column=1, sticky='w', padx=6) self.two_decade_range.grid(row=5, column=1, sticky='w', padx=6) self.custom_range.grid(row=6, column=1, sticky='w', padx=6) self.custom_range_input.grid(row=6, column=2, sticky='w', padx=6) self.button_frame.grid(row=7, columnspan=3, sticky='nsew') self.ok_button.pack(side=Tkinter.RIGHT) self.cancel_button.pack(side=Tkinter.RIGHT) #self.ok_button.grid(row=7, column=1, pady=2) #self.cancel_button.grid(row=7, column=2, pady=2) def on_change(self): is_custom_range_checked = self.custom_range_var.get() == 1 if is_custom_range_checked: self.custom_range_input.config(state=Tkinter.NORMAL) else: self.custom_range_input.config(state=Tkinter.DISABLED) def on_submit(self): if self.input_directory_gui.get_directory() == '' or self.output_directory_gui.get_directory() == '': self.on_close() return # The job parameters are extracted from the GUI here and passed to the processing thread to run the requisite job. job = dict() job['job_id'] = self.job_id job['delimiter'] = '.' job['individual_files'] = False job['input_directory'] = self.input_directory_gui.get_directory() job['output_directory'] = self.output_directory_gui.get_directory() job['start'] = self.beginning_year_selector.get_value() job['end'] = self.ending_year_selector.get_value() job['calculate_one_decade'] = self.one_decade_range_var.get() == 1 job['calculate_two_decade'] = self.two_decade_range_var.get() == 1 job['calculate_custom_range'] = self.custom_range_var.get() == 1 job['custom_range'] = int(self.custom_range_val.get()) job['log'] = True if self.entry != None: self.entry.add_job(job) self.on_close() def on_close(self): if self.parent != None: self.parent.focus_set() self.withdraw() self.destroy() def on_focus(self): self.focus_force()
class TkPictureFrame(Frame): def __init__(self, x, y, master=None): Frame.__init__(self, master) self.photo = None self.resolution = (x, y) #The center of the Canvas is 0, 0. Find the center so #we can draw the image properly. self.center = ( x/2, y/2) #Setup the canvas self.picture = Canvas(self, width=x, height=y) #Place the canvas in the Grid. self.picture.grid(row=0,column=0,columnspan=2) #Camera check button control. self.checkButton = Checkbutton(self, text='Camera?',\ command=self.toggleCamera) #Place it on the grid. self.checkButton.grid(row=1,column=0) #Spinbox to set FPS self.fpsSpin = Spinbox(self, text="FPS", from_=2, to=30,\ command=self.fpsSpinCallback) self.fpsSpin.grid(row=1, column=1) #Set framerate self.fpsSpinCallback() #To determine if the camera is running self.capturing = False def fpsSpinCallback(self): self.fps = int(self.fpsSpin.get()) def changePic(self, photo): #Make a reference to the old photo for removal self.oldphoto = self.photo #Setup the new photo self.photo = photo #Draw the new photo over the old photo self.picture.create_image(self.center,image=self.photo) #Remove the old photo self.picture.delete(self.oldphoto) #Enable the checkbox self.checkButton.config(state="normal") def timedDisable(self, widget): #Disable a widget for 2 seconds. widget.config(state="disabled") time.sleep(2) widget.config(state="normal") def threadTimeDisable(self, widget): #Run the timed disable in a thread to avoid lockups. thread.start_new_thread(self.timedDisable, (widget,)) def startCamera(self): #Disable the checkbox and fps spinner. self.checkButton.config(state="disabled") self.fpsSpin.config(state="disabled") #Start the camera thread.start_new_thread(self.setupCamera, self.resolution) self.capturing = True def stopCamera(self): #Disable the checkbox for a duration. self.threadTimeDisable(self.checkButton) #Enable the spinner. self.fpsSpin.config(state="normal") #Clear the canvas. self.capturing = False self.picture.delete("all") def toggleCamera(self): if self.capturing: self.stopCamera() else: self.startCamera() def setupCamera(self, x, y): with picamera.PiCamera() as camera: camera.resolution = (x, y) camera.framerate = self.fps stream = io.BytesIO() for each in camera.capture_continuous(stream, format='jpeg'): # Truncate the stream to the current position (in case # prior iterations output a longer image) each.truncate() #Rewind the stream each.seek(0) #Open the image stream image = Image.open(each) photo = ImageTk.PhotoImage(image) #Break out of the loop if not capturing if not self.capturing: break #Update the canvas self.changePic(photo) #Reset playback to the beginning for the next image. each.seek(0)
class pump_ui(object): def __init__(self): master = Tk() master.style = ttk.Style() master.style.theme_use("default") master.config(bg=background_colour) master.resizable( 0, 0 ) # Disable resizing the UI to prevent having to make widget placing dynamic master.winfo_toplevel().title(frame_title) master.iconbitmap("bitcoin.ico") # Create pumper assistant to store data on the current BTC and alt holdings. self.pumper = pumper() self.create_title(master) self.create_api_info(master, previous_row=0) self.create_auto_sell(master, previous_row=3) self.create_stop_loss(master, previous_row=4) self.create_order_type(master, previous_row=6) self.create_fee_type(master, previous_row=7) self.create_btc_balance_picker(master, previous_row=8) self.create_alt_ticker(master, previous_row=10) self.create_pump_and_sell_buttons(master, previous_row=11) self.create_current_profit(master, previous_row=12) self.create_output_box(master, rightmost_column=1) # Can hardcode API key and Secret #self.api_key_entry.delete(0,END) #self.api_key_entry.insert(0,"KEY") #self.api_key_entry.config(state=DISABLED) #self.api_secret_entry.delete(0, END) #self.api_secret_entry.insert(0, "SECRET") #self.api_secret_entry.config(state=DISABLED) # Display the UI, this can only be called once per program. # Nothing in the main Python script will be run after creating the UI because of this. master.mainloop() def create_title(self, master, previous_row=-1, previous_column=-1): empty = Label(master, text=frame_title) empty.grid(row=previous_row + 1, column=previous_column + 2, columnspan=1) empty.config(bg=background_colour, fg=label_font_colour) def create_api_info(self, master, previous_row=-1, previous_column=-1): api_key_lbl = Label(master, text="API Key:") api_key_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=1, sticky=E, padx=(3, 0)) api_key_lbl.config(bg=background_colour, fg=label_font_colour) self.api_key_entry = Entry(master, highlightthickness=0, bd=0, width=21, show="*") self.api_key_entry.config(borderwidth=2, relief=default_relief) self.api_key_entry.grid(row=previous_row + 1, column=previous_column + 2) api_secret_lbl = Label(master, text="API Secret:") api_secret_lbl.grid(row=previous_row + 2, column=previous_column + 1, columnspan=1, sticky=E, padx=(3, 0)) api_secret_lbl.config(bg=background_colour, fg=label_font_colour) self.api_secret_entry = Entry(master, highlightthickness=0, bd=0, width=21, show="*") self.api_secret_entry.config(borderwidth=2, relief=default_relief) self.api_secret_entry.grid(row=previous_row + 2, column=previous_column + 2) self.api_connect_btn = Button(master, text="Connect To Binance", command=self.on_connect_api) self.api_connect_btn.grid(row=previous_row + 3, column=previous_column + 2, columnspan=1, sticky=W + E, padx=10, pady=(0, 3)) self.api_connect_btn.config(highlightbackground=background_colour) def create_auto_sell(self, master, previous_row=-1, previous_column=-1): auto_sell_lbl = Label(master, text="Auto Sell (%):") auto_sell_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=1, sticky=E, padx=(3, 0)) auto_sell_lbl.config(bg=background_colour, fg=label_font_colour) self.auto_sell_spinbox = Spinbox(master, from_=1.0, to=300.0, increment=1.0, highlightbackground=background_colour) self.auto_sell_spinbox.config(borderwidth=2, relief=default_relief) self.auto_sell_spinbox.grid(row=previous_row + 1, column=previous_column + 2) self.auto_sell_spinbox.delete(0, "end") self.auto_sell_spinbox.insert(0, 50) def create_stop_loss(self, master, previous_row=-1, previous_column=-1): stop_loss_lbl = Label(master, text="Stop Loss (%):") stop_loss_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=1, sticky=E, padx=(3, 0)) stop_loss_lbl.config(bg=background_colour, fg=label_font_colour) self.stop_loss_spinbox = Spinbox(master, from_=-100.0, to=-10.0, increment=1.0, highlightbackground=background_colour) self.stop_loss_spinbox.config(borderwidth=2, relief=default_relief) self.stop_loss_spinbox.grid(row=previous_row + 1, column=previous_column + 2) self.stop_loss_spinbox.delete(0, "end") self.stop_loss_spinbox.insert(0, -10) def create_btc_balance_picker(self, master, previous_row=-1, previous_column=-1): self.btc_balance_str = StringVar() btc_balance_lbl = Label(master, textvar=self.btc_balance_str) btc_balance_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=2, sticky=W + E, padx=(3, 0)) btc_balance_lbl.config(bg=background_colour, fg=label_font_colour) self.set_available_btc_balance(Decimal(0)) btc_to_use_label = Label(master, text="BTC to spend:", bg=background_colour, fg=label_font_colour) btc_to_use_label.grid(row=previous_row + 2, column=previous_column + 1, sticky=E, padx=(3, 0)) self.btc_to_use_spinbox = Spinbox( master, from_=minimum_trade, to=minimum_trade, increment=btc_to_use_increment, highlightbackground=background_colour) self.btc_to_use_spinbox.config(borderwidth=2, relief=default_relief) self.btc_to_use_spinbox.grid(row=previous_row + 2, column=previous_column + 2) def create_order_type(self, master, previous_row=-1, previous_column=-1): order_type_lbl = Label(master, text="Entry Type:") order_type_lbl.grid(row=previous_row + 1, column=previous_column + 1, sticky=E, padx=(3, 0)) order_type_lbl.config(bg=background_colour, fg=label_font_colour) self.is_entry_market = True def change_order_type(*args): self.is_entry_market = (self.order_type.get() == "Market Buy \/") self.order_type = StringVar() self.order_type.trace( "w", change_order_type ) # Reduces how much work is done when the pump starts choices = {"Market Buy \/", "Limit Buy \/"} self.entry_type_option_menu = OptionMenu(master, self.order_type, *choices) self.entry_type_option_menu.grid(row=previous_row + 1, column=previous_column + 2, sticky=W + E, padx=8) self.entry_type_option_menu.config(highlightthickness=0) self.entry_type_option_menu.configure(indicatoron=0) self.order_type.set("Market Buy \/") def create_fee_type(self, master, previous_row=-1, previous_column=-1): fee_type_lbl = Label(master, text="Fee Type:") fee_type_lbl.grid(row=previous_row + 1, column=previous_column + 1, sticky=E, padx=(3, 0)) fee_type_lbl.config(bg=background_colour, fg=label_font_colour) self.is_using_bnb = True def change_fee_type(*args): self.is_using_bnb = ( self.order_type.get() == "Binance Coin (BNB) \/") self.fee_type = StringVar() self.fee_type.trace( "w", change_fee_type ) # Reduces how much work is done when the pump starts choices = {"Binance Coin (BNB) \/", "0.1% Of All Trades \/"} self.fee_type_option_menu = OptionMenu(master, self.fee_type, *choices) self.fee_type_option_menu.grid(row=previous_row + 1, column=previous_column + 2, sticky=W + E, padx=8) self.fee_type_option_menu.config(highlightthickness=0) self.fee_type_option_menu.configure(indicatoron=0) self.fee_type.set("Binance Coin (BNB) \/") def create_pump_and_sell_buttons(self, master, previous_row=-1, previous_column=-1): # Manual sell button can only be activated after initiating a pump. self.manual_sell_btn = Button(master, text="Sell", state=DISABLED, command=self.on_manual_sell) self.manual_sell_btn.grid(row=previous_row + 1, column=previous_column + 1, sticky=W + E, padx=(3, 0)) self.manual_sell_btn.config(highlightbackground=background_colour) self.pump_btn = Button(master, text="Pump", command=self.on_pump) self.pump_btn.grid(row=previous_row + 1, column=previous_column + 2, sticky=W + E, padx=8) self.pump_btn.config(highlightbackground=background_colour, state=DISABLED) def create_alt_ticker(self, master, previous_row=-1, previous_column=-1): ticker_lbl = Label(master, text="Ticker To Pump:") ticker_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=1, sticky=E, padx=(3, 0), pady=(0, 8)) ticker_lbl.config(bg=background_colour, fg=label_font_colour) self.ticker_entry = Entry(master, highlightthickness=0, bd=0, width=21) self.ticker_entry.config(borderwidth=2, relief=default_relief) self.ticker_entry.grid(row=previous_row + 1, column=previous_column + 2, pady=8) self.ticker_entry.bind('<Return>', self.on_pump_shortcut) def create_current_profit(self, master, previous_row=-1, previous_column=-1): self.current_profit_str = StringVar() current_profit_lbl = Label(master, textvar=self.current_profit_str) current_profit_lbl.grid(row=previous_row + 1, column=previous_column + 1, columnspan=2, sticky=W + E, padx=3, pady=(0, 3)) current_profit_lbl.config(bg=background_colour, fg=label_font_colour) self.current_profit_str.set("Current Profit: 0%") def create_output_box(self, master, rightmost_column): self.pump_output = StringVar() console_lbl = Label(master, textvar=self.pump_output, borderwidth=2, relief=default_relief, anchor=N) console_lbl.grid(row=0, column=rightmost_column + 1, columnspan=1, rowspan=14, padx=(10, 0), pady=0) console_lbl.config(width=50, height=22, bg="black", font=Font(family="Courier", size=9), fg="white") self.lines = 0 def disable_pre_pump_options(self): # Change the buttons that can be clicked to prevent the user # from trying to pump multiple coins with one bot. self.manual_sell_btn.config(state=NORMAL) self.pump_btn.config(state=DISABLED) self.btc_to_use_spinbox.config(state=DISABLED) self.ticker_entry.config(state=DISABLED) self.auto_sell_spinbox.config(state=DISABLED) self.stop_loss_spinbox.config(state=DISABLED) self.api_key_entry.config( state=DISABLED) # Comment out if hardcoding key self.api_secret_entry.config( state=DISABLED) # Comment out if hardcoding secret self.api_connect_btn.config(state=DISABLED) self.entry_type_option_menu.config(state=DISABLED) self.fee_type_option_menu.config(state=DISABLED) def enable_pump_options(self): # Change the buttons that can be clicked to prevent the user # from trying to pump multiple coins with one bot. self.manual_sell_btn.config(state=DISABLED) self.pump_btn.config(state=NORMAL) self.btc_to_use_spinbox.config(state=NORMAL) self.ticker_entry.config(state=NORMAL) self.auto_sell_spinbox.config(state=NORMAL) self.stop_loss_spinbox.config(state=NORMAL) self.api_key_entry.config( state=NORMAL) # Comment out if hardcoding key self.api_secret_entry.config( state=NORMAL) # Comment out if hardcoding secret self.api_connect_btn.config(state=NORMAL) self.entry_type_option_menu.config(state=NORMAL) self.fee_type_option_menu.config(state=NORMAL) def set_available_btc_balance(self, btc_balance): self.pumper.btc_balance = btc_balance self.btc_balance_str.set("Available Balance: " + readable_btc_balance(btc_balance)) def set_current_profit(self, current_profit): self.current_profit_str.set( "Current Profit: " + '{0:.3f}'.format(round(current_profit * Decimal(100), 3)) + "%") def write_to_console(self, line): self.lines += 1 if self.lines > max_lines_in_console: i = self.pump_output.get().index('\n') self.pump_output.set(self.pump_output.get()[i + 1:] + "\n" + line) elif self.lines == 1: self.pump_output.set(line) else: self.pump_output.set(self.pump_output.get() + "\n" + line) #### Button Behaviour #### def on_pump(self): try: api = self.api btc_to_use = Decimal(self.btc_to_use_spinbox.get()) except InvalidOperation: # The BTC to spend box is empty. self.write_to_console("Stop!") self.write_to_console("BTC to spend cannot be empty.") return except AttributeError: # There is no API object. self.write_to_console( "You need to connect to Binance before pumping.") return if btc_to_use >= minimum_trade: if btc_to_use <= self.pumper.btc_balance: target_profit_percentage = Decimal( float(self.auto_sell_spinbox.get()) / 100.0) # Validate auto-sell and stop loss if target_profit_percentage <= Decimal(0): self.write_to_console("Auto sell has to be positive.") return if Decimal(self.stop_loss_spinbox.get()) >= Decimal(0): self.write_to_console("Stop loss has to be negative.") return ticker = self.ticker_entry.get().upper() # Empty strings are False in Python if ticker: full_ticker = api.full_ticker_for(ticker) try: alt = self.api.get_ticker(symbol=full_ticker) except BinanceAPIException, e: logging.debug(str(e)) self.write_to_console("Invalid ticker.") return alt_value = Decimal(alt["askPrice"]) # Used in console output decimal_points = minimum_decimals_in_quantity.get( full_ticker, 0) self.pumper.decimal_points_in_alt = decimal_points self.pumper.set_up(btc_to_use, target_profit_percentage, alt_value, ticker) if self.is_entry_market: self.pumper.alt_holdings = api.market_buy( self.pumper.btc_to_use, full_ticker, self.is_using_bnb) self.write_to_console( "Bought " + readable_alt_balance( decimal_points, pumper=self.pumper) + " with " + readable_btc_balance(btc_to_use) + ".") else: highest_bid = Decimal(alt["bidPrice"]) if alt_value - highest_bid <= Decimal(0.00000001): to_bid = highest_bid else: # Bid between the highest bid and the lowest ask for the best odds of being filled. to_bid = (alt_value - highest_bid) / 2 + highest_bid to_bid = Decimal( floor(to_bid * Decimal(100000000.0)) ) / Decimal(100000000.0) self.pumper.starting_alt_value = to_bid expected = api.limit_buy( btc_to_alt(btc_to_use, alt_value), pumper, full_ticker, to_bid, self.is_using_bnb) self.write_to_console("Buying " + readable_alt_balance( decimal_points, alt_amount=expected, ticker=ticker ) + " for " + readable_btc_balance(btc_to_use) + ".") self.write_to_console( "This is a limit order, it may not get filled.") self.disable_pre_pump_options() self.set_stop_loss() self.start_monitoring_orderbook(full_ticker) else: # The user is trying to trade with more than they actually have. self.write_to_console("You did not enter a ticker.") else: # The user is trying to trade with more than they actually have. self.write_to_console("Stop!") self.write_to_console( "You are trying to spend more BTC than you have.") else:
class Temperatures(LabelFrame): def __init__(self, root, prtr, settings, log, *arg): LabelFrame.__init__(self, root, *arg, text="Temperatures") self.app = root self.printer = prtr self.settings = settings self.log = log l = Label(self, text="Extruder:", justify=LEFT) l.grid(row=1, column=1, sticky=W) self.statExt = Canvas(self, width=150, height=STAT_HEIGHT, bd=2, bg="white", relief=RIDGE) self.statExt.grid(row=1, column=2, columnspan=2) self.setStatus(self.statExt, UNKNOWN) self.bSetExt = Button(self, text="Set", width=4, command=self.doSetExt) self.bSetExt.grid(row=2, column=1, padx=2) self.spExtTemp = Spinbox(self, values=self.formatTemps(self.settings.extemps), width=12, justify=RIGHT) self.spExtTemp.grid(row=2, column=2) if self.settings.lastexttemp != None: self.spExtTemp.delete(0, END) self.spExtTemp.insert(0, self.settings.lastexttemp) self.spExtTemp.bind("<FocusOut>", self.valExtTemp) self.spExtTemp.bind("<Leave>", self.valExtTemp) self.bOffExt = Button(self, text="Off", width=4, command=self.doOffExt) self.bOffExt.grid(row=2, column=3, padx=2) self.frameExt = Frame(self) self.frameExt.grid(row=3, column=1, columnspan=3) self.legendExt = Canvas(self.frameExt, width=LEGEND_WIDTH, height=100, bd=2, bg="white", relief=RIDGE) self.legendExt.pack(side=LEFT, padx=0, pady=2) self.canvasExt = Canvas(self.frameExt, width=200, height=100, bd=2, bg="white", relief=RIDGE) self.canvasExt.pack(side=LEFT, padx=0, pady=2) self.drawExtAxes() self.extFan = IntVar() if self.settings.fanwithextruder: self.extFan.set(1) else: self.extFan.set(0) self.cb = Checkbutton(self, text="Fan on with extruder", variable=self.extFan, command=self.fanCheck) self.cb.grid(row=4, column=1, columnspan=3) self.forceExtTemp = IntVar() if self.settings.forceexttemp: self.forceExtTemp.set(1) else: self.forceExtTemp.set(0) self.forceExtTempCheck() self.bForceExt = Checkbutton(self, text="Force Ext Temp", variable=self.forceExtTemp, command=self.forceExtTempCheck) self.bForceExt.grid(row=5, column=1, columnspan=3) l = Label(self, text="Bed:", justify=LEFT) l.grid(row=6, column=1, sticky=W) self.statBed = Canvas(self, width=150, height=STAT_HEIGHT, bd=2, bg="white", relief=RIDGE) self.statBed.grid(row=6, column=2, columnspan=2) self.setStatus(self.statBed, UNKNOWN) self.bSetBed = Button(self, text="Set", width=4, command=self.doSetBed) self.bSetBed.grid(row=7, column=1, padx=2) self.spBedTemp = Spinbox(self, values=self.formatTemps(self.settings.bedtemps), width=12, justify=RIGHT) self.spBedTemp.grid(row=7, column=2) if self.settings.lastbedtemp != None: self.spBedTemp.delete(0, END) self.spBedTemp.insert(0, self.settings.lastbedtemp) self.spBedTemp.bind("<FocusOut>", self.valBedTemp) self.spBedTemp.bind("<Leave>", self.valBedTemp) self.bOffBed = Button(self, text="Off", width=4, command=self.doOffBed) self.bOffBed.grid(row=7, column=3, padx=2) self.frameBed = Frame(self) self.frameBed.grid(row=8, column=1, columnspan=3) self.legendBed = Canvas(self.frameBed, width=LEGEND_WIDTH, height=100, bd=2, bg="white", relief=RIDGE) self.legendBed.pack(side=LEFT, padx=0, pady=2) self.canvasBed = Canvas(self.frameBed, width=200, height=100, bd=2, bg="white", relief=RIDGE) self.canvasBed.pack(side=LEFT, padx=0, pady=2) self.drawBedAxes() self.dataBed = [] self.dataExt = [] self.forceBedTemp = IntVar() if self.settings.forcebedtemp: self.forceBedTemp.set(1) else: self.forceBedTemp.set(0) self.forceBedTempCheck() self.bForceBed = Checkbutton(self, text="Force Bed Temp", variable=self.forceBedTemp, command=self.forceBedTempCheck) self.bForceBed.grid(row=9, column=1, columnspan=3) self.monTemp = IntVar() self.monTemp.set(1) self.monCheck() self.cb = Checkbutton(self, text="Monitor temperatures", variable=self.monTemp, command=self.monCheck) self.cb.grid(row=10, column=1, columnspan=3) self.rptre = re.compile("ok *T:([0-9\.]+) *\/([0-9\.]+) *B:([0-9\.]+) *\/([0-9\.]+)") def fanCheck(self): self.settings.fanwithextruder = (self.extFan.get() == 1) self.settings.setModified() def forceBedTempCheck(self): if self.forceBedTemp.get() == 1: if not self.settings.forcebedtemp: self.settings.forcebedtemp = True self.settings.setModified() else: if self.settings.forcebedtemp: self.settings.forcebedtemp = False self.settings.setModified() def forceExtTempCheck(self): if self.forceExtTemp.get() == 1: if not self.settings.forceexttemp: self.settings.forceexttemp = True self.settings.setModified() else: if self.settings.forceexttemp: self.settings.forceexttemp = False self.settings.setModified() def monCheck(self): if self.monTemp.get() == 1: self.app.monitorTemp = True self.printer.tempcb = self.tempcb else: self.app.monitorTemp = False self.dataBed = [] self.canvasBed.delete("GRAPH") self.canvasBed.delete("TARGET") self.dataExt = [] self.canvasExt.delete("GRAPH") self.canvasExt.delete("TARGET") self.printer.tempcb = None self.setStatus(self.statExt, UNKNOWN) self.setStatus(self.statBed, UNKNOWN) def tempcb(self, l): m = self.rptre.search(l) t = m.groups() if len(t) != 4: return self.app.exttemp = float(t[0]) if self.settings.forceexttemp: tp = float(t[1]) if tp >= 0.05 and tp < self.app.exttarget: self.log.logMsg("Asserting hot end temperature of %.2f" % self.app.exttarget) self.printer.send_now("M104 S%s" % self.app.exttarget) else: self.app.exttarget = tp else: self.app.exttarget = float(t[1]) self.app.bedtemp = float(t[2]) if self.settings.forcebedtemp: tp = float(t[3]) if tp >= 0.05 and tp < self.app.bedtarget: self.log.logMsg("Asserting bed temperature of %.2f" % self.app.bedtarget) self.printer.send_now("M140 S%s" % self.app.bedtarget) else: self.app.bedtarget = tp else: self.app.bedtarget = float(t[3]) self.updateTempDisplay(float(t[2]), float(t[0])) def updateTempDisplay(self, tBed, tExt): self.addBedDatapoint(tBed) self.addExtDatapoint(tExt) if self.app.exttarget < 0.05: # unit is off if self.app.exttemp > 35: self.setStatus(self.statExt, COOLING) else: self.setStatus(self.statExt, COOL) else: # unit is on if self.app.exttarget - self.app.exttemp < 0.05: self.setStatus(self.statExt, HOT) else: self.setStatus(self.statExt, HEATING) if self.app.bedtarget < 0.05: # unit is off if self.app.bedtemp > 35: self.setStatus(self.statBed, COOLING) else: self.setStatus(self.statBed, COOL) else: # unit is on if self.app.bedtarget - self.app.bedtemp < 0.05: self.setStatus(self.statBed, HOT) else: self.setStatus(self.statBed, HEATING) def formatTemps(self, t): def cmptemp(a, b): ta = int(a.split()[0]) tb = int(b.split()[0]) return cmp(ta, tb) result = [x for x in t] return sorted(result, cmptemp) def doSetExt(self): if self.app.printerAvailable(cmd="M104"): if self.settings.fanwithextruder and self.app.FanSpeed != 255: if self.settings.forcefanspeed: self.logger.logMsg("Asserting fan speed of %d" % self.app.FanSpeed) else: self.app.toolbar.setFanSpeed(255) temp = self.spExtTemp.get().split(' ')[0] self.printer.send_now("M104 S%s" % temp) self.app.exttarget = float(temp) def valExtTemp(self, *arg): invalid = False try: choice = self.spExtTemp.get() x = int(choice.split(' ')[0]) except: self.log.logMsg("Value for Extruder temperature not a valid integer") invalid = True else: if x <=0 or x >MAXEXTTEMP: self.log.logMsg("Value for Extruder temperature out of range(0-%d" % MAXEXTTEMP) invalid = True if invalid: self.spExtTemp.delete(0, END) self.spExtTemp.insert(0, "%s" % self.formatTemps(self.settings.extemps)[0]) else: if choice not in self.settings.extemps: self.settings.extemps.append(choice) self.settings.setModified() self.spExtTemp.config(values=self.formatTemps(self.settings.extemps)) self.spExtTemp.delete(0, END) self.spExtTemp.insert(0, choice) if self.settings.lastexttemp != choice: self.settings.lastexttemp = choice self.settings.setModified() return True def doOffExt(self, *arg): if self.app.printerAvailable(cmd="M104"): if self.settings.fanwithextruder and self.app.FanSpeed != 0: if self.settings.forcefanspeed: self.logger.logMsg("Asserting fan speed of %d" % self.app.FanSpeed) else: self.app.toolbar.setFanSpeed(0) self.printer.send_now("M104 S0") def drawExtAxes(self): self.canvasExt.delete("AXES") for i in range(0, MAXEXTTEMP, 50): y = 100.0-(float(i)/MAXEXTTEMP*100.0) self.canvasExt.create_line(0, y, 200, y, fill="#C0C0C0", tags="AXES") self.legendExt.create_text(LEGEND_WIDTH, y+7, anchor=SE, tags="AXES", text="%3d" % i) def addExtDatapoint(self, t): c = (t/MAXEXTTEMP)*100.0 self.dataExt.append(c) while len(self.dataExt) >= MAXDATAPOINTS: del(self.dataExt[0]) self.canvasExt.delete("TARGET") self.canvasExt.delete("GRAPH") target = int((self.app.exttarget/MAXEXTTEMP)*100.0) if target != 0: self.canvasExt.create_line(0, 100-target, 200, 100-target, fill="blue", tags="TARGET") self.canvasExt.create_text(10, 100-target, anchor=SW, tags="TARGET", fill="blue", text="%3.3d" % int(self.app.exttarget)) start = MAXDATAPOINTS - len(self.dataExt) pTemp = 0 for i in range(start, MAXDATAPOINTS): if i != start: self.canvasExt.create_line(i*5, 100-pTemp, (i+1)*5, 100-self.dataExt[i-start], fill="red", tags="GRAPH") pTemp = self.dataExt[i-start] self.canvasExt.create_text(190, 100-c, anchor=SE, tags="GRAPH", fill="red", text="%3.3d" % int(t)) def setStatus(self, w, status): if status == UNKNOWN: c = "black" s = "??" elif status == HEATING: c = "orange" s = "heating" elif status == HOT: c = "red" s = "hot" elif status == COOLING: c = "blue" s = "cooling" elif status == COOL: c = "green" s = "cool" else: c = "black" s = "??" w.config(bg=c) w.delete(ALL) w.create_text(75, STAT_HEIGHT-5, text=s, fill="white") def doSetBed(self): if self.app.printerAvailable(cmd="M140"): temp = self.spBedTemp.get().split(' ')[0] self.printer.send_now("M140 S%s" % temp) self.app.bedtarget = float(temp) def valBedTemp(self, *arg): invalid = False try: choice = self.spBedTemp.get() x = int(choice.split(' ')[0]) except: self.log.logMsg("Value for Bed temperature not a valid integer") invalid = True else: if x <=0 or x >MAXEXTTEMP: self.log.logMsg("Value for Bed temperature out of range(0-%d" % MAXBEDTEMP) invalid = True if invalid: self.spBedTemp.delete(0, END) self.spBedTemp.insert(0, "%s" % self.formatTemps(self.settings.bedtemps)[0]) else: if choice not in self.settings.bedtemps: self.settings.bedtemps.append(choice) self.settings.setModified() self.spBedTemp.config(values=self.formatTemps(self.settings.bedtemps)) self.spBedTemp.delete(0, END) self.spBedTemp.insert(0, choice) if self.settings.lastbedtemp != choice: self.settings.lastbedtemp = choice self.settings.setModified() return True def doOffBed(self, *arg): if self.app.printerAvailable(cmd="M140"): self.printer.send_now("M140 S0") def addBedDatapoint(self, t): c = (t/MAXBEDTEMP)*100.0 self.dataBed.append(c) while len(self.dataBed) >= MAXDATAPOINTS: del(self.dataBed[0]) self.canvasBed.delete("TARGET") self.canvasBed.delete("GRAPH") target = int((self.app.bedtarget/MAXBEDTEMP)*100.0) if target != 0: self.canvasBed.create_line(0, 100-target, 200, 100-target, fill="blue", tags="TARGET") self.canvasBed.create_text(10, 100-target, anchor=SW, tags="TARGET", fill="blue", text="%3.3d" % int(self.app.bedtarget)) start = MAXDATAPOINTS - len(self.dataBed) pTemp = 0 for i in range(start, MAXDATAPOINTS): if i != start: self.canvasBed.create_line((i-1)*5, 100-pTemp, i*5, 100-self.dataBed[i-start], fill="red", tags="GRAPH") pTemp = self.dataBed[i-start] self.canvasBed.create_text(190, 100-c, anchor=SE, tags="GRAPH", fill="red", text="%3.3d" % int(t)) def drawBedAxes(self): self.canvasBed.delete("AXES") for i in range(0, MAXBEDTEMP, 50): y = 100.0-(float(i)/MAXBEDTEMP*100.0) self.canvasBed.create_line(0, y, 200, y, fill="#C0C0C0", tags="AXES") self.legendBed.create_text(LEGEND_WIDTH, y+7, anchor=SE, tags="AXES", text="%3d" % i)
class MacroEdit(Toplevel): def __init__(self, root, prtr, settings, log, fn, text, *arg): Toplevel.__init__(self, root, *arg) self.title("Macro Editor") self.fn = fn self.app = root self.settings = settings self.printer = prtr self.log = log self.macroList = self.settings.getMacroList() self.macroEntry = None if fn == None: title = "New macro" self.macroTitle = None self.newMacro = True else: self.newMacro = False self.macroTitle = title = os.path.basename(fn) for m in self.macroList: if self.macroTitle == m[MNAME]: self.macroEntry = m break self.f = LabelFrame(self, text=title) self.entry = Text(self.f, width=80, height=24, relief=RIDGE, bd=2) self.entry.grid(row=1, column=1) self.f.grid(row=1, column=1, columnspan=6) self.bSave = Button(self, text="Save", width=20, command=self.doSave) self.bSave.grid(row=2, column=1) self.bExit = Button(self, text="Exit", width=20, command=self.doExit) self.bExit.grid(row=2, column=2) self.cbvAddButton = IntVar() self.cbAddButton = Checkbutton(self, text="Add Button", variable=self.cbvAddButton, command=self.doAddButton) self.cbAddButton.grid(row=2, column=3) self.buttonText = Entry(self, width=12) self.buttonText.grid(row=2, column=4) l = Label(self, text="Column:", justify=RIGHT) l.grid(row=2, column=5, sticky=E) self.spCol = Spinbox(self, values=[1,2,3], width=12, justify=RIGHT) self.spCol.grid(row=2, column=6) l = Label(self, text="Row:", justify=RIGHT) l.grid(row=3, column=5, sticky=E) self.spRow = Spinbox(self, values=[1,2,3,4,5,6,7,8,9,10], width=12, justify=RIGHT) self.spRow.grid(row=3, column=6) if self.macroEntry != None: self.cbvAddButton.set(1) self.spRow.delete(0, END) self.spRow.insert(0, self.macroEntry[MROW]) self.spCol.delete(0, END) self.spCol.insert(0, self.macroEntry[MCOL]) self.buttonText.delete(0, END) self.buttonText.insert(0, self.macroEntry[MTEXT]) self.initialButtonInfo = [1, self.macroEntry[MCOL], self.macroEntry[MROW], self.macroEntry[MTEXT]] else: self.cbvAddButton.set(0) self.spRow.delete(0, END) self.spRow.insert(0, 1) self.spCol.delete(0, END) self.spCol.insert(0, 1) self.buttonText.delete(0, END) self.initialButtonInfo = [0, 1, 1, ""] self.doAddButton() self.startText = text self.entry.delete("1.0", END) self.entry.insert(END, self.startText) self.entry.edit_modified(False) self.grab_set() self.app.wait_window(self) def doAddButton(self): if self.cbvAddButton.get() == 1: self.buttonText.config(state=NORMAL) self.spRow.config(state=NORMAL) self.spCol.config(state=NORMAL) else: self.buttonText.config(state=DISABLED) self.spRow.config(state=DISABLED) self.spCol.config(state=DISABLED) def buttonInfoChanged(self): if self.cbvAddButton.get() != self.initialButtonInfo[0]: return True if self.initialButtonInfo[1] != int(self.spCol.get()): return True if self.initialButtonInfo[2] != int(self.spRow.get()): return True return self.initialButtonInfo[3] != self.buttonText.get() def doSave(self): if self.cbvAddButton.get() == 1 and self.buttonInfoChanged(): c = int(self.spCol.get()) r = int(self.spRow.get()) for m in self.macroList: if c == m[MCOL] and r == m[MROW]: if self.newMacro or self.macroTitle != m[MNAME]: showerror("Duplicate Location", "That position is already in use by another macro", parent=self) return buttonInfo = [c, r, self.buttonText.get()] else: buttonInfo = None if self.entry.edit_modified() or self.buttonInfoChanged(): l = self.entry.get("1.0", END).rstrip() if self.app.macroEditSave(self.fn, l, buttonInfo, self): self.entry.edit_modified(False) self.initialButtonInfo[0] = self.cbvAddButton.get() self.initialButtonInfo[1] = int(self.spCol.get()) self.initialButtonInfo[2] = int(self.spRow.get()) self.initialButtonInfo[3] = self.buttonText.get() def doExit(self): if self.entry.edit_modified() or self.buttonInfoChanged(): if not tkMessageBox.askyesno("Exit?", "Are you sure you want to exit and lose changes?", parent=self): return self.app.macroEditExit(self.fn) self.destroy()
class Cockpit(ttkFrame): ''' Remote device GUI ''' #TODO: 20160415 DPM - Set these values from configuration file #--- config THROTTLE_BY_USER = True THROTTLE_RESOLUTION = 0.1 # Joystick enabled or not, if any JOYSTICK_ENABLED = True DEFAULT_DRONE_IP = "192.168.1.130" DEFAULT_DRONE_PORT = 2121 #--- end config KEY_ANG_SPEED = "ang-speed" KEY_ANGLES = "angles" KEY_ACCEL = "accel" PID_KEYS = ["P", "I", "D"] DIR_NONE = 0 DIR_VERTICAL = 1 DIR_HORIZONTAL = 2 def __init__(self, parent, isDummy = False, droneIp = DEFAULT_DRONE_IP, dronePort = DEFAULT_DRONE_PORT): ''' Constructor ''' ttkFrame.__init__(self, parent) self._target = [0.0] * 4 self._selectedPidConstats = "--" self._pidConstants = { Cockpit.KEY_ANG_SPEED:{ "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Z":{ "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ANGLES: { "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ACCEL:{ "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Z":{ "P": 0.0, "I": 0.0, "D": 0.0 } } } self.parent = parent self.initUI() self._controlKeysLocked = False if not isDummy: self._link = INetLink(droneIp, dronePort) else: self._link = ConsoleLink() self._link.open() self._updateInfoThread = Thread(target=self._updateInfo) self._updateInfoThreadRunning = False self._readingState = False self._start() def initUI(self): self.parent.title("Drone control") self.style = Style() self.style.theme_use("default") self.pack(fill=BOTH, expand=1) self.parent.bind_all("<Key>", self._keyDown) self.parent.bind_all("<KeyRelease>", self._keyUp) if system() == "Linux": self.parent.bind_all("<Button-4>", self._onMouseWheelUp) self.parent.bind_all("<Button-5>", self._onMouseWheelDown) else: #case of Windows self.parent.bind_all("<MouseWheel>", self._onMouseWheel) #Commands commandsFrame = tkFrame(self) commandsFrame.grid(column=0, row=0, sticky="WE") self._started = IntVar() self._startedCB = Checkbutton(commandsFrame, text="On", variable=self._started, command=self._startedCBChanged) self._startedCB.pack(side=LEFT, padx=4) # self._integralsCB = Checkbutton(commandsFrame, text="Int.", variable=self._integralsEnabled, \ # command=self._integralsCBChanged, state=DISABLED) # self._integralsCB.pack(side=LEFT, padx=4) self._quitButton = Button(commandsFrame, text="Quit", command=self.exit) self._quitButton.pack(side=LEFT, padx=2, pady=2) # self._angleLbl = Label(commandsFrame, text="Angle") # self._angleLbl.pack(side=LEFT, padx=4) # # self._angleEntry = Entry(commandsFrame, state=DISABLED) # self._angleEntry.pack(side=LEFT) #Info infoFrame = tkFrame(self) infoFrame.grid(column=1, row=1, sticky="NE", padx=4) #Throttle Label(infoFrame, text="Throttle").grid(column=0, row=0, sticky="WE") self._throttleTexts = [StringVar(),StringVar(),StringVar(),StringVar()] Entry(infoFrame, textvariable=self._throttleTexts[3], state=DISABLED, width=5).grid(column=0, row=1) Entry(infoFrame, textvariable=self._throttleTexts[0], state=DISABLED, width=5).grid(column=1, row=1) Entry(infoFrame, textvariable=self._throttleTexts[2], state=DISABLED, width=5).grid(column=0, row=2) Entry(infoFrame, textvariable=self._throttleTexts[1], state=DISABLED, width=5).grid(column=1, row=2) #Angles Label(infoFrame, text="Angles").grid(column=0, row=3, sticky="WE") self._angleTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._angleTexts[index], state=DISABLED, width=5).grid(column=index, row=4) #Accels Label(infoFrame, text="Accels").grid(column=0, row=5, sticky="WE") self._accelTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._accelTexts[index], state=DISABLED, width=5).grid(column=index, row=6) #Speeds Label(infoFrame, text="Speeds").grid(column=0, row=7, sticky="WE") self._speedTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._speedTexts[index], state=DISABLED, width=5).grid(column=index, row=8) #Height Label(infoFrame, text="Height").grid(column=0, row=9, sticky="E") self._heightText = StringVar() Entry(infoFrame, textvariable=self._heightText, state=DISABLED, width=5).grid(column=1, row=9) #Loop rate Label(infoFrame, text="Loop @").grid(column=0, row=10, sticky="E") self._loopRateText = StringVar() Entry(infoFrame, textvariable=self._loopRateText, state=DISABLED, width=5).grid(column=1, row=10) Label(infoFrame, text="Hz").grid(column=2, row=10, sticky="W") #control controlFrame = tkFrame(self) controlFrame.grid(column=0, row=1, sticky="W") self._throttle = DoubleVar() if Cockpit.THROTTLE_BY_USER: self._thrustScale = Scale(controlFrame, orient=VERTICAL, from_=100.0, to=0.0, \ tickinterval=0, variable=self._throttle, resolution=Cockpit.THROTTLE_RESOLUTION, \ length=200, showvalue=1, \ state=DISABLED, command=self._onThrustScaleChanged) else: self._thrustScale = Scale(controlFrame, orient=VERTICAL, from_=100.0, to=-100.0, \ tickinterval=0, variable=self._throttle, \ length=200, showvalue=1, \ state=DISABLED, command=self._onThrustScaleChanged) self._thrustScale.bind("<Double-Button-1>", self._onThrustScaleDoubleButton1, "+") self._thrustScale.grid(column=0) self._shiftCanvas = Canvas(controlFrame, bg="white", height=400, width=400, \ relief=SUNKEN) self._shiftCanvas.bind("<Button-1>", self._onMouseButton1) #self._shiftCanvas.bind("<ButtonRelease-1>", self._onMouseButtonRelease1) self._shiftCanvas.bind("<B1-Motion>", self._onMouseButton1Motion) self._shiftCanvas.bind("<Double-Button-1>", self._onMouseDoubleButton1) self._shiftCanvas.bind("<Button-3>", self._onMouseButton3) #self._shiftCanvas.bind("<ButtonRelease-3>", self._onMouseButtonRelease3) self._shiftCanvas.bind("<B3-Motion>", self._onMouseButton3Motion) self._shiftCanvas.grid(row=0,column=1, padx=2, pady=2) self._shiftCanvas.create_oval(1, 1, 400, 400, outline="#ff0000") self._shiftCanvas.create_line(200, 2, 200, 400, fill="#ff0000") self._shiftCanvas.create_line(2, 200, 400, 200, fill="#ff0000") self._shiftMarker = self._shiftCanvas.create_oval(196, 196, 204, 204, outline="#0000ff", fill="#0000ff") self._yaw = DoubleVar() self._yawScale = Scale(controlFrame, orient=HORIZONTAL, from_=-100.0, to=100.0, \ tickinterval=0, variable=self._yaw, \ length=200, showvalue=1, \ command=self._onYawScaleChanged) self._yawScale.bind("<Double-Button-1>", self._onYawScaleDoubleButton1, "+") self._yawScale.grid(row=1, column=1) self._controlKeyActive = False #PID calibration pidCalibrationFrame = tkFrame(self) pidCalibrationFrame.grid(column=0, row=2, sticky="WE"); self._pidSelected = StringVar() self._pidSelected.set("--") self._pidListBox = OptionMenu(pidCalibrationFrame, self._pidSelected, "--", \ Cockpit.KEY_ANG_SPEED, Cockpit.KEY_ANGLES, Cockpit.KEY_ACCEL, \ command=self._onPidListBoxChanged) self._pidListBox.pack(side=LEFT, padx=2) self._pidListBox.config(width=10) self._axisSelected = StringVar() self._axisSelected.set("--") self._axisListBox = OptionMenu(pidCalibrationFrame, self._axisSelected, "--", "X", "Y", "Z", \ command=self._onAxisListBoxChanged) self._axisListBox.pack(side=LEFT, padx=2) self._axisListBox.config(state=DISABLED) Label(pidCalibrationFrame, text="P").pack(side=LEFT, padx=(14, 2)) self._pidPString = StringVar() self._pidPString.set("0.00") self._pidPSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidPString, command=self._onPidSpinboxChanged) self._pidPSpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="I").pack(side=LEFT, padx=(14, 2)) self._pidIString = StringVar() self._pidIString.set("0.00") self._pidISpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidIString, command=self._onPidSpinboxChanged) self._pidISpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="D").pack(side=LEFT, padx=(14, 2)) self._pidDString = StringVar() self._pidDString.set("0.00") self._pidDSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidDString, command=self._onPidSpinboxChanged) self._pidDSpinbox.pack(side=LEFT, padx=2) #debug debugFrame = tkFrame(self) debugFrame.grid(column=0, row=3, sticky="WE") self._debugMsg = Message(debugFrame, anchor="nw", justify=LEFT, relief=SUNKEN, width=300) self._debugMsg.pack(fill=BOTH, expand=1) def _start(self): self._readDroneConfig() if Cockpit.JOYSTICK_ENABLED: self._joystickManager = JoystickManager.getInstance() self._joystickManager.start() joysticks = self._joystickManager.getJoysticks() if len(joysticks) != 0: self._joystick = joysticks[0] self._joystick.onAxisChanged += self._onJoystickAxisChanged self._joystick.onButtonPressed += self._onJoystickButtonPressed else: self._joystick = None def _onJoystickAxisChanged(self, sender, index): if self._started.get() and sender == self._joystick: axisValue = self._joystick.getAxisValue(index) if index == 0: self._yaw.set(axisValue) self._updateTarget() elif index == 1 and not Cockpit.THROTTLE_BY_USER: thrust = -axisValue self._throttle.set(thrust) self._updateTarget() elif index == 2 and Cockpit.THROTTLE_BY_USER: rowThrottle = (axisValue + 100.0)/2.0 if rowThrottle < 10.0: throttle = rowThrottle * 6.0 elif rowThrottle < 90.0: throttle = 60.0 + ((rowThrottle - 10.0) / 8.0) else: throttle = 70.0 + (rowThrottle - 90.0) * 3.0 self._throttle.set(throttle) self._sendThrottle() elif index == 3: x = 196 + axisValue * 2 lastCoords = self._shiftCanvas.coords(self._shiftMarker) coords = (x, lastCoords[1]) self._plotShiftCanvasMarker(coords) elif index == 4: y = 196 + axisValue * 2 lastCoords = self._shiftCanvas.coords(self._shiftMarker) coords = (lastCoords[0], y) self._plotShiftCanvasMarker(coords) def _onJoystickButtonPressed(self, sender, index): if sender == self._joystick and index == 7: if self._started.get() == 0: self._startedCB.select() else: self._startedCB.deselect() # Tkinter's widgets seem not to be calling the event-handler # when they are changed programmatically. Therefore, the # even-handler is called explicitly here. self._startedCBChanged() def exit(self): self._link.send({"key": "close", "data": None}) self._stopUpdateInfoThread() self._link.close() if Cockpit.JOYSTICK_ENABLED: self._joystickManager.stop() self.quit() def _updateTarget(self): markerCoords = self._shiftCanvas.coords(self._shiftMarker) coords = ((markerCoords[0] + markerCoords[2]) / 2, (markerCoords[1] + markerCoords[3]) / 2) self._target[0] = float(coords[1] - 200) / 2.0 # X-axis angle / X-axis acceleration self._target[1] = float(coords[0] - 200) / 2.0 # Y-axis angle / Y-axis acceleration #Remote control uses clockwise angle, but the drone's referece system uses counter-clockwise angle self._target[2] = -self._yaw.get() # Z-axis angular speed # Z-axis acceleration (thrust). Only when the motor throttle is not controlled by user directly if Cockpit.THROTTLE_BY_USER: self._target[3] = 0.0 else: self._target[3] = self._throttle.get() self._sendTarget() def _keyDown(self, event): if event.keysym == "Escape": self._throttle.set(0) self._started.set(0) self._thrustScale.config(state=DISABLED) self._stopUpdateInfoThread() self._sendIsStarted() elif event.keysym.startswith("Control"): self._controlKeyActive = True elif not self._controlKeysLocked and self._controlKeyActive: if event.keysym == "Up": self._thrustScaleUp() elif event.keysym == "Down": self._thrustScaleDown() elif event.keysym == "Left": self._yawLeft() elif event.keysym == "Right": self._yawRight() elif event.keysym == "space": self._yawReset() if not Cockpit.THROTTLE_BY_USER: self._thrustReset() elif not self._controlKeysLocked and not self._controlKeyActive: if event.keysym == "Up": self._moveShiftCanvasMarker((0,-5)) elif event.keysym == "Down": self._moveShiftCanvasMarker((0,5)) elif event.keysym == "Left": self._moveShiftCanvasMarker((-5,0)) elif event.keysym == "Right": self._moveShiftCanvasMarker((5,0)) elif event.keysym == "space": self._resetShiftCanvasMarker() def _keyUp(self, eventArgs): if eventArgs.keysym.startswith("Control"): self._controlKeyActive = False def _onMouseButton1(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseButtonRelease1(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) def _limitCoordsToSize(self, coords, size, width): maxSize = size-(width/2.0) minSize = -(width/2.0) if coords[0] > maxSize: x = maxSize elif coords[0] < minSize: x = minSize else: x = coords[0] if coords[1] > maxSize: y = maxSize elif coords[1] < minSize: y = minSize else: y = coords[1] return (x,y) def _plotShiftCanvasMarker(self, coords): coords = self._limitCoordsToSize(coords, 400, 8) self._shiftCanvas.coords(self._shiftMarker, coords[0], coords[1], coords[0] + 8, coords[1] + 8) self._updateTarget() def _moveShiftCanvasMarker(self, shift): lastCoords = self._shiftCanvas.coords(self._shiftMarker) newCoords = (lastCoords[0] + shift[0], lastCoords[1] + shift[1]) self._plotShiftCanvasMarker(newCoords) def _resetShiftCanvasMarker(self): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) self._updateTarget() def _onMouseButton1Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseDoubleButton1(self, eventArgs): self._resetShiftCanvasMarker() def _onMouseButton3(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) self._mouseDirection = Cockpit.DIR_NONE def _onMouseButtonRelease3(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) def _onMouseButton3Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) if self._mouseDirection == Cockpit.DIR_NONE: if abs(deltaCoords[0]) > abs(deltaCoords[1]): self._mouseDirection = Cockpit.DIR_HORIZONTAL else: self._mouseDirection = Cockpit.DIR_VERTICAL if self._mouseDirection == Cockpit.DIR_HORIZONTAL: deltaCoords = (deltaCoords[0], 0) else: deltaCoords = (0, deltaCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _thrustScaleUp(self): #TODO: 20160526 DPM: El valor de incremento de aceleraciĆ³n (1.0) puede ser muy alto if self._started.get(): newValue = self._thrustScale.get() \ + (Cockpit.THROTTLE_RESOLUTION if Cockpit.THROTTLE_BY_USER else 1.0) self._thrustScale.set(newValue) self._updateTarget() def _thrustScaleDown(self): #TODO: 20160526 DPM: El valor de decremento de aceleraciĆ³n (1.0) puede ser muy alto if self._started.get(): newValue = self._thrustScale.get() \ - (Cockpit.THROTTLE_RESOLUTION if Cockpit.THROTTLE_BY_USER else 1.0) self._thrustScale.set(newValue) self._updateTarget() def _thrustReset(self): if self._started.get(): self._thrustScale.set(0.0) self._updateTarget() def _onThrustScaleDoubleButton1(self, eventArgs): self._thrustReset() return "break" def _yawRight(self): newValue = self._yaw.get() + 1 self._yaw.set(newValue) self._updateTarget() def _yawLeft(self): newValue = self._yaw.get() - 1 self._yaw.set(newValue) self._updateTarget() def _yawReset(self): self._yaw.set(0) self._updateTarget() def _onMouseWheelUp(self, eventArgs): if not self._controlKeyActive: self._thrustScaleUp() else: self._yawRight() def _onMouseWheelDown(self, eventArgs): if not self._controlKeyActive: self._thrustScaleDown() else: self._yawLeft() def _onMouseWheel(self, eventArgs): factor = eventArgs.delta/(1200.0 if Cockpit.THROTTLE_BY_USER and not self._controlKeyActive else 120.0) if not self._controlKeyActive: if self._started.get(): newValue = self._thrustScale.get() + factor self._thrustScale.set(newValue) self._updateTarget() else: newValue = self._yaw.get() + factor self._yaw.set(newValue) self._updateTarget() def _onYawScaleChanged(self, eventArgs): self._updateTarget() def _onYawScaleDoubleButton1(self, eventArgs): self._yawReset() return "break" def _startedCBChanged(self): if not self._started.get(): self._throttle.set(0) self._thrustScale.config(state=DISABLED) #self._integralsCB.config(state=DISABLED) self._stopUpdateInfoThread() else: self._thrustScale.config(state="normal") #self._integralsCB.config(state="normal") self._startUpdateInfoThread() self._sendIsStarted() # def _integralsCBChanged(self): # # self._link.send({"key": "integrals", "data":self._integralsEnabled.get() != 0}) # def _onThrustScaleChanged(self, eventArgs): if Cockpit.THROTTLE_BY_USER: self._sendThrottle() else: self._updateTarget() def _sendThrottle(self): self._link.send({"key": "throttle", "data": self._throttle.get()}) def _sendTarget(self): self._link.send({"key": "target", "data": self._target}) def _sendIsStarted(self): isStarted = self._started.get() != 0 self._link.send({"key": "is-started", "data": isStarted}) def _sendPidCalibrationData(self): if self._pidSelected.get() != "--" and self._axisSelected.get() != "--": pidData = { "pid": self._pidSelected.get(), "axis": self._axisSelected.get(), "p": float(self._pidPSpinbox.get()), "i": float(self._pidISpinbox.get()), "d": float(self._pidDSpinbox.get())} self._link.send({"key": "pid-calibration", "data": pidData}) def _updatePidCalibrationData(self): pid = self._pidSelected.get() axis = self._axisSelected.get() if pid != "--" and axis != "--": self._pidConstants[pid][axis]["P"] = float(self._pidPSpinbox.get()) self._pidConstants[pid][axis]["I"] = float(self._pidISpinbox.get()) self._pidConstants[pid][axis]["D"] = float(self._pidDSpinbox.get()) def _readDroneConfig(self): self._link.send({"key": "read-drone-config", "data": None}, self._onDroneConfigRead) def _readDroneState(self): if not self._readingState: self._readingState = True self._link.send({"key": "read-drone-state", "data": None}, self._onDroneStateRead) def _readPidConfigItem(self, message, cockpitKey, axises, configKeys): for i in range(len(axises)): for j in range(len(Cockpit.PID_KEYS)): self._pidConstants[cockpitKey][axises[i]][Cockpit.PID_KEYS[j]] = message[configKeys[j]][i] def _onDroneConfigRead(self, message): #TODO Show current configuration within the GUI (at least relevant settings) if message: #Angle-speeds self._readPidConfigItem(message, Cockpit.KEY_ANG_SPEED, ["X", "Y", "Z"], \ [Configuration.PID_ANGLES_SPEED_KP, \ Configuration.PID_ANGLES_SPEED_KI, \ Configuration.PID_ANGLES_SPEED_KD]) #Angles self._readPidConfigItem(message, Cockpit.KEY_ANGLES, ["X", "Y"], \ [Configuration.PID_ANGLES_KP, \ Configuration.PID_ANGLES_KI, \ Configuration.PID_ANGLES_KD]) #Accels self._readPidConfigItem(message, Cockpit.KEY_ACCEL, ["X", "Y", "Z"], \ [Configuration.PID_ACCEL_KP, \ Configuration.PID_ACCEL_KI, \ Configuration.PID_ACCEL_KD]) def _onDroneStateRead(self, state): if state: for index in range(4): self._throttleTexts[index].set("{0:.3f}".format(state["_throttles"][index])) for index in range(3): self._accelTexts[index].set("{0:.3f}".format(state["_accels"][index])) self._angleTexts[index].set("{0:.3f}".format(state["_angles"][index])) currentPeriod = state["_currentPeriod"] if currentPeriod > 0.0: freq = 1.0/currentPeriod self._loopRateText.set("{0:.3f}".format(freq)) else: self._loopRateText.set("--") else: self._stopUpdateInfoThread() self._readingState = False def _onPidSpinboxChanged(self): self._updatePidCalibrationData() self._sendPidCalibrationData() def _onPidListBoxChanged(self, pid): self._axisSelected.set("--") self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._selectedPidConstats = pid if pid == "--": self._axisListBox.config(state=DISABLED) self._controlKeysLocked = False else: self._axisListBox.config(state="normal") self._controlKeysLocked = True def _onAxisListBoxChanged(self, axis): if axis == "--" or (self._selectedPidConstats == Cockpit.KEY_ANGLES and axis == "Z"): self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._controlKeysLocked = axis != "--" else: self._pidPString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["P"])) self._pidIString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["I"])) self._pidDString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["D"])) self._pidPSpinbox.config(state="normal") self._pidISpinbox.config(state="normal") self._pidDSpinbox.config(state="normal") self._controlKeysLocked = True def _updateInfo(self): while self._updateInfoThreadRunning: self._readDroneState() time.sleep(1.0) def _startUpdateInfoThread(self): self._updateInfoThreadRunning = True if not self._updateInfoThread.isAlive(): self._updateInfoThread.start() def _stopUpdateInfoThread(self): self._updateInfoThreadRunning = False if self._updateInfoThread.isAlive(): self._updateInfoThread.join()
class Cockpit(ttkFrame): ''' Remote controller GUI ''' KEY_ANG_SPEED = "ang-speed" KEY_ANGLES = "angles" KEY_ACCEL = "accel" PID_KEYS = ["P", "I", "D"] DEFAULT_DRONE_IP = "192.168.1.130" DEFAULT_DRONE_PORT = 2121 DIR_NONE = 0 DIR_VERTICAL = 1 DIR_HORIZONTAL = 2 MAX_ACCEL = 10.0 #TODO angles. Replace by m/sĀ² MAX_ACCEL_Z = 0.1 #m/sĀ² MAX_ANGLE_SPEED = 50.0 #Āŗ/s def __init__(self, parent, isDummy = False, droneIp = DEFAULT_DRONE_IP, dronePort = DEFAULT_DRONE_PORT): ''' Constructor ''' ttkFrame.__init__(self, parent) self._started = IntVar() self._integralsEnabled = IntVar() self._target = [0.0] * 4 self._selectedPidConstats = "--" self._pidConstants = { Cockpit.KEY_ANG_SPEED:{ "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Z":{ "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ANGLES: { "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ACCEL:{ "X":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Y":{ "P": 0.0, "I": 0.0, "D": 0.0 }, "Z":{ "P": 0.0, "I": 0.0, "D": 0.0 } } } self.parent = parent self.initUI() self._controlKeysLocked = False if not isDummy: self._link = INetLink(droneIp, dronePort) else: self._link = ConsoleLink() self._link.open() self._updateInfoThread = Thread(target=self._updateInfo) self._updateInfoThreadRunning = False self._readingState = False self._start() def initUI(self): self.parent.title("Drone control") self.style = Style() self.style.theme_use("default") self.pack(fill=BOTH, expand=1) self.parent.bind_all("<Key>", self._keyDown) self.parent.bind_all("<KeyRelease>", self._keyUp) if system() == "Linux": self.parent.bind_all("<Button-4>", self._onMouseWheelUp) self.parent.bind_all("<Button-5>", self._onMouseWheelDown) else: #case of Windows self.parent.bind_all("<MouseWheel>", self._onMouseWheel) #Commands commandsFrame = tkFrame(self) commandsFrame.grid(column=0, row=0, sticky="WE") self._startedCB = Checkbutton(commandsFrame, text="On", variable=self._started, command=self._startedCBChanged) self._startedCB.pack(side=LEFT, padx=4) self._integralsCB = Checkbutton(commandsFrame, text="Int.", variable=self._integralsEnabled, \ command=self._integralsCBChanged, state=DISABLED) self._integralsCB.pack(side=LEFT, padx=4) self._quitButton = Button(commandsFrame, text="Quit", command=self.exit) self._quitButton.pack(side=LEFT, padx=2, pady=2) # self._angleLbl = Label(commandsFrame, text="Angle") # self._angleLbl.pack(side=LEFT, padx=4) # # self._angleEntry = Entry(commandsFrame, state=DISABLED) # self._angleEntry.pack(side=LEFT) #Info infoFrame = tkFrame(self) infoFrame.grid(column=1, row=1, sticky="E", padx=4) #Throttle Label(infoFrame, text="Throttle").grid(column=0, row=0, sticky="WE") self._throttleTexts = [StringVar(),StringVar(),StringVar(),StringVar()] Entry(infoFrame, textvariable=self._throttleTexts[3], state=DISABLED, width=5).grid(column=0, row=1) Entry(infoFrame, textvariable=self._throttleTexts[0], state=DISABLED, width=5).grid(column=1, row=1) Entry(infoFrame, textvariable=self._throttleTexts[2], state=DISABLED, width=5).grid(column=0, row=2) Entry(infoFrame, textvariable=self._throttleTexts[1], state=DISABLED, width=5).grid(column=1, row=2) #Angles Label(infoFrame, text="Angles").grid(column=0, row=3, sticky="WE") self._angleTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._angleTexts[index], state=DISABLED, width=5).grid(column=index, row=4) #Accels Label(infoFrame, text="Accels").grid(column=0, row=5, sticky="WE") self._accelTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._accelTexts[index], state=DISABLED, width=5).grid(column=index, row=6) #Speeds Label(infoFrame, text="Speeds").grid(column=0, row=7, sticky="WE") self._speedTexts = [StringVar(),StringVar(),StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._speedTexts[index], state=DISABLED, width=5).grid(column=index, row=8) #Height Label(infoFrame, text="Height").grid(column=0, row=9, sticky="W") self._heightText = StringVar() Entry(infoFrame, state=DISABLED, width=5).grid(column=1, row=9) #control controlFrame = tkFrame(self) controlFrame.grid(column=0, row=1, sticky="W") self._throttle = DoubleVar() self._thrustScale = Scale(controlFrame, orient=VERTICAL, from_=100.0, to=-100.0, \ tickinterval=0, variable=self._throttle, \ length=200, showvalue=1, \ state=DISABLED, command=self._onThrustScaleChanged) self._thrustScale.bind("<Double-Button-1>", self._onThrustScaleDoubleButton1, "+") self._thrustScale.grid(column=0) self._shiftCanvas = Canvas(controlFrame, bg="white", height=400, width=400, \ relief=SUNKEN) self._shiftCanvas.bind("<Button-1>", self._onMouseButton1) #self._shiftCanvas.bind("<ButtonRelease-1>", self._onMouseButtonRelease1) self._shiftCanvas.bind("<B1-Motion>", self._onMouseButton1Motion) self._shiftCanvas.bind("<Double-Button-1>", self._onMouseDoubleButton1) self._shiftCanvas.bind("<Button-3>", self._onMouseButton3) #self._shiftCanvas.bind("<ButtonRelease-3>", self._onMouseButtonRelease3) self._shiftCanvas.bind("<B3-Motion>", self._onMouseButton3Motion) self._shiftCanvas.grid(row=0,column=1, padx=2, pady=2) self._shiftCanvas.create_oval(2, 2, 400, 400, outline="#ff0000") self._shiftCanvas.create_line(201, 2, 201, 400, fill="#ff0000") self._shiftCanvas.create_line(2, 201, 400, 201, fill="#ff0000") self._shiftMarker = self._shiftCanvas.create_oval(197, 197, 205, 205, outline="#0000ff", fill="#0000ff") self._yaw = DoubleVar() self._yawScale = Scale(controlFrame, orient=HORIZONTAL, from_=-100.0, to=100.0, \ tickinterval=0, variable=self._yaw, \ length=200, showvalue=1, \ command=self._onYawScaleChanged) self._yawScale.bind("<Double-Button-1>", self._onYawScaleDoubleButton1, "+") self._yawScale.grid(row=1, column=1) self._controlKeyActive = False #PID calibration pidCalibrationFrame = tkFrame(self) pidCalibrationFrame.grid(column=0, row=2, sticky="WE"); self._pidSelected = StringVar() self._pidSelected.set("--") self._pidListBox = OptionMenu(pidCalibrationFrame, self._pidSelected, "--", \ Cockpit.KEY_ANG_SPEED, Cockpit.KEY_ANGLES, Cockpit.KEY_ACCEL, \ command=self._onPidListBoxChanged) self._pidListBox.pack(side=LEFT, padx=2) self._pidListBox.config(width=10) self._axisSelected = StringVar() self._axisSelected.set("--") self._axisListBox = OptionMenu(pidCalibrationFrame, self._axisSelected, "--", "X", "Y", "Z", \ command=self._onAxisListBoxChanged) self._axisListBox.pack(side=LEFT, padx=2) self._axisListBox.config(state=DISABLED) Label(pidCalibrationFrame, text="P").pack(side=LEFT, padx=(14, 2)) self._pidPString = StringVar() self._pidPString.set("0.00") self._pidPSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=100.0, increment=0.01, state=DISABLED, \ textvariable=self._pidPString, command=self._onPidSpinboxChanged) self._pidPSpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="I").pack(side=LEFT, padx=(14, 2)) self._pidIString = StringVar() self._pidIString.set("0.00") self._pidISpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=100.0, increment=0.01, state=DISABLED, \ textvariable=self._pidIString, command=self._onPidSpinboxChanged) self._pidISpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="D").pack(side=LEFT, padx=(14, 2)) self._pidDString = StringVar() self._pidDString.set("0.00") self._pidDSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=100.0, increment=0.01, state=DISABLED, \ textvariable=self._pidDString, command=self._onPidSpinboxChanged) self._pidDSpinbox.pack(side=LEFT, padx=2) #debug debugFrame = tkFrame(self) debugFrame.grid(column=0, row=3, sticky="WE") self._debugMsg = Message(debugFrame, anchor="nw", justify=LEFT, relief=SUNKEN, width=300) self._debugMsg.pack(fill=BOTH, expand=1) def _start(self): self._readDroneConfig() def exit(self): self._link.send({"key": "close", "data": None}) self._stopUpdateInfoThread() self._link.close() self.quit() def _updateTarget(self): markerCoords = self._shiftCanvas.coords(self._shiftMarker) coords = ((markerCoords[0] + markerCoords[2]) / 2, (markerCoords[1] + markerCoords[3]) / 2) self._target[1] = float(coords[0] - 201) * Cockpit.MAX_ACCEL / 200.0 self._target[0] = float(coords[1] - 201) * Cockpit.MAX_ACCEL / 200.0 #Remote control uses clockwise angle, but the drone's referece system uses counter-clockwise angle self._target[2] = -self._yaw.get() * Cockpit.MAX_ANGLE_SPEED / 100.0 self._target[3] = self._throttle.get() * Cockpit.MAX_ACCEL_Z / 100.0 self._sendTarget() def _keyDown(self, event): if event.keysym == "Escape": self._throttle.set(0) self._started.set(0) self._thrustScale.config(state=DISABLED) self._stopUpdateInfoThread() self._sendIsStarted() elif event.keysym.startswith("Control"): self._controlKeyActive = True elif not self._controlKeysLocked and self._controlKeyActive: if event.keysym == "Up": self._thrustScaleUp() elif event.keysym == "Down": self._thrustScaleDown() elif event.keysym == "Left": self._yawLeft() elif event.keysym == "Right": self._yawRight() elif event.keysym == "space": self._yawReset() self._thrustReset() elif not self._controlKeysLocked and not self._controlKeyActive: if event.keysym == "Up": self._moveShiftCanvasMarker((0,-5)) elif event.keysym == "Down": self._moveShiftCanvasMarker((0,5)) elif event.keysym == "Left": self._moveShiftCanvasMarker((-5,0)) elif event.keysym == "Right": self._moveShiftCanvasMarker((5,0)) elif event.keysym == "space": self._resetShiftCanvasMarker() def _keyUp(self, eventArgs): if eventArgs.keysym.startswith("Control"): self._controlKeyActive = False def _onMouseButton1(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseButtonRelease1(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 197, 197, 205, 205) def _limitCoordsToSize(self, coords, size): if coords[0] > size: x = size elif coords[0] < 0: x = 0 else: x = coords[0] if coords[1] > size: y = size elif coords[1] < 0: y = 0 else: y = coords[1] return (x,y) def _moveShiftCanvasMarker(self, shift): lastCoords = self._shiftCanvas.coords(self._shiftMarker) newCoords = (lastCoords[0] + shift[0], lastCoords[1] + shift[1]) newCoords = self._limitCoordsToSize(newCoords, 400) self._shiftCanvas.coords(self._shiftMarker, newCoords[0], newCoords[1], newCoords[0] + 8, newCoords[1] + 8) self._updateTarget() def _resetShiftCanvasMarker(self): self._shiftCanvas.coords(self._shiftMarker, 197, 197, 205, 205) self._updateTarget() def _onMouseButton1Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseDoubleButton1(self, eventArgs): self._resetShiftCanvasMarker() def _onMouseButton3(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) self._mouseDirection = Cockpit.DIR_NONE def _onMouseButtonRelease3(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 197, 197, 205, 205) def _onMouseButton3Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) if self._mouseDirection == Cockpit.DIR_NONE: if abs(deltaCoords[0]) > abs(deltaCoords[1]): self._mouseDirection = Cockpit.DIR_HORIZONTAL else: self._mouseDirection = Cockpit.DIR_VERTICAL if self._mouseDirection == Cockpit.DIR_HORIZONTAL: deltaCoords = (deltaCoords[0], 0) else: deltaCoords = (0, deltaCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _thrustScaleUp(self): if self._started.get(): newValue = self._thrustScale.get() + 1 self._thrustScale.set(newValue) self._updateTarget() def _thrustScaleDown(self): if self._started.get(): newValue = self._thrustScale.get() - 1 self._thrustScale.set(newValue) self._updateTarget() def _thrustReset(self): if self._started.get(): self._thrustScale.set(0.0) self._updateTarget() def _onThrustScaleDoubleButton1(self, eventArgs): self._thrustReset() return "break" def _yawRight(self): newValue = self._yaw.get() + 1 self._yaw.set(newValue) self._updateTarget() def _yawLeft(self): newValue = self._yaw.get() - 1 self._yaw.set(newValue) self._updateTarget() def _yawReset(self): self._yaw.set(0) self._updateTarget() def _onMouseWheelUp(self, eventArgs): if not self._controlKeyActive: self._thrustScaleUp() else: self._yawRight() def _onMouseWheelDown(self, eventArgs): if not self._controlKeyActive: self._thrustScaleDown() else: self._yawLeft() def _onMouseWheel(self, eventArgs): factor = int(eventArgs.delta/120) if not self._controlKeyActive: if self._started.get(): newValue = self._thrustScale.get() + factor self._thrustScale.set(newValue) self._updateTarget() else: newValue = self._yaw.get() + factor self._yaw.set(newValue) self._updateTarget() def _onYawScaleChanged(self, eventArgs): self._updateTarget() def _onYawScaleDoubleButton1(self, eventArgs): self._yawReset() return "break" def _startedCBChanged(self): if not self._started.get(): self._throttle.set(0) self._thrustScale.config(state=DISABLED) self._integralsCB.config(state=DISABLED) self._stopUpdateInfoThread() else: self._thrustScale.config(state="normal") self._integralsCB.config(state="normal") self._startUpdateInfoThread() self._sendIsStarted() def _integralsCBChanged(self): self._link.send({"key": "integrals", "data":self._integralsEnabled.get() != 0}) def _onThrustScaleChanged(self, eventArgs): self._updateTarget() def _sendTarget(self): self._link.send({"key": "target", "data": self._target}) def _sendIsStarted(self): isStarted = self._started.get() != 0 self._link.send({"key": "is-started", "data": isStarted}) def _sendPidCalibrationData(self): if self._pidSelected.get() != "--" and self._axisSelected.get() != "--": pidData = { "pid": self._pidSelected.get(), "axis": self._axisSelected.get(), "p": float(self._pidPSpinbox.get()), "i": float(self._pidISpinbox.get()), "d": float(self._pidDSpinbox.get())} self._link.send({"key": "pid-calibration", "data": pidData}) def _updatePidCalibrationData(self): pid = self._pidSelected.get() axis = self._axisSelected.get() if pid != "--" and axis != "--": self._pidConstants[pid][axis]["P"] = float(self._pidPSpinbox.get()) self._pidConstants[pid][axis]["I"] = float(self._pidISpinbox.get()) self._pidConstants[pid][axis]["D"] = float(self._pidDSpinbox.get()) def _readDroneConfig(self): self._link.send({"key": "read-drone-config", "data": None}, self._onDroneConfigRead) def _readDroneState(self): if not self._readingState: self._readingState = True self._link.send({"key": "read-drone-state", "data": None}, self._onDroneStateRead) def _readPidConfigItem(self, message, cockpitKey, axises, configKeys): for i in range(len(axises)): for j in range(len(Cockpit.PID_KEYS)): self._pidConstants[cockpitKey][axises[i]][Cockpit.PID_KEYS[j]] = message[configKeys[j]][i] def _onDroneConfigRead(self, message): #TODO Show current configuration within the GUI (at least relevant settings) if message: #Angle-speeds self._readPidConfigItem(message, Cockpit.KEY_ANG_SPEED, ["X", "Y", "Z"], \ [Configuration.PID_ANGLES_SPEED_KP, \ Configuration.PID_ANGLES_SPEED_KI, \ Configuration.PID_ANGLES_SPEED_KD]) #Angles self._readPidConfigItem(message, Cockpit.KEY_ANGLES, ["X", "Y"], \ [Configuration.PID_ANGLES_KP, \ Configuration.PID_ANGLES_KI, \ Configuration.PID_ANGLES_KD]) #Accels self._readPidConfigItem(message, Cockpit.KEY_ACCEL, ["X", "Y", "Z"], \ [Configuration.PID_ACCEL_KP, \ Configuration.PID_ACCEL_KI, \ Configuration.PID_ACCEL_KD]) def _onDroneStateRead(self, state): if state: for index in range(4): self._throttleTexts[index].set("{0:.3f}".format(state["_throttles"][index])) for index in range(3): self._accelTexts[index].set("{0:.3f}".format(state["_accels"][index])) self._angleTexts[index].set("{0:.3f}".format(state["_angles"][index])) else: self._stopUpdateInfoThread() self._readingState = False def _onPidSpinboxChanged(self): self._updatePidCalibrationData() self._sendPidCalibrationData() def _onPidListBoxChanged(self, pid): self._axisSelected.set("--") self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._selectedPidConstats = pid if pid == "--": self._axisListBox.config(state=DISABLED) self._controlKeysLocked = False else: self._axisListBox.config(state="normal") self._controlKeysLocked = True def _onAxisListBoxChanged(self, axis): if axis == "--" or (self._selectedPidConstats == Cockpit.KEY_ANGLES and axis == "Z"): self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._controlKeysLocked = axis != "--" else: self._pidPString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["P"])) self._pidIString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["I"])) self._pidDString.set("{:.2f}".format(self._pidConstants[self._selectedPidConstats][axis]["D"])) self._pidPSpinbox.config(state="normal") self._pidISpinbox.config(state="normal") self._pidDSpinbox.config(state="normal") self._controlKeysLocked = True def _updateInfo(self): while self._updateInfoThreadRunning: self._readDroneState() time.sleep(1.0) def _startUpdateInfoThread(self): self._updateInfoThreadRunning = True if not self._updateInfoThread.isAlive(): self._updateInfoThread.start() def _stopUpdateInfoThread(self): self._updateInfoThreadRunning = False if self._updateInfoThread.isAlive(): self._updateInfoThread.join()
class Cockpit(ttkFrame): ''' Remote device GUI ''' #TODO: 20160415 DPM - Set these values from configuration file #--- config THROTTLE_BY_USER = True THROTTLE_RESOLUTION = 0.1 # Joystick enabled or not, if any JOYSTICK_ENABLED = True DEFAULT_DRONE_IP = "192.168.1.130" DEFAULT_DRONE_PORT = 2121 #--- end config KEY_ANG_SPEED = "ang-speed" KEY_ANGLES = "angles" KEY_ACCEL = "accel" PID_KEYS = ["P", "I", "D"] DIR_NONE = 0 DIR_VERTICAL = 1 DIR_HORIZONTAL = 2 def __init__(self, parent, isDummy=False, droneIp=DEFAULT_DRONE_IP, dronePort=DEFAULT_DRONE_PORT): ''' Constructor ''' ttkFrame.__init__(self, parent) self._target = [0.0] * 4 self._selectedPidConstats = "--" self._pidConstants = { Cockpit.KEY_ANG_SPEED: { "X": { "P": 0.0, "I": 0.0, "D": 0.0 }, "Y": { "P": 0.0, "I": 0.0, "D": 0.0 }, "Z": { "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ANGLES: { "X": { "P": 0.0, "I": 0.0, "D": 0.0 }, "Y": { "P": 0.0, "I": 0.0, "D": 0.0 } }, Cockpit.KEY_ACCEL: { "X": { "P": 0.0, "I": 0.0, "D": 0.0 }, "Y": { "P": 0.0, "I": 0.0, "D": 0.0 }, "Z": { "P": 0.0, "I": 0.0, "D": 0.0 } } } self.parent = parent self.initUI() self._controlKeysLocked = False if not isDummy: self._link = INetLink(droneIp, dronePort) else: self._link = ConsoleLink() self._link.open() self._updateInfoThread = Thread(target=self._updateInfo) self._updateInfoThreadRunning = False self._readingState = False self._start() def initUI(self): self.parent.title("Drone control") self.style = Style() self.style.theme_use("default") self.pack(fill=BOTH, expand=1) self.parent.bind_all("<Key>", self._keyDown) self.parent.bind_all("<KeyRelease>", self._keyUp) if system() == "Linux": self.parent.bind_all("<Button-4>", self._onMouseWheelUp) self.parent.bind_all("<Button-5>", self._onMouseWheelDown) else: #case of Windows self.parent.bind_all("<MouseWheel>", self._onMouseWheel) #Commands commandsFrame = tkFrame(self) commandsFrame.grid(column=0, row=0, sticky="WE") self._started = IntVar() self._startedCB = Checkbutton(commandsFrame, text="On", variable=self._started, command=self._startedCBChanged) self._startedCB.pack(side=LEFT, padx=4) # self._integralsCB = Checkbutton(commandsFrame, text="Int.", variable=self._integralsEnabled, \ # command=self._integralsCBChanged, state=DISABLED) # self._integralsCB.pack(side=LEFT, padx=4) self._quitButton = Button(commandsFrame, text="Quit", command=self.exit) self._quitButton.pack(side=LEFT, padx=2, pady=2) # self._angleLbl = Label(commandsFrame, text="Angle") # self._angleLbl.pack(side=LEFT, padx=4) # # self._angleEntry = Entry(commandsFrame, state=DISABLED) # self._angleEntry.pack(side=LEFT) #Info infoFrame = tkFrame(self) infoFrame.grid(column=1, row=1, sticky="NE", padx=4) #Throttle Label(infoFrame, text="Throttle").grid(column=0, row=0, sticky="WE") self._throttleTexts = [ StringVar(), StringVar(), StringVar(), StringVar() ] Entry(infoFrame, textvariable=self._throttleTexts[3], state=DISABLED, width=5).grid(column=0, row=1) Entry(infoFrame, textvariable=self._throttleTexts[0], state=DISABLED, width=5).grid(column=1, row=1) Entry(infoFrame, textvariable=self._throttleTexts[2], state=DISABLED, width=5).grid(column=0, row=2) Entry(infoFrame, textvariable=self._throttleTexts[1], state=DISABLED, width=5).grid(column=1, row=2) #Angles Label(infoFrame, text="Angles").grid(column=0, row=3, sticky="WE") self._angleTexts = [StringVar(), StringVar(), StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._angleTexts[index], state=DISABLED, width=5).grid(column=index, row=4) #Accels Label(infoFrame, text="Accels").grid(column=0, row=5, sticky="WE") self._accelTexts = [StringVar(), StringVar(), StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._accelTexts[index], state=DISABLED, width=5).grid(column=index, row=6) #Speeds Label(infoFrame, text="Speeds").grid(column=0, row=7, sticky="WE") self._speedTexts = [StringVar(), StringVar(), StringVar()] for index in range(3): Entry(infoFrame, textvariable=self._speedTexts[index], state=DISABLED, width=5).grid(column=index, row=8) #Height Label(infoFrame, text="Height").grid(column=0, row=9, sticky="E") self._heightText = StringVar() Entry(infoFrame, textvariable=self._heightText, state=DISABLED, width=5).grid(column=1, row=9) #Loop rate Label(infoFrame, text="Loop @").grid(column=0, row=10, sticky="E") self._loopRateText = StringVar() Entry(infoFrame, textvariable=self._loopRateText, state=DISABLED, width=5).grid(column=1, row=10) Label(infoFrame, text="Hz").grid(column=2, row=10, sticky="W") #control controlFrame = tkFrame(self) controlFrame.grid(column=0, row=1, sticky="W") self._throttle = DoubleVar() if Cockpit.THROTTLE_BY_USER: self._thrustScale = Scale(controlFrame, orient=VERTICAL, from_=100.0, to=0.0, \ tickinterval=0, variable=self._throttle, resolution=Cockpit.THROTTLE_RESOLUTION, \ length=200, showvalue=1, \ state=DISABLED, command=self._onThrustScaleChanged) else: self._thrustScale = Scale(controlFrame, orient=VERTICAL, from_=100.0, to=-100.0, \ tickinterval=0, variable=self._throttle, \ length=200, showvalue=1, \ state=DISABLED, command=self._onThrustScaleChanged) self._thrustScale.bind("<Double-Button-1>", self._onThrustScaleDoubleButton1, "+") self._thrustScale.grid(column=0) self._shiftCanvas = Canvas(controlFrame, bg="white", height=400, width=400, \ relief=SUNKEN) self._shiftCanvas.bind("<Button-1>", self._onMouseButton1) #self._shiftCanvas.bind("<ButtonRelease-1>", self._onMouseButtonRelease1) self._shiftCanvas.bind("<B1-Motion>", self._onMouseButton1Motion) self._shiftCanvas.bind("<Double-Button-1>", self._onMouseDoubleButton1) self._shiftCanvas.bind("<Button-3>", self._onMouseButton3) #self._shiftCanvas.bind("<ButtonRelease-3>", self._onMouseButtonRelease3) self._shiftCanvas.bind("<B3-Motion>", self._onMouseButton3Motion) self._shiftCanvas.grid(row=0, column=1, padx=2, pady=2) self._shiftCanvas.create_oval(1, 1, 400, 400, outline="#ff0000") self._shiftCanvas.create_line(200, 2, 200, 400, fill="#ff0000") self._shiftCanvas.create_line(2, 200, 400, 200, fill="#ff0000") self._shiftMarker = self._shiftCanvas.create_oval(196, 196, 204, 204, outline="#0000ff", fill="#0000ff") self._yaw = DoubleVar() self._yawScale = Scale(controlFrame, orient=HORIZONTAL, from_=-100.0, to=100.0, \ tickinterval=0, variable=self._yaw, \ length=200, showvalue=1, \ command=self._onYawScaleChanged) self._yawScale.bind("<Double-Button-1>", self._onYawScaleDoubleButton1, "+") self._yawScale.grid(row=1, column=1) self._controlKeyActive = False #PID calibration pidCalibrationFrame = tkFrame(self) pidCalibrationFrame.grid(column=0, row=2, sticky="WE") self._pidSelected = StringVar() self._pidSelected.set("--") self._pidListBox = OptionMenu(pidCalibrationFrame, self._pidSelected, "--", \ Cockpit.KEY_ANG_SPEED, Cockpit.KEY_ANGLES, Cockpit.KEY_ACCEL, \ command=self._onPidListBoxChanged) self._pidListBox.pack(side=LEFT, padx=2) self._pidListBox.config(width=10) self._axisSelected = StringVar() self._axisSelected.set("--") self._axisListBox = OptionMenu(pidCalibrationFrame, self._axisSelected, "--", "X", "Y", "Z", \ command=self._onAxisListBoxChanged) self._axisListBox.pack(side=LEFT, padx=2) self._axisListBox.config(state=DISABLED) Label(pidCalibrationFrame, text="P").pack(side=LEFT, padx=(14, 2)) self._pidPString = StringVar() self._pidPString.set("0.00") self._pidPSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidPString, command=self._onPidSpinboxChanged) self._pidPSpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="I").pack(side=LEFT, padx=(14, 2)) self._pidIString = StringVar() self._pidIString.set("0.00") self._pidISpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidIString, command=self._onPidSpinboxChanged) self._pidISpinbox.pack(side=LEFT, padx=2) Label(pidCalibrationFrame, text="D").pack(side=LEFT, padx=(14, 2)) self._pidDString = StringVar() self._pidDString.set("0.00") self._pidDSpinbox = Spinbox(pidCalibrationFrame, width=5, from_=0.0, to=10000.0, increment=0.01, state=DISABLED, \ textvariable=self._pidDString, command=self._onPidSpinboxChanged) self._pidDSpinbox.pack(side=LEFT, padx=2) #debug debugFrame = tkFrame(self) debugFrame.grid(column=0, row=3, sticky="WE") self._debugMsg = Message(debugFrame, anchor="nw", justify=LEFT, relief=SUNKEN, width=300) self._debugMsg.pack(fill=BOTH, expand=1) def _start(self): self._readDroneConfig() if Cockpit.JOYSTICK_ENABLED: self._joystickManager = JoystickManager.getInstance() self._joystickManager.start() joysticks = self._joystickManager.getJoysticks() if len(joysticks) != 0: self._joystick = joysticks[0] self._joystick.onAxisChanged += self._onJoystickAxisChanged self._joystick.onButtonPressed += self._onJoystickButtonPressed else: self._joystick = None def _onJoystickAxisChanged(self, sender, index): if self._started.get() and sender == self._joystick: axisValue = self._joystick.getAxisValue(index) if index == 0: self._yaw.set(axisValue) self._updateTarget() elif index == 1 and not Cockpit.THROTTLE_BY_USER: thrust = -axisValue self._throttle.set(thrust) self._updateTarget() elif index == 2 and Cockpit.THROTTLE_BY_USER: rowThrottle = (axisValue + 100.0) / 2.0 if rowThrottle < 10.0: throttle = rowThrottle * 6.0 elif rowThrottle < 90.0: throttle = 60.0 + ((rowThrottle - 10.0) / 8.0) else: throttle = 70.0 + (rowThrottle - 90.0) * 3.0 self._throttle.set(throttle) self._sendThrottle() elif index == 3: x = 196 + axisValue * 2 lastCoords = self._shiftCanvas.coords(self._shiftMarker) coords = (x, lastCoords[1]) self._plotShiftCanvasMarker(coords) elif index == 4: y = 196 + axisValue * 2 lastCoords = self._shiftCanvas.coords(self._shiftMarker) coords = (lastCoords[0], y) self._plotShiftCanvasMarker(coords) def _onJoystickButtonPressed(self, sender, index): if sender == self._joystick and index == 7: if self._started.get() == 0: self._startedCB.select() else: self._startedCB.deselect() # Tkinter's widgets seem not to be calling the event-handler # when they are changed programmatically. Therefore, the # even-handler is called explicitly here. self._startedCBChanged() def exit(self): self._link.send({"key": "close", "data": None}) self._stopUpdateInfoThread() self._link.close() if Cockpit.JOYSTICK_ENABLED: self._joystickManager.stop() self.quit() def _updateTarget(self): markerCoords = self._shiftCanvas.coords(self._shiftMarker) coords = ((markerCoords[0] + markerCoords[2]) / 2, (markerCoords[1] + markerCoords[3]) / 2) self._target[0] = float( coords[1] - 200) / 2.0 # X-axis angle / X-axis acceleration self._target[1] = float( coords[0] - 200) / 2.0 # Y-axis angle / Y-axis acceleration #Remote control uses clockwise angle, but the drone's referece system uses counter-clockwise angle self._target[2] = -self._yaw.get() # Z-axis angular speed # Z-axis acceleration (thrust). Only when the motor throttle is not controlled by user directly if Cockpit.THROTTLE_BY_USER: self._target[3] = 0.0 else: self._target[3] = self._throttle.get() self._sendTarget() def _keyDown(self, event): if event.keysym == "Escape": self._throttle.set(0) self._started.set(0) self._thrustScale.config(state=DISABLED) self._stopUpdateInfoThread() self._sendIsStarted() elif event.keysym.startswith("Control"): self._controlKeyActive = True elif not self._controlKeysLocked and self._controlKeyActive: if event.keysym == "Up": self._thrustScaleUp() elif event.keysym == "Down": self._thrustScaleDown() elif event.keysym == "Left": self._yawLeft() elif event.keysym == "Right": self._yawRight() elif event.keysym == "space": self._yawReset() if not Cockpit.THROTTLE_BY_USER: self._thrustReset() elif not self._controlKeysLocked and not self._controlKeyActive: if event.keysym == "Up": self._moveShiftCanvasMarker((0, -5)) elif event.keysym == "Down": self._moveShiftCanvasMarker((0, 5)) elif event.keysym == "Left": self._moveShiftCanvasMarker((-5, 0)) elif event.keysym == "Right": self._moveShiftCanvasMarker((5, 0)) elif event.keysym == "space": self._resetShiftCanvasMarker() def _keyUp(self, eventArgs): if eventArgs.keysym.startswith("Control"): self._controlKeyActive = False def _onMouseButton1(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseButtonRelease1(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) def _limitCoordsToSize(self, coords, size, width): maxSize = size - (width / 2.0) minSize = -(width / 2.0) if coords[0] > maxSize: x = maxSize elif coords[0] < minSize: x = minSize else: x = coords[0] if coords[1] > maxSize: y = maxSize elif coords[1] < minSize: y = minSize else: y = coords[1] return (x, y) def _plotShiftCanvasMarker(self, coords): coords = self._limitCoordsToSize(coords, 400, 8) self._shiftCanvas.coords(self._shiftMarker, coords[0], coords[1], coords[0] + 8, coords[1] + 8) self._updateTarget() def _moveShiftCanvasMarker(self, shift): lastCoords = self._shiftCanvas.coords(self._shiftMarker) newCoords = (lastCoords[0] + shift[0], lastCoords[1] + shift[1]) self._plotShiftCanvasMarker(newCoords) def _resetShiftCanvasMarker(self): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) self._updateTarget() def _onMouseButton1Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _onMouseDoubleButton1(self, eventArgs): self._resetShiftCanvasMarker() def _onMouseButton3(self, eventArgs): self._lastMouseCoords = (eventArgs.x, eventArgs.y) self._mouseDirection = Cockpit.DIR_NONE def _onMouseButtonRelease3(self, eventArgs): self._shiftCanvas.coords(self._shiftMarker, 196, 196, 204, 204) def _onMouseButton3Motion(self, eventArgs): deltaCoords = (eventArgs.x - self._lastMouseCoords[0], eventArgs.y - self._lastMouseCoords[1]) if self._mouseDirection == Cockpit.DIR_NONE: if abs(deltaCoords[0]) > abs(deltaCoords[1]): self._mouseDirection = Cockpit.DIR_HORIZONTAL else: self._mouseDirection = Cockpit.DIR_VERTICAL if self._mouseDirection == Cockpit.DIR_HORIZONTAL: deltaCoords = (deltaCoords[0], 0) else: deltaCoords = (0, deltaCoords[1]) self._moveShiftCanvasMarker(deltaCoords) self._lastMouseCoords = (eventArgs.x, eventArgs.y) def _thrustScaleUp(self): #TODO: 20160526 DPM: El valor de incremento de aceleraciĆ³n (1.0) puede ser muy alto if self._started.get(): newValue = self._thrustScale.get() \ + (Cockpit.THROTTLE_RESOLUTION if Cockpit.THROTTLE_BY_USER else 1.0) self._thrustScale.set(newValue) self._updateTarget() def _thrustScaleDown(self): #TODO: 20160526 DPM: El valor de decremento de aceleraciĆ³n (1.0) puede ser muy alto if self._started.get(): newValue = self._thrustScale.get() \ - (Cockpit.THROTTLE_RESOLUTION if Cockpit.THROTTLE_BY_USER else 1.0) self._thrustScale.set(newValue) self._updateTarget() def _thrustReset(self): if self._started.get(): self._thrustScale.set(0.0) self._updateTarget() def _onThrustScaleDoubleButton1(self, eventArgs): self._thrustReset() return "break" def _yawRight(self): newValue = self._yaw.get() + 1 self._yaw.set(newValue) self._updateTarget() def _yawLeft(self): newValue = self._yaw.get() - 1 self._yaw.set(newValue) self._updateTarget() def _yawReset(self): self._yaw.set(0) self._updateTarget() def _onMouseWheelUp(self, eventArgs): if not self._controlKeyActive: self._thrustScaleUp() else: self._yawRight() def _onMouseWheelDown(self, eventArgs): if not self._controlKeyActive: self._thrustScaleDown() else: self._yawLeft() def _onMouseWheel(self, eventArgs): factor = eventArgs.delta / (1200.0 if Cockpit.THROTTLE_BY_USER and not self._controlKeyActive else 120.0) if not self._controlKeyActive: if self._started.get(): newValue = self._thrustScale.get() + factor self._thrustScale.set(newValue) self._updateTarget() else: newValue = self._yaw.get() + factor self._yaw.set(newValue) self._updateTarget() def _onYawScaleChanged(self, eventArgs): self._updateTarget() def _onYawScaleDoubleButton1(self, eventArgs): self._yawReset() return "break" def _startedCBChanged(self): if not self._started.get(): self._throttle.set(0) self._thrustScale.config(state=DISABLED) #self._integralsCB.config(state=DISABLED) self._stopUpdateInfoThread() else: self._thrustScale.config(state="normal") #self._integralsCB.config(state="normal") self._startUpdateInfoThread() self._sendIsStarted() # def _integralsCBChanged(self): # # self._link.send({"key": "integrals", "data":self._integralsEnabled.get() != 0}) # def _onThrustScaleChanged(self, eventArgs): if Cockpit.THROTTLE_BY_USER: self._sendThrottle() else: self._updateTarget() def _sendThrottle(self): self._link.send({"key": "throttle", "data": self._throttle.get()}) def _sendTarget(self): self._link.send({"key": "target", "data": self._target}) def _sendIsStarted(self): isStarted = self._started.get() != 0 self._link.send({"key": "is-started", "data": isStarted}) def _sendPidCalibrationData(self): if self._pidSelected.get() != "--" and self._axisSelected.get( ) != "--": pidData = { "pid": self._pidSelected.get(), "axis": self._axisSelected.get(), "p": float(self._pidPSpinbox.get()), "i": float(self._pidISpinbox.get()), "d": float(self._pidDSpinbox.get()) } self._link.send({"key": "pid-calibration", "data": pidData}) def _updatePidCalibrationData(self): pid = self._pidSelected.get() axis = self._axisSelected.get() if pid != "--" and axis != "--": self._pidConstants[pid][axis]["P"] = float(self._pidPSpinbox.get()) self._pidConstants[pid][axis]["I"] = float(self._pidISpinbox.get()) self._pidConstants[pid][axis]["D"] = float(self._pidDSpinbox.get()) def _readDroneConfig(self): self._link.send({ "key": "read-drone-config", "data": None }, self._onDroneConfigRead) def _readDroneState(self): if not self._readingState: self._readingState = True self._link.send({ "key": "read-drone-state", "data": None }, self._onDroneStateRead) def _readPidConfigItem(self, message, cockpitKey, axises, configKeys): for i in range(len(axises)): for j in range(len(Cockpit.PID_KEYS)): self._pidConstants[cockpitKey][axises[i]][ Cockpit.PID_KEYS[j]] = message[configKeys[j]][i] def _onDroneConfigRead(self, message): #TODO Show current configuration within the GUI (at least relevant settings) if message: #Angle-speeds self._readPidConfigItem(message, Cockpit.KEY_ANG_SPEED, ["X", "Y", "Z"], \ [Configuration.PID_ANGLES_SPEED_KP, \ Configuration.PID_ANGLES_SPEED_KI, \ Configuration.PID_ANGLES_SPEED_KD]) #Angles self._readPidConfigItem(message, Cockpit.KEY_ANGLES, ["X", "Y"], \ [Configuration.PID_ANGLES_KP, \ Configuration.PID_ANGLES_KI, \ Configuration.PID_ANGLES_KD]) #Accels self._readPidConfigItem(message, Cockpit.KEY_ACCEL, ["X", "Y", "Z"], \ [Configuration.PID_ACCEL_KP, \ Configuration.PID_ACCEL_KI, \ Configuration.PID_ACCEL_KD]) def _onDroneStateRead(self, state): if state: for index in range(4): self._throttleTexts[index].set("{0:.3f}".format( state["_throttles"][index])) for index in range(3): self._accelTexts[index].set("{0:.3f}".format( state["_accels"][index])) self._angleTexts[index].set("{0:.3f}".format( state["_angles"][index])) currentPeriod = state["_currentPeriod"] if currentPeriod > 0.0: freq = 1.0 / currentPeriod self._loopRateText.set("{0:.3f}".format(freq)) else: self._loopRateText.set("--") else: self._stopUpdateInfoThread() self._readingState = False def _onPidSpinboxChanged(self): self._updatePidCalibrationData() self._sendPidCalibrationData() def _onPidListBoxChanged(self, pid): self._axisSelected.set("--") self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._selectedPidConstats = pid if pid == "--": self._axisListBox.config(state=DISABLED) self._controlKeysLocked = False else: self._axisListBox.config(state="normal") self._controlKeysLocked = True def _onAxisListBoxChanged(self, axis): if axis == "--" or (self._selectedPidConstats == Cockpit.KEY_ANGLES and axis == "Z"): self._pidPString.set("--") self._pidIString.set("--") self._pidDString.set("--") self._pidPSpinbox.config(state=DISABLED) self._pidISpinbox.config(state=DISABLED) self._pidDSpinbox.config(state=DISABLED) self._controlKeysLocked = axis != "--" else: self._pidPString.set("{:.2f}".format( self._pidConstants[self._selectedPidConstats][axis]["P"])) self._pidIString.set("{:.2f}".format( self._pidConstants[self._selectedPidConstats][axis]["I"])) self._pidDString.set("{:.2f}".format( self._pidConstants[self._selectedPidConstats][axis]["D"])) self._pidPSpinbox.config(state="normal") self._pidISpinbox.config(state="normal") self._pidDSpinbox.config(state="normal") self._controlKeysLocked = True def _updateInfo(self): while self._updateInfoThreadRunning: self._readDroneState() time.sleep(1.0) def _startUpdateInfoThread(self): self._updateInfoThreadRunning = True if not self._updateInfoThread.isAlive(): self._updateInfoThread.start() def _stopUpdateInfoThread(self): self._updateInfoThreadRunning = False if self._updateInfoThread.isAlive(): self._updateInfoThread.join()