class GCS: def __init__(self): # No communications or arming yet self.comms = None self.armed = False # Do basic Tk initialization self.root = Tk() self.root.configure(bg=BACKGROUND_COLOR) self.root.resizable(False, False) self.root.title('Hackflight Ground Control Station') left = (self.root.winfo_screenwidth() - DISPLAY_WIDTH) / 2 top = (self.root.winfo_screenheight() - DISPLAY_HEIGHT) / 2 self.root.geometry('%dx%d+%d+%d' % (DISPLAY_WIDTH, DISPLAY_HEIGHT, left, top)) self.frame = Frame(self.root) self.root.wm_iconbitmap(bitmap = "@media/icon.xbm") self.root.tk.call('wm', 'iconphoto', self.root._w, PhotoImage('icon.png')) self.root.protocol('WM_DELETE_WINDOW', self.quit) # Create panes for two rows of widgets self.pane1 = self._add_pane() self.pane2 = self._add_pane() # Add a buttons self.button_connect = self._add_button('Connect', self.pane1, self._connect_callback) self.button_setup = self._add_button('Setup', self.pane2, self._setup_callback) self.button_motors = self._add_button('Motors', self.pane2, self._motors_button_callback) self.button_receiver = self._add_button('Receiver', self.pane2, self._receiver_button_callback) self.button_messages = self._add_button('Messages', self.pane2, self._messages_button_callback) #self.button_maps = self._add_button('Maps', self.pane2, self._maps_button_callback, disabled=False) # Prepare for adding ports as they are detected by our timer task self.portsvar = StringVar(self.root) self.portsmenu = None self.connected = False self.ports = [] # Finalize Tk stuff self.frame.pack() self.canvas = Canvas(self.root, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, background='black') self.canvas.pack() # Add widgets for motor-testing dialog; hide them immediately self.motors = Motors(self) self.motors.stop() # Create receiver dialog self.receiver = Receiver(self) # Create messages dialog self.messages = Messages(self) # Create setup dialog self.setup = Setup(self) self._schedule_connection_task() # Create a maps dialog #self.maps = Maps(self, yoffset=-30) # Create a splash image self.splashimage = PhotoImage(file='media/splash.png') self._show_splash() # Create a message parser self.parser = MSP_Parser() # Set up parser's request strings self.attitude_request = self.parser.serialize_ATTITUDE_Request() self.rc_request = self.parser.serialize_RC_Request() # No messages yet self.yaw_pitch_roll = 0,0,0 self.rxchannels = 0,0,0,0,0 # A hack to support display in Setup dialog self.active_axis = 0 def quit(self): self.motors.stop() self.root.destroy() def hide(self, widget): widget.place(x=-9999) def getChannels(self): return self.rxchannels def getYawPitchRoll(self): # configure button to show connected self._enable_buttons() self.button_connect['text'] = 'Disconnect' self._enable_button(self.button_connect) return self.yaw_pitch_roll def checkArmed(self): if self.armed: self._show_armed(self.root) self._show_armed(self.pane1) self._show_armed(self.pane2) self._disable_button(self.button_motors) else: self._show_disarmed(self.root) self._show_disarmed(self.pane1) self._show_disarmed(self.pane2) def scheduleTask(self, delay_msec, task): self.root.after(delay_msec, task) def _add_pane(self): pane = PanedWindow(self.frame, bg=BACKGROUND_COLOR) pane.pack(fill=BOTH, expand=1) return pane def _add_button(self, label, parent, callback, disabled=True): button = Button(parent, text=label, command=callback) button.pack(side=LEFT) button.config(state = 'disabled' if disabled else 'normal') return button # Callback for Setup button def _setup_callback(self): self._clear() self.motors.stop() self.receiver.stop() self.messages.stop() #self.maps.stop() self.parser.set_ATTITUDE_Handler(self._handle_attitude) self.setup.start() def _start(self): self.parser.set_ATTITUDE_Handler(self._handle_attitude) self._send_attitude_request() self.setup.start() self.parser.set_RC_Handler(self._handle_rc) self._send_rc_request() # Sends Attitude request to FC def _send_attitude_request(self): self.comms.send_request(self.attitude_request) # Sends RC request to FC def _send_rc_request(self): self.comms.send_request(self.rc_request) # Callback for Motors button def _motors_button_callback(self): self._clear() self.setup.stop() self.parser.set_ATTITUDE_Handler(self._handle_attitude) self.receiver.stop() self.messages.stop() #self.maps.stop() self.motors.start() def _clear(self): self.canvas.delete(ALL) # Callback for Receiver button def _receiver_button_callback(self): self._clear() self.setup.stop() self.motors.stop() self.messages.stop() #self.maps.stop() self.receiver.start() # Callback for Messages button def _messages_button_callback(self): self._clear() self.setup.stop() self.motors.stop() #self.maps.stop() self.receiver.stop() self.messages.start() def _getting_messages(self): return self.button_connect['text'] == 'Disconnect' # Callback for Maps button def _maps_button_callback(self): self._clear() if self._getting_messages(): self.receiver.stop() self.messages.stop() self.setup.stop() self.motors.stop() #self.maps.start() # Callback for Connect / Disconnect button def _connect_callback(self): if self.connected: self.setup.stop() #self.maps.stop() self.motors.stop() self.messages.stop() self.receiver.stop() if not self.comms is None: self.comms.stop() self._clear() self._disable_buttons() self.button_connect['text'] = 'Connect' self._show_splash() else: #self.maps.stop() self.comms = Comms(self) self.comms.start() self.button_connect['text'] = 'Connecting ...' self._disable_button(self.button_connect) self._hide_splash() self.scheduleTask(CONNECTION_DELAY_MSEC, self._start) self.connected = not self.connected # Gets available ports def _getports(self): allports = comports() ports = [] for port in allports: portname = port[0] if 'ttyACM' in portname or 'ttyUSB' in portname or 'COM' in portname: ports.append(portname) return ports # Checks for changes in port status (hot-plugging USB cables) def _connection_task(self): ports = self._getports() if ports != self.ports: if self.portsmenu is None: self.portsmenu = OptionMenu(self.pane1, self.portsvar, *ports) else: for port in ports: self.portsmenu['menu'].add_command(label=port) self.portsmenu.pack(side=LEFT) if ports == []: self._disable_button(self.button_connect) self._disable_buttons() else: self.portsvar.set(ports[0]) # default value self._enable_button(self.button_connect) self.ports = ports self._schedule_connection_task() # Mutually recursive with above def _schedule_connection_task(self): self.root.after(USB_UPDATE_MSEC, self._connection_task) def _disable_buttons(self): self._disable_button(self.button_setup) self._disable_button(self.button_motors) self._disable_button(self.button_receiver) self._disable_button(self.button_messages) def _enable_buttons(self): self._enable_button(self.button_setup) self._enable_button(self.button_motors) self._enable_button(self.button_receiver) self._enable_button(self.button_messages) def _enable_button(self, button): button['state'] = 'normal' def _disable_button(self, button): button['state'] = 'disabled' def sendMotorMessage(self, index, value): values = [1000]*4 values[index-1] = value self.comms.send_message(self.parser.serialize_SET_MOTOR, values) def _show_splash(self): self.splash = self.canvas.create_image((400,250), image=self.splashimage) def _hide_splash(self): self.canvas.delete(self.splash) def _show_armed(self, widget): widget.configure(bg='red') def _show_disarmed(self, widget): widget.configure(bg=BACKGROUND_COLOR) if self._getting_messages(): self._enable_button(self.button_motors) def _handle_calibrate_response(self): self.setup.showCalibrated() def _handle_params_response(self, pitchroll_kp_percent, yaw_kp_percent): # Only handle parms from firmware on a fresh connection if self.newconnect: self.setup.setParams(pitchroll_kp_percent, yaw_kp_percent) self.newconnect = False def _handle_attitude(self, x, y, z): self.yaw_pitch_roll = z, -y/10., x/10. self.messages.setCurrentMessage('Yaw/Pitch/Roll: %+3.3f %+3.3f %+3.3f' % self.yaw_pitch_roll) # As soon as we handle the callback from one request, send another request self._send_attitude_request() def _handle_rc(self, c1, c2, c3, c4, c5, c6, c7, c8): self.rxchannels = c1, c2, c3, c4, c5 # As soon as we handle the callback from one request, send another request self._send_rc_request() self.messages.setCurrentMessage('Receiver: %04d %04d %04d %04d %04d' % (c1, c2, c3, c4, c5)) def _handle_arm_status(self, armed): self.armed = armed self.messages.setCurrentMessage('ArmStatus: %s' % ('ARMED' if armed else 'Disarmed')) def _handle_battery_status(self, volts, amps): self.messages.setCurrentMessage('BatteryStatus: %3.3f volts, %3.3f amps' % (volts, amps))
from sys import argv if len(argv) < 2: print('Usage: python3 %s PORT' % argv[0]) print('Example: python3 %s /dev/ttyUSB0' % argv[0]) exit(1) parser = Parser() request = serialize_RC_Request() port = serial.Serial(argv[1], BAUD) def handler(c1, c2, c3, c4, c5, c6, c7, c8): print(c1, c2, c3, c4, c5) port.write(request) parser.set_RC_Handler(handler) port.write(request) while True: try: parser.parse(port.read(1)) except KeyboardInterrupt: break