class AutonomousTester: def __init__(self): self.initialized = False self.init_time = None self.chooser = None self.state = "auto" self.currentChoice = None self.until = None def initialize_chooser(self, tm): if self.chooser is None: self.chooser = ChooserControl("Autonomous Mode") self.choices = self.chooser.getChoices() if len(self.choices) == 0: return False self.state = "disabled" self.currentChoice = -1 self.until = tm self.init_time = tm self.initialized = True return True def on_step(self, tm): if not self.initialized: if not self.initialize_chooser(tm): assert ( tm < 10 ), "Robot didn't create a chooser within 10 seconds, probably an error" return True if self.state == "auto": if tm >= self.until: self.until = tm + 1 self.state = "disabled" control.set_operator_control(enabled=False) elif self.state == "disabled": if tm >= self.until: control.set_autonomous() self.state = "auto" self.until = tm + autonomous_seconds self.currentChoice += 1 if self.currentChoice >= len(self.choices): return False self.chooser.setSelected(self.choices[self.currentChoice]) return True
def initialize_chooser(self, tm): if self.chooser is None: self.chooser = ChooserControl('Autonomous Mode') self.choices = self.chooser.getChoices() if len(self.choices) == 0: return False self.state = 'disabled' self.currentChoice = -1 self.until = tm self.init_time = tm self.initialized = True return True
def test_chooser_control(nt): c = ChooserControl('Autonomous Mode') assert c.getChoices() == () assert c.getSelected() is None c.setSelected("foo") assert c.getSelected() == 'foo' t = nt.getTable('/SmartDashboard/Autonomous Mode') assert t.getString('selected', None) == 'foo' t.putStringArray('options', ('option1', 'option2')) assert c.getChoices() == ('option1', 'option2')
def test_chooser_control(nt): c = ChooserControl("Autonomous Mode", inst=nt) assert c.getChoices() == () assert c.getSelected() is None c.setSelected("foo") assert c.getSelected() == "foo" t = nt.getTable("/SmartDashboard/Autonomous Mode") assert t.getString("selected", None) == "foo" t.putStringArray("options", ("option1", "option2")) assert c.getChoices() == ("option1", "option2")
def test_chooser_control(nt): c = ChooserControl("Autonomous Mode") assert c.getChoices() == () assert c.getSelected() is None c.setSelected("foo") assert c.getSelected() == "foo" t = nt.getTable("/SmartDashboard/Autonomous Mode") assert t.getString("selected", None) == "foo" t.putStringArray("options", ("option1", "option2")) assert c.getChoices() == ("option1", "option2")
def test_chooser_control(nt): c = ChooserControl('Autonomous Mode') assert c.getChoices() == () assert c.getSelected() is None c.setSelected("foo") assert c.getSelected() == 'foo' t = nt.getTable('/SmartDashboard/Autonomous Mode') assert t.getString('selected') == 'foo' t.putStringArray('options', ('option1', 'option2')) assert c.getChoices() == ('option1', 'option2')
def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text="Analog") self.analog = [] for i in range(len(hal_data["analog_in"])): if hal_data["analog_in"][i]["initialized"] or hal_data["analog_out"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, "analog", i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text="Digital") label = tk.Label(slot, text="PWM") label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data["pwm"])): if hal_data["pwm"][i]["initialized"]: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, "pwm", i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text="Digital I/O") label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data["dio"])): if hal_data["dio"][i]["initialized"]: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, "dio", i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text="Relay") label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data["relay"])): if hal_data["relay"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, "relay", i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) csfm = tk.Frame(top) # solenoid slot = tk.LabelFrame(csfm, text="Solenoid") self.solenoids = [] for i in range(len(hal_data["solenoid"])): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, "solenoid", i) self.solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) # CAN self.can_slot = tk.LabelFrame(csfm, text="CAN") self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) self.can_mode_map = { tsrxc.kMode_CurrentCloseLoop: "PercentVbus", tsrxc.kMode_DutyCycle: "PercentVbus", tsrxc.kMode_NoDrive: "Disabled", tsrxc.kMode_PositionCloseLoop: "Position", tsrxc.kMode_SlaveFollower: "Follower", tsrxc.kMode_VelocityCloseLoop: "Speed", tsrxc.kMode_VoltCompen: "Voltage", } self.can = {} # detect new devices for k in sorted(hal_data["CAN"].keys()): self._add_CAN(k, hal_data["CAN"][k]) csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text="Joysticks") self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text="Stick %s" % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(["X", "Y", "Z", "T", "4", "5"]): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, "axes", t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text="POV") label.grid(column=col, row=row) pov = ValueWidget(slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, "buttons", j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text="Time") def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton(timing_control, text="Run", variable=realtime_mode, value=0, command=_set_realtime) button.pack(fill=tk.X) button = tk.Radiobutton(timing_control, text="Pause", variable=realtime_mode, value=1, command=_set_realtime) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text="Step", command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, "Click this to increment time by the step value") Tooltip.create(step_entry, "Time to step (in seconds)") realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text="Robot") self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton( sim, text="Disabled", variable=self.mode, value=self.manager.MODE_DISABLED, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Autonomous", variable=self.mode, value=self.manager.MODE_AUTONOMOUS, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Teleoperated", variable=self.mode, value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text="Test", variable=self.mode, value=self.manager.MODE_TEST, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text="Robot died!", fg="red") sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text="Autonomous") self.autobox = Combobox(auton, state="readonly") self.autobox.bind("<<ComboboxSelected>>", self.on_auton_selected) self.autobox["width"] = 12 self.autobox.pack(fill=tk.X) Tooltip.create(self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box") from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( "Autonomous Mode", lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v), ) auton.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y)
class SimUI(object): def __init__(self, manager, fake_time, config_obj): """ initializes all default values and creates a board, waits for run() to be called to start the board manager - sim manager class instance """ self.manager = manager self.fake_time = fake_time self.config_obj = config_obj # Set up idle_add self.queue = queue.Queue() self.root = tk.Tk() self.root.wm_title("PyFRC Robot Simulator v%s" % __version__) # setup mode switch frame = tk.Frame(self.root) frame.pack(side=tk.TOP, anchor=tk.W) self._setup_widgets(frame) self.root.resizable(width=0, height=0) self.mode_start_tm = 0 self.text_id = None # connect to the controller self.manager.on_mode_change(lambda mode: self.idle_add(self.on_robot_mode_change, mode)) self.on_robot_mode_change(self.manager.get_mode()) # create pygame joystick if supported try: from .pygame_joysticks import UsbJoysticks except ImportError: logger.warn("pygame not detected, real joystick support not loaded") self.usb_joysticks = None else: self.usb_joysticks = UsbJoysticks(self) logger.info("pygame was detected, real joystick support loaded!") try: self.root.lift() self.root.attributes("-topmost", True) self.root.attributes("-topmost", False) except Exception: pass self.timer_fired() def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text="Analog") self.analog = [] for i in range(len(hal_data["analog_in"])): if hal_data["analog_in"][i]["initialized"] or hal_data["analog_out"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, "analog", i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text="Digital") label = tk.Label(slot, text="PWM") label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data["pwm"])): if hal_data["pwm"][i]["initialized"]: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, "pwm", i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text="Digital I/O") label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data["dio"])): if hal_data["dio"][i]["initialized"]: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, "dio", i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text="Relay") label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data["relay"])): if hal_data["relay"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, "relay", i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) csfm = tk.Frame(top) # solenoid slot = tk.LabelFrame(csfm, text="Solenoid") self.solenoids = [] for i in range(len(hal_data["solenoid"])): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, "solenoid", i) self.solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) # CAN self.can_slot = tk.LabelFrame(csfm, text="CAN") self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) self.can_mode_map = { tsrxc.kMode_CurrentCloseLoop: "PercentVbus", tsrxc.kMode_DutyCycle: "PercentVbus", tsrxc.kMode_NoDrive: "Disabled", tsrxc.kMode_PositionCloseLoop: "Position", tsrxc.kMode_SlaveFollower: "Follower", tsrxc.kMode_VelocityCloseLoop: "Speed", tsrxc.kMode_VoltCompen: "Voltage", } self.can = {} # detect new devices for k in sorted(hal_data["CAN"].keys()): self._add_CAN(k, hal_data["CAN"][k]) csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text="Joysticks") self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text="Stick %s" % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(["X", "Y", "Z", "T", "4", "5"]): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, "axes", t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text="POV") label.grid(column=col, row=row) pov = ValueWidget(slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, "buttons", j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text="Time") def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton(timing_control, text="Run", variable=realtime_mode, value=0, command=_set_realtime) button.pack(fill=tk.X) button = tk.Radiobutton(timing_control, text="Pause", variable=realtime_mode, value=1, command=_set_realtime) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text="Step", command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, "Click this to increment time by the step value") Tooltip.create(step_entry, "Time to step (in seconds)") realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text="Robot") self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton( sim, text="Disabled", variable=self.mode, value=self.manager.MODE_DISABLED, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Autonomous", variable=self.mode, value=self.manager.MODE_AUTONOMOUS, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Teleoperated", variable=self.mode, value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text="Test", variable=self.mode, value=self.manager.MODE_TEST, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text="Robot died!", fg="red") sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text="Autonomous") self.autobox = Combobox(auton, state="readonly") self.autobox.bind("<<ComboboxSelected>>", self.on_auton_selected) self.autobox["width"] = 12 self.autobox.pack(fill=tk.X) Tooltip.create(self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box") from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( "Autonomous Mode", lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v), ) auton.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y) def _add_CAN(self, canId, device): row = len(self.can) * 2 lbl = tk.Label(self.can_slot, text=str(canId)) lbl.grid(column=0, row=row) motor = ValueWidget(self.can_slot, default=0.0) motor.grid(column=1, row=row) self.set_tooltip(motor, "CAN", canId) fl = CheckButtonWrapper(self.can_slot, text="F") fl.grid(column=2, row=row) rl = CheckButtonWrapper(self.can_slot, text="R") rl.grid(column=3, row=row) Tooltip.create(fl, "Forward limit switch") Tooltip.create(rl, "Reverse limit switch") mode_lbl_txt = tk.StringVar(value=self.can_mode_map[device["mode_select"]]) mode_label = tk.Label(self.can_slot, textvariable=mode_lbl_txt) mode_label.grid(column=4, row=row) labels = tk.Frame(self.can_slot) labels.grid(column=0, row=row + 1, columnspan=6) enc_value = tk.StringVar(value="E: 0") enc_label = tk.Label(labels, textvariable=enc_value) enc_label.pack(side=tk.LEFT) analog_value = tk.StringVar(value="A: 0") analog_label = tk.Label(labels, textvariable=analog_value) analog_label.pack(side=tk.LEFT) pwm_value = tk.StringVar(value="P: 0") pwm_label = tk.Label(labels, textvariable=pwm_value) pwm_label.pack(side=tk.LEFT) Tooltip.create(enc_label, "Encoder Input") Tooltip.create(analog_label, "Analog Input") Tooltip.create(pwm_label, "PWM Input") self.can[canId] = (motor, fl, rl, mode_lbl_txt, enc_value, analog_value, pwm_value) def idle_add(self, callable, *args): """Call this with a function as the argument, and that function will be called on the GUI thread via an event This function returns immediately """ self.queue.put((callable, args)) def __process_idle_events(self): """This should never be called directly, it is called via an event, and should always be on the GUI thread""" while True: try: callable, args = self.queue.get(block=False) except queue.Empty: break callable(*args) def run(self): # and launch the thread self.root.mainloop() # This call BLOCKS def timer_fired(self): """Polling loop for events from other threads""" self.__process_idle_events() # grab the simulation lock, gather all of the # wpilib objects, and display them on the screen self.update_widgets() # call next timer_fired (or we'll never call timer_fired again!) delay = 100 # milliseconds self.root.after(delay, self.timer_fired) # pause, then call timer_fired again def update_widgets(self): # TODO: support multiple slots? # joystick stuff if self.usb_joysticks is not None: self.usb_joysticks.update() # analog module for i, (ain, aout) in enumerate(zip(hal_data["analog_in"], hal_data["analog_out"])): aio = self.analog[i] if aio is not None: if ain["initialized"]: aio.set_disabled(False) ain["voltage"] = aio.get_value() elif aout["initialized"]: aio.set_value(aout["voltage"]) # digital module for i, ch in enumerate(hal_data["dio"]): dio = self.dio[i] if dio is not None: if not ch["initialized"]: dio.set_disabled() else: # determine which one changed, and set the appropriate one ret = dio.sync_value(ch["value"]) if ret is not None: ch["value"] = ret for i, ch in enumerate(hal_data["pwm"]): pwm = self.pwm[i] if pwm is not None: pwm.set_value(ch["value"]) for i, ch in enumerate(hal_data["relay"]): relay = self.relays[i] if relay is not None: if ch["fwd"]: relay.set_on() elif ch["rev"]: relay.set_back() else: relay.set_off() # solenoid for i, ch in enumerate(hal_data["solenoid"]): sol = self.solenoids[i] if not ch["initialized"]: sol.set_disabled() else: sol.set_value(ch["value"]) # CAN for k, (motor, fl, rl, mode_lbl_txt, enc_txt, analog_txt, pwm_txt) in self.can.items(): can = hal_data["CAN"][k] mode = can["mode_select"] mode_lbl_txt.set(self.can_mode_map[mode]) # change how output works based on control mode if mode == tsrxc.kMode_DutyCycle: # based on the fact that the vbus has 1023 steps motor.set_value(can["value"] / 1023) elif mode == tsrxc.kMode_VoltCompen: # assume voltage is 12 divide by muliplier in cantalon code (256) motor.set_value(can["value"] / 12 / 256) elif mode == tsrxc.kMode_SlaveFollower: # follow the value of the motor value is equal too motor.set_value(self.can[can["value"]][0].get_value()) # # currently other control modes are not correctly implemented # else: motor.set_value(can["value"]) enc_txt.set("E: %s" % can["enc_position"]) analog_txt.set("A: %s" % can["analog_in_position"]) pwm_txt.set("P: %s" % can["pulse_width_position"]) ret = fl.sync_value(can["limit_switch_closed_for"]) if ret is not None: can["limit_switch_closed_for"] = ret ret = rl.sync_value(can["limit_switch_closed_rev"]) if ret is not None: can["limit_switch_closed_rev"] = ret # joystick/driver station # sticks = _core.DriverStation.GetInstance().sticks # stick_buttons = _core.DriverStation.GetInstance().stick_buttons for i, (axes, buttons, povs) in enumerate(self.joysticks): joy = hal_data["joysticks"][i] jaxes = joy["axes"] for j, ax in enumerate(axes): jaxes[j] = ax.get_value() jbuttons = joy["buttons"] for j, (ck, var) in enumerate(buttons): jbuttons[j + 1] = True if var.get() else False jpovs = joy["povs"] for j, pov in enumerate(povs): jpovs[j] = int(pov.get_value()) self.field.update_widgets() tm = self.fake_time.get() mode_tm = tm - self.mode_start_tm self.status.config(text="Time: %.03f mode, %.03f total" % (mode_tm, tm)) def set_tooltip(self, widget, cat, idx): tooltip = self.config_obj["pyfrc"][cat].get(str(idx)) if tooltip is not None: Tooltip.create(widget, tooltip) def set_joy_tooltip(self, widget, idx, typ, idx2): tooltip = self.config_obj["pyfrc"]["joysticks"][str(idx)][typ].get(str(idx2)) if tooltip is not None: Tooltip.create(widget, tooltip) def on_auton_choices(self, choices): self.autobox["values"] = choices[:] def on_auton_selection(self, selection): self.autobox.set(selection) def on_auton_selected(self, e): self.auton_ctrl.setSelected(self.autobox.get()) def on_robot_mode_change(self, mode): self.mode.set(mode) self.mode_start_tm = self.fake_time.get() # this is not strictly true... a robot can actually receive joystick # commands from the driver station in disabled mode. However, most # people aren't going to use that functionality... controls_disabled = False if mode == self.manager.MODE_OPERATOR_CONTROL else True state = tk.DISABLED if controls_disabled else tk.NORMAL for axes, buttons, povs in self.joysticks: for axis in axes: axis.set_disabled(disabled=controls_disabled) for ck, var in buttons: ck.config(state=state) for pov in povs: pov.set_disabled(disabled=controls_disabled) if not self.manager.is_alive(): for button in self.state_buttons: button.config(state=tk.DISABLED) self.robot_dead.pack() # # Time related callbacks # def on_pause(self, pause): if pause: self.fake_time.pause() else: self.fake_time.resume() def on_step_time(self): val = self.step_entry.get() try: tm = float(self.step_entry.get()) except ValueError: tk.messagebox.showerror("Invalid step time", "'%s' is not a valid number" % val) return if tm > 0: self.fake_time.resume(tm)
import time from networktables import NetworkTables from networktables.util import ChooserControl # To see messages from networktables, you must setup logging import logging logging.basicConfig(level=logging.DEBUG) if len(sys.argv) != 2: print("Error: specify an IP to connect to!") exit(0) ip = sys.argv[1] NetworkTables.initialize(server=ip) def on_choices(value): print("OnChoices", value) def on_selected(value): print("OnSelected", value) cc = ChooserControl("Autonomous Mode", on_choices, on_selected) while True: time.sleep(1)
class SimUI(object): def __init__(self, manager, fake_time, config_obj): """ initializes all default values and creates a board, waits for run() to be called to start the board manager - sim manager class instance """ self.manager = manager self.fake_time = fake_time self.config_obj = config_obj # Set up idle_add self.queue = queue.Queue() self.root = tk.Tk() self.root.wm_title("PyFRC Robot Simulator v%s" % __version__) self.root.protocol("WM_DELETE_WINDOW", self._delete_window) # setup mode switch frame = tk.Frame(self.root) frame.pack(side=tk.TOP, anchor=tk.W) self._setup_widgets(frame) self.root.resizable(width=0, height=0) # Allow extending the simulation from 3rd party libraries # -> TODO: better API for this self.extensions = [] for ep in iter_entry_points(group="robotpysim", name=None): try: extension = ep.load() except ImportError: logger.debug("Error importing extension '%s'", ep.name, exc_info=True) else: logger.debug("Loaded simulation extension '%s'", ep.name) extension = extension() if hasattr(extension, "update_tk_widgets"): self.extensions.append(extension) self.mode_start_tm = 0 self.text_id = None # connect to the controller self.manager.on_mode_change( lambda mode: self.idle_add(self.on_robot_mode_change, mode) ) self.on_robot_mode_change(self.manager.get_mode()) # create pygame joystick if supported try: from .pygame_joysticks import UsbJoysticks except ImportError: logger.warn("pygame not detected, real joystick support not loaded") self.usb_joysticks = None else: self.usb_joysticks = UsbJoysticks(self) logger.info("pygame was detected, real joystick support loaded!") try: self.root.lift() self.root.attributes("-topmost", True) self.root.attributes("-topmost", False) except Exception: pass self.timer_fired() def _delete_window(self): self.root.destroy() if self.usb_joysticks is not None: self.usb_joysticks.close() def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text="Analog") self.analog = [] for i in range(len(hal_data["analog_in"])): if ( hal_data["analog_in"][i]["initialized"] or hal_data["analog_out"][i]["initialized"] ): label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, "analog", i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text="Digital") label = tk.Label(slot, text="PWM") label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data["pwm"])): if hal_data["pwm"][i]["initialized"]: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, "pwm", i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text="Digital I/O") label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data["dio"])): if hal_data["dio"][i]["initialized"]: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, "dio", i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text="Relay") label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data["relay"])): if hal_data["relay"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, "relay", i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) self.csfm = csfm = tk.Frame(top) # solenoid (pcm) self.pcm = {} # values self.values = {} # CAN self.can_slot = tk.LabelFrame(csfm, text="CAN") self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) self.can = {} csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text="Joysticks") self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text="Stick %s" % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(["X", "Y", "Z", "T", "4", "5"]): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, "axes", t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text="POV") label.grid(column=col, row=row) pov = ValueWidget( slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True, ) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, "buttons", j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text="Time") def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton( timing_control, text="Run", variable=realtime_mode, value=0, command=_set_realtime, ) button.pack(fill=tk.X) button = tk.Radiobutton( timing_control, text="Pause", variable=realtime_mode, value=1, command=_set_realtime, ) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text="Step", command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, "Click this to increment time by the step value") Tooltip.create(step_entry, "Time to step (in seconds)") realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text="Robot") self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton( sim, text="Disabled", variable=self.mode, value=self.manager.MODE_DISABLED, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Autonomous", variable=self.mode, value=self.manager.MODE_AUTONOMOUS, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Teleoperated", variable=self.mode, value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Test", variable=self.mode, value=self.manager.MODE_TEST, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text="Robot died!", fg="red") sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text="Autonomous") self.autobox = Combobox(auton, state="readonly") self.autobox.bind("<<ComboboxSelected>>", self.on_auton_selected) self.autobox["width"] = 12 self.autobox.pack(fill=tk.X) Tooltip.create( self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box", ) from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( "Autonomous Mode", lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v), ) auton.pack(side=tk.TOP) messages = self.config_obj["pyfrc"]["game_specific_messages"] if messages: gamedata = tk.LabelFrame(ctrl_frame, text="Game Data") self.gamedataval = tk.StringVar() if hasattr(self.gamedataval, "trace_add"): self.gamedataval.trace_add("write", self.on_gamedata_selected) else: self.gamedataval.trace_variable("w", self.on_gamedata_selected) self.gamedatabox = Combobox(gamedata, textvariable=self.gamedataval) self.gamedatabox["width"] = 12 self.gamedatabox.pack(fill=tk.X) self.gamedatabox["values"] = messages self.gamedatabox.current(0) self.manager.game_specific_message = self.gamedatabox.get() Tooltip.create( self.gamedatabox, "Use this selection box to simulate game specific data", ) gamedata.pack(side=tk.TOP) def _reset_robot(): for robot in self.manager.robots: robot.physics_controller.reset_position() button = tk.Button(ctrl_frame, text="Reset Robot", command=_reset_robot) button.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y) def _render_pcm(self): for k, data in sorted(hal_data["pcm"].items()): if k not in self.pcm: slot = tk.LabelFrame(self.csfm, text="Solenoid (PCM %s)" % k) solenoids = [] self.pcm[k] = solenoids for i in range(len(data)): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, "solenoid", i) solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) solenoids = self.pcm[k] for i, ch in enumerate(data): sol = solenoids[i] if not ch["initialized"]: sol.set_disabled() else: sol.set_value(ch["value"]) def _render_values(self): for k, v in hal_data["robot"].items(): if not k.endswith("_angle"): continue gyro_label = self.values.get(k) if not gyro_label: gyro_label = self._create_value(k, k, "Angle (Degrees)") gyro_label["text"] = "%.3f" % v for i, gyro in enumerate(hal_data["analog_gyro"]): if not gyro["initialized"]: continue k = "Gyro %s" % i gyro_label = self.values.get(k) if not gyro_label: gyro_label = self._create_value(k, k, "Angle (Degrees)") gyro_label["text"] = "%.3f" % gyro["angle"] for i, encoder in enumerate(hal_data["encoder"]): if not encoder["initialized"]: continue k = "Encoder %s" % i label = self.values.get(k) if not label: txt = "Encoder (%s,%s)" % ( encoder["config"]["ASource_Channel"], encoder["config"]["BSource_Channel"], ) label = self._create_value(k, txt, "Count / Distance") label["text"] = "%s / %.3f" % ( encoder["count"], encoder["count"] * encoder["distance_per_pulse"], ) for k, v in hal_data.get("custom", {}).items(): label = self.values.get(k) if not label: label = self._create_value(k, k, k) if isinstance(v, float): label["text"] = "%.3f" % v else: label["text"] = str(v) def _create_value(self, key, text, tooltip): slot = tk.LabelFrame(self.csfm, text=text) label = tk.Label(slot) label.pack(side=tk.TOP, fill=tk.BOTH) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) self.values[key] = label Tooltip.create(label, tooltip) return label def idle_add(self, callable, *args): """Call this with a function as the argument, and that function will be called on the GUI thread via an event This function returns immediately """ self.queue.put((callable, args)) def __process_idle_events(self): """This should never be called directly, it is called via an event, and should always be on the GUI thread""" while True: try: callable, args = self.queue.get(block=False) except queue.Empty: break callable(*args) def run(self): # and launch the thread self.root.mainloop() # This call BLOCKS def timer_fired(self): """Polling loop for events from other threads""" self.__process_idle_events() # grab the simulation lock, gather all of the # wpilib objects, and display them on the screen self.update_widgets() # call next timer_fired (or we'll never call timer_fired again!) delay = 100 # milliseconds self.root.after(delay, self.timer_fired) # pause, then call timer_fired again def update_widgets(self): # TODO: support multiple slots? # joystick stuff if self.usb_joysticks is not None: self.usb_joysticks.update() # analog module for i, (ain, aout) in enumerate( zip(hal_data["analog_in"], hal_data["analog_out"]) ): aio = self.analog[i] if aio is not None: if ain["initialized"]: aio.set_disabled(False) ain["voltage"] = aio.get_value() elif aout["initialized"]: aio.set_value(aout["voltage"]) # digital module for i, ch in enumerate(hal_data["dio"]): dio = self.dio[i] if dio is not None: if not ch["initialized"]: dio.set_disabled() else: # determine which one changed, and set the appropriate one ret = dio.sync_value(ch["value"]) if ret is not None: ch["value"] = ret for i, ch in enumerate(hal_data["pwm"]): pwm = self.pwm[i] if pwm is not None: pwm.set_value(ch["value"]) for i, ch in enumerate(hal_data["relay"]): relay = self.relays[i] if relay is not None: if ch["fwd"]: relay.set_on() elif ch["rev"]: relay.set_back() else: relay.set_off() # solenoid self._render_pcm() # gyro/encoder self._render_values() # joystick/driver station # sticks = _core.DriverStation.GetInstance().sticks # stick_buttons = _core.DriverStation.GetInstance().stick_buttons for i, (axes, buttons, povs) in enumerate(self.joysticks): joy = hal_data["joysticks"][i] jaxes = joy["axes"] for j, ax in enumerate(axes): jaxes[j] = ax.get_value() jbuttons = joy["buttons"] for j, (ck, var) in enumerate(buttons): jbuttons[j + 1] = True if var.get() else False jpovs = joy["povs"] for j, pov in enumerate(povs): jpovs[j] = int(pov.get_value()) for extension in self.extensions: extension.update_tk_widgets(self) self.field.update_widgets() tm = self.fake_time.get() mode_tm = tm - self.mode_start_tm self.status.config(text="Time: %.03f mode, %.03f total" % (mode_tm, tm)) def set_tooltip(self, widget, cat, idx): tooltip = self.config_obj["pyfrc"][cat].get(str(idx)) if tooltip is not None: Tooltip.create(widget, tooltip) def set_joy_tooltip(self, widget, idx, typ, idx2): tooltip = self.config_obj["pyfrc"]["joysticks"][str(idx)][typ].get(str(idx2)) if tooltip is not None: Tooltip.create(widget, tooltip) def on_auton_choices(self, choices): self.autobox["values"] = choices[:] def on_auton_selection(self, selection): self.autobox.set(selection) def on_auton_selected(self, e): self.auton_ctrl.setSelected(self.autobox.get()) def on_gamedata_selected(self, *args): self.manager.game_specific_message = self.gamedatabox.get() def on_robot_mode_change(self, mode): self.mode.set(mode) self.mode_start_tm = self.fake_time.get() # this is not strictly true... a robot can actually receive joystick # commands from the driver station in disabled mode. However, most # people aren't going to use that functionality... controls_disabled = ( False if mode == self.manager.MODE_OPERATOR_CONTROL else True ) state = tk.DISABLED if controls_disabled else tk.NORMAL for axes, buttons, povs in self.joysticks: for axis in axes: axis.set_disabled(disabled=controls_disabled) for ck, var in buttons: ck.config(state=state) for pov in povs: pov.set_disabled(disabled=controls_disabled) if not self.manager.is_alive(): for button in self.state_buttons: button.config(state=tk.DISABLED) self.robot_dead.pack() # # Time related callbacks # def on_pause(self, pause): if pause: self.fake_time.pause() else: self.fake_time.resume() def on_step_time(self): val = self.step_entry.get() try: tm = float(self.step_entry.get()) except ValueError: tk.messagebox.showerror( "Invalid step time", "'%s' is not a valid number" % val ) return if tm > 0: self.fake_time.resume(tm)
def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text='Analog') self.analog = [] for i in range(len(hal_data['analog_in'])): if hal_data['analog_in'][i]['initialized'] or hal_data[ 'analog_out'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, 'analog', i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text='Digital') label = tk.Label(slot, text='PWM') label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data['pwm'])): if hal_data['pwm'][i]['initialized']: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, 'pwm', i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text='Digital I/O') label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data['dio'])): if hal_data['dio'][i]['initialized']: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, 'dio', i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text='Relay') label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data['relay'])): if hal_data['relay'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, 'relay', i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) csfm = tk.Frame(top) # solenoid slot = tk.LabelFrame(csfm, text='Solenoid') self.solenoids = [] for i in range(len(hal_data['solenoid'])): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, 'solenoid', i) self.solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) # CAN # self.can_slot = tk.LabelFrame(csfm, text='CAN') # self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) # self.can_mode_map = {} # tsrxc.kMode_CurrentCloseLoop: 'PercentVbus', # tsrxc.kMode_DutyCycle:'PercentVbus', # tsrxc.kMode_NoDrive:'Disabled', # tsrxc.kMode_PositionCloseLoop:'Position', # tsrxc.kMode_SlaveFollower:'Follower', # tsrxc.kMode_VelocityCloseLoop:'Speed', # tsrxc.kMode_VoltCompen:'Voltage' # } self.can = {} # detect new devices # for k in sorted(hal_data['CAN'].keys()): # self._add_CAN(k, hal_data['CAN'][k]) csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text='Joysticks') self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text='Stick %s' % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(['X', 'Y', 'Z', 'T', '4', '5']): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, 'axes', t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text='POV') label.grid(column=col, row=row) pov = ValueWidget(slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, 'buttons', j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text='Time') def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton(timing_control, text='Run', variable=realtime_mode, value=0, command=_set_realtime) button.pack(fill=tk.X) button = tk.Radiobutton(timing_control, text='Pause', variable=realtime_mode, value=1, command=_set_realtime) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text='Step', command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, 'Click this to increment time by the step value') Tooltip.create(step_entry, 'Time to step (in seconds)') realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text='Robot') self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton(sim, text='Disabled', variable=self.mode, \ value=self.manager.MODE_DISABLED, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Autonomous', variable=self.mode, \ value=self.manager.MODE_AUTONOMOUS, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Teleoperated', variable=self.mode, \ value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Test', variable=self.mode, \ value=self.manager.MODE_TEST, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text='Robot died!', fg='red') sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text='Autonomous') self.autobox = Combobox(auton, state='readonly') self.autobox.bind('<<ComboboxSelected>>', self.on_auton_selected) self.autobox['width'] = 12 self.autobox.pack(fill=tk.X) Tooltip.create( self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box" ) from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( 'Autonomous Mode', lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v)) auton.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y)
class SimUI(object): def __init__(self, manager, fake_time, config_obj): ''' initializes all default values and creates a board, waits for run() to be called to start the board manager - sim manager class instance ''' self.manager = manager self.fake_time = fake_time self.config_obj = config_obj # Set up idle_add self.queue = queue.Queue() self.root = tk.Tk() self.root.wm_title("PyFRC Robot Simulator v%s" % __version__) # setup mode switch frame = tk.Frame(self.root) frame.pack(side=tk.TOP, anchor=tk.W) self._setup_widgets(frame) self.root.resizable(width=0, height=0) self.mode_start_tm = 0 self.text_id = None # connect to the controller self.manager.on_mode_change( lambda mode: self.idle_add(self.on_robot_mode_change, mode)) self.on_robot_mode_change(self.manager.get_mode()) # create pygame joystick if supported try: from .pygame_joysticks import UsbJoysticks except ImportError: logger.warn( 'pygame not detected, real joystick support not loaded') self.usb_joysticks = None else: self.usb_joysticks = UsbJoysticks(self) logger.info('pygame was detected, real joystick support loaded!') try: self.root.lift() self.root.attributes('-topmost', True) self.root.attributes('-topmost', False) except Exception: pass self.timer_fired() def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text='Analog') self.analog = [] for i in range(len(hal_data['analog_in'])): if hal_data['analog_in'][i]['initialized'] or hal_data[ 'analog_out'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, 'analog', i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text='Digital') label = tk.Label(slot, text='PWM') label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data['pwm'])): if hal_data['pwm'][i]['initialized']: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, 'pwm', i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text='Digital I/O') label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data['dio'])): if hal_data['dio'][i]['initialized']: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, 'dio', i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text='Relay') label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data['relay'])): if hal_data['relay'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, 'relay', i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) csfm = tk.Frame(top) # solenoid slot = tk.LabelFrame(csfm, text='Solenoid') self.solenoids = [] for i in range(len(hal_data['solenoid'])): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, 'solenoid', i) self.solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) # CAN # self.can_slot = tk.LabelFrame(csfm, text='CAN') # self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) # self.can_mode_map = {} # tsrxc.kMode_CurrentCloseLoop: 'PercentVbus', # tsrxc.kMode_DutyCycle:'PercentVbus', # tsrxc.kMode_NoDrive:'Disabled', # tsrxc.kMode_PositionCloseLoop:'Position', # tsrxc.kMode_SlaveFollower:'Follower', # tsrxc.kMode_VelocityCloseLoop:'Speed', # tsrxc.kMode_VoltCompen:'Voltage' # } self.can = {} # detect new devices # for k in sorted(hal_data['CAN'].keys()): # self._add_CAN(k, hal_data['CAN'][k]) csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text='Joysticks') self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text='Stick %s' % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(['X', 'Y', 'Z', 'T', '4', '5']): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, 'axes', t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text='POV') label.grid(column=col, row=row) pov = ValueWidget(slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, 'buttons', j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text='Time') def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton(timing_control, text='Run', variable=realtime_mode, value=0, command=_set_realtime) button.pack(fill=tk.X) button = tk.Radiobutton(timing_control, text='Pause', variable=realtime_mode, value=1, command=_set_realtime) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text='Step', command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, 'Click this to increment time by the step value') Tooltip.create(step_entry, 'Time to step (in seconds)') realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text='Robot') self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton(sim, text='Disabled', variable=self.mode, \ value=self.manager.MODE_DISABLED, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Autonomous', variable=self.mode, \ value=self.manager.MODE_AUTONOMOUS, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Teleoperated', variable=self.mode, \ value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Test', variable=self.mode, \ value=self.manager.MODE_TEST, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text='Robot died!', fg='red') sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text='Autonomous') self.autobox = Combobox(auton, state='readonly') self.autobox.bind('<<ComboboxSelected>>', self.on_auton_selected) self.autobox['width'] = 12 self.autobox.pack(fill=tk.X) Tooltip.create( self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box" ) from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( 'Autonomous Mode', lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v)) auton.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y) def _add_CAN(self, canId, device): row = len(self.can) * 2 lbl = tk.Label(self.can_slot, text=str(canId)) lbl.grid(column=0, row=row) motor = ValueWidget(self.can_slot, default=0.0) motor.grid(column=1, row=row) self.set_tooltip(motor, 'CAN', canId) fl = CheckButtonWrapper(self.can_slot, text='F') fl.grid(column=2, row=row) rl = CheckButtonWrapper(self.can_slot, text='R') rl.grid(column=3, row=row) Tooltip.create(fl, 'Forward limit switch') Tooltip.create(rl, 'Reverse limit switch') mode_lbl_txt = tk.StringVar( value=self.can_mode_map[device['mode_select']]) mode_label = tk.Label(self.can_slot, textvariable=mode_lbl_txt) mode_label.grid(column=4, row=row) labels = tk.Frame(self.can_slot) labels.grid(column=0, row=row + 1, columnspan=6) enc_value = tk.StringVar(value='E: 0') enc_label = tk.Label(labels, textvariable=enc_value) enc_label.pack(side=tk.LEFT) analog_value = tk.StringVar(value='A: 0') analog_label = tk.Label(labels, textvariable=analog_value) analog_label.pack(side=tk.LEFT) pwm_value = tk.StringVar(value='P: 0') pwm_label = tk.Label(labels, textvariable=pwm_value) pwm_label.pack(side=tk.LEFT) Tooltip.create(enc_label, "Encoder Input") Tooltip.create(analog_label, "Analog Input") Tooltip.create(pwm_label, "PWM Input") self.can[canId] = (motor, fl, rl, mode_lbl_txt, enc_value, analog_value, pwm_value) def idle_add(self, callable, *args): '''Call this with a function as the argument, and that function will be called on the GUI thread via an event This function returns immediately ''' self.queue.put((callable, args)) def __process_idle_events(self): '''This should never be called directly, it is called via an event, and should always be on the GUI thread''' while True: try: callable, args = self.queue.get(block=False) except queue.Empty: break callable(*args) def run(self): # and launch the thread self.root.mainloop() # This call BLOCKS def timer_fired(self): '''Polling loop for events from other threads''' self.__process_idle_events() # grab the simulation lock, gather all of the # wpilib objects, and display them on the screen self.update_widgets() # call next timer_fired (or we'll never call timer_fired again!) delay = 100 # milliseconds self.root.after(delay, self.timer_fired) # pause, then call timer_fired again def update_widgets(self): # TODO: support multiple slots? #joystick stuff if self.usb_joysticks is not None: self.usb_joysticks.update() # analog module for i, (ain, aout) in enumerate( zip(hal_data['analog_in'], hal_data['analog_out'])): aio = self.analog[i] if aio is not None: if ain['initialized']: aio.set_disabled(False) ain['voltage'] = aio.get_value() elif aout['initialized']: aio.set_value(aout['voltage']) # digital module for i, ch in enumerate(hal_data['dio']): dio = self.dio[i] if dio is not None: if not ch['initialized']: dio.set_disabled() else: # determine which one changed, and set the appropriate one ret = dio.sync_value(ch['value']) if ret is not None: ch['value'] = ret for i, ch in enumerate(hal_data['pwm']): pwm = self.pwm[i] if pwm is not None: pwm.set_value(ch['value']) for i, ch in enumerate(hal_data['relay']): relay = self.relays[i] if relay is not None: if ch['fwd']: relay.set_on() elif ch['rev']: relay.set_back() else: relay.set_off() # solenoid for i, ch in enumerate(hal_data['solenoid']): sol = self.solenoids[i] if not ch['initialized']: sol.set_disabled() else: sol.set_value(ch['value']) # CAN for k, (motor, fl, rl, mode_lbl_txt, enc_txt, analog_txt, pwm_txt) in self.can.items(): can = hal_data['CAN'][k] mode = can['mode_select'] mode_lbl_txt.set(self.can_mode_map[mode]) #change how output works based on control mode if mode == tsrxc.kMode_DutyCycle: #based on the fact that the vbus has 1023 steps motor.set_value(can['value'] / 1023) elif mode == tsrxc.kMode_VoltCompen: #assume voltage is 12 divide by muliplier in cantalon code (256) motor.set_value(can['value'] / 12 / 256) elif mode == tsrxc.kMode_SlaveFollower: #follow the value of the motor value is equal too motor.set_value(self.can[can['value']][0].get_value()) # # currently other control modes are not correctly implemented # else: motor.set_value(can['value']) enc_txt.set('E: %s' % can['enc_position']) analog_txt.set('A: %s' % can['analog_in_position']) pwm_txt.set('P: %s' % can['pulse_width_position']) ret = fl.sync_value(can['limit_switch_closed_for']) if ret is not None: can['limit_switch_closed_for'] = ret ret = rl.sync_value(can['limit_switch_closed_rev']) if ret is not None: can['limit_switch_closed_rev'] = ret # joystick/driver station #sticks = _core.DriverStation.GetInstance().sticks #stick_buttons = _core.DriverStation.GetInstance().stick_buttons for i, (axes, buttons, povs) in enumerate(self.joysticks): joy = hal_data['joysticks'][i] jaxes = joy['axes'] for j, ax in enumerate(axes): jaxes[j] = ax.get_value() jbuttons = joy['buttons'] for j, (ck, var) in enumerate(buttons): jbuttons[j + 1] = True if var.get() else False jpovs = joy['povs'] for j, pov in enumerate(povs): jpovs[j] = int(pov.get_value()) self.field.update_widgets() tm = self.fake_time.get() mode_tm = tm - self.mode_start_tm self.status.config(text="Time: %.03f mode, %.03f total" % (mode_tm, tm)) def set_tooltip(self, widget, cat, idx): tooltip = self.config_obj['pyfrc'][cat].get(str(idx)) if tooltip is not None: Tooltip.create(widget, tooltip) def set_joy_tooltip(self, widget, idx, typ, idx2): tooltip = self.config_obj['pyfrc']['joysticks'][str(idx)][typ].get( str(idx2)) if tooltip is not None: Tooltip.create(widget, tooltip) def on_auton_choices(self, choices): self.autobox['values'] = choices[:] def on_auton_selection(self, selection): self.autobox.set(selection) def on_auton_selected(self, e): self.auton_ctrl.setSelected(self.autobox.get()) def on_robot_mode_change(self, mode): self.mode.set(mode) self.mode_start_tm = self.fake_time.get() # this is not strictly true... a robot can actually receive joystick # commands from the driver station in disabled mode. However, most # people aren't going to use that functionality... controls_disabled = False if mode == self.manager.MODE_OPERATOR_CONTROL else True state = tk.DISABLED if controls_disabled else tk.NORMAL for axes, buttons, povs in self.joysticks: for axis in axes: axis.set_disabled(disabled=controls_disabled) for ck, var in buttons: ck.config(state=state) for pov in povs: pov.set_disabled(disabled=controls_disabled) if not self.manager.is_alive(): for button in self.state_buttons: button.config(state=tk.DISABLED) self.robot_dead.pack() # # Time related callbacks # def on_pause(self, pause): if pause: self.fake_time.pause() else: self.fake_time.resume() def on_step_time(self): val = self.step_entry.get() try: tm = float(self.step_entry.get()) except ValueError: tk.messagebox.showerror("Invalid step time", "'%s' is not a valid number" % val) return if tm > 0: self.fake_time.resume(tm)
def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W+tk.E) # analog slot = tk.LabelFrame(top, text='Analog') self.analog = [] for i in range(len(hal_data['analog_in'])): if hal_data['analog_in'][i]['initialized'] or hal_data['analog_out'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i+1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i+1) self.set_tooltip(vw, 'analog', i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text='Digital') label = tk.Label(slot, text='PWM') label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data['pwm'])): if hal_data['pwm'][i]['initialized']: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0+2*c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1+2*c, row=1 + i % 10) self.set_tooltip(vw, 'pwm', i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text='Digital I/O') label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data['dio'])): if hal_data['dio'][i]['initialized']: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4+c*2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5+c*2, row=1 + i % 9) self.set_tooltip(pi, 'dio', i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text='Relay') label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data['relay'])): if hal_data['relay'][i]['initialized']: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, 'relay', i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) csfm = tk.Frame(top) # solenoid slot = tk.LabelFrame(csfm, text='Solenoid') self.solenoids = [] for i in range(len(hal_data['solenoid'])): label = tk.Label(slot, text=str(i)) c = int(i/2)*2 r = i%2 label.grid(column=0+c, row=r) pi = PanelIndicator(slot) pi.grid(column=1+c, row=r) self.set_tooltip(pi, 'solenoid', i) self.solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) # CAN # self.can_slot = tk.LabelFrame(csfm, text='CAN') # self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) # self.can_mode_map = {} # tsrxc.kMode_CurrentCloseLoop: 'PercentVbus', # tsrxc.kMode_DutyCycle:'PercentVbus', # tsrxc.kMode_NoDrive:'Disabled', # tsrxc.kMode_PositionCloseLoop:'Position', # tsrxc.kMode_SlaveFollower:'Follower', # tsrxc.kMode_VelocityCloseLoop:'Speed', # tsrxc.kMode_VoltCompen:'Voltage' # } self.can = {} # detect new devices # for k in sorted(hal_data['CAN'].keys()): # self._add_CAN(k, hal_data['CAN'][k]) # csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text='Joysticks') self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i*3 row = 0 label = tk.Label(slot, text='Stick %s' % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(['X', 'Y', 'Z', 'T', '4', '5']): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col+1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, 'axes', t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text='POV') label.grid(column=col, row=row) pov = ValueWidget(slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True) pov.grid(column=col+1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col+1+(1-j%2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, 'buttons', j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text='Time') def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton(timing_control, text='Run', variable=realtime_mode, value=0, command=_set_realtime) button.pack(fill=tk.X) button = tk.Radiobutton(timing_control, text='Pause', variable=realtime_mode, value=1, command=_set_realtime) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text='Step', command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, 'Click this to increment time by the step value') Tooltip.create(step_entry, 'Time to step (in seconds)') realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text='Robot') self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton(sim, text='Disabled', variable=self.mode, \ value=self.manager.MODE_DISABLED, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Autonomous', variable=self.mode, \ value=self.manager.MODE_AUTONOMOUS, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Teleoperated', variable=self.mode, \ value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton(sim, text='Test', variable=self.mode, \ value=self.manager.MODE_TEST, command=_set_mode) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text='Robot died!', fg='red') sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text='Autonomous') self.autobox = Combobox(auton, state='readonly') self.autobox.bind('<<ComboboxSelected>>', self.on_auton_selected) self.autobox['width'] = 12 self.autobox.pack(fill=tk.X) Tooltip.create(self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box") from networktables.util import ChooserControl self.auton_ctrl = ChooserControl('Autonomous Mode', lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v)) auton.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y)
class SimUI(object): def __init__(self, manager, fake_time, config_obj): """ initializes all default values and creates a board, waits for run() to be called to start the board manager - sim manager class instance """ self.manager = manager self.fake_time = fake_time self.config_obj = config_obj # Set up idle_add self.queue = queue.Queue() self.root = tk.Tk() self.root.wm_title("PyFRC Robot Simulator v%s" % __version__) self.root.protocol("WM_DELETE_WINDOW", self._delete_window) # setup mode switch frame = tk.Frame(self.root) frame.pack(side=tk.TOP, anchor=tk.W) self._setup_widgets(frame) self.root.resizable(width=0, height=0) # Allow extending the simulation from 3rd party libraries # -> TODO: better API for this self.extensions = [] for ep in iter_entry_points(group="robotpysim", name=None): try: extension = ep.load() except ImportError: logger.debug("Error importing extension '%s'", ep.name, exc_info=True) else: logger.debug("Loaded simulation extension '%s'", ep.name) extension = extension() if hasattr(extension, "update_tk_widgets"): self.extensions.append(extension) self.mode_start_tm = 0 self.text_id = None # connect to the controller self.manager.on_mode_change( lambda mode: self.idle_add(self.on_robot_mode_change, mode)) self.on_robot_mode_change(self.manager.get_mode()) # create pygame joystick if supported try: from .pygame_joysticks import UsbJoysticks except ImportError: logger.warn( "pygame not detected, real joystick support not loaded") self.usb_joysticks = None else: self.usb_joysticks = UsbJoysticks(self) logger.info("pygame was detected, real joystick support loaded!") try: self.root.lift() self.root.attributes("-topmost", True) self.root.attributes("-topmost", False) except Exception: pass self.timer_fired() def _delete_window(self): self.root.destroy() if self.usb_joysticks is not None: self.usb_joysticks.close() def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text="Analog") self.analog = [] for i in range(len(hal_data["analog_in"])): if (hal_data["analog_in"][i]["initialized"] or hal_data["analog_out"][i]["initialized"]): label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, "analog", i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text="Digital") label = tk.Label(slot, text="PWM") label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data["pwm"])): if hal_data["pwm"][i]["initialized"]: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, "pwm", i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text="Digital I/O") label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data["dio"])): if hal_data["dio"][i]["initialized"]: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, "dio", i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text="Relay") label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data["relay"])): if hal_data["relay"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, "relay", i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) self.csfm = csfm = tk.Frame(top) # solenoid (pcm) self.pcm = {} # values self.values = {} # CAN self.can_slot = tk.LabelFrame(csfm, text="CAN") self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) self.can = {} csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text="Joysticks") self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text="Stick %s" % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(["X", "Y", "Z", "T", "4", "5"]): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, "axes", t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text="POV") label.grid(column=col, row=row) pov = ValueWidget( slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True, ) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, "buttons", j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text="Time") def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton( timing_control, text="Run", variable=realtime_mode, value=0, command=_set_realtime, ) button.pack(fill=tk.X) button = tk.Radiobutton( timing_control, text="Pause", variable=realtime_mode, value=1, command=_set_realtime, ) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text="Step", command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, "Click this to increment time by the step value") Tooltip.create(step_entry, "Time to step (in seconds)") realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text="Robot") self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton( sim, text="Disabled", variable=self.mode, value=self.manager.MODE_DISABLED, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Autonomous", variable=self.mode, value=self.manager.MODE_AUTONOMOUS, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Teleoperated", variable=self.mode, value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Test", variable=self.mode, value=self.manager.MODE_TEST, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text="Robot died!", fg="red") sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text="Autonomous") self.autobox = Combobox(auton, state="readonly") self.autobox.bind("<<ComboboxSelected>>", self.on_auton_selected) self.autobox["width"] = 12 self.autobox.pack(fill=tk.X) Tooltip.create( self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box", ) from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( "Autonomous Mode", lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v), ) auton.pack(side=tk.TOP) messages = self.config_obj["pyfrc"]["game_specific_messages"] if messages: gamedata = tk.LabelFrame(ctrl_frame, text="Game Data") self.gamedataval = tk.StringVar() if hasattr(self.gamedataval, "trace_add"): self.gamedataval.trace_add("write", self.on_gamedata_selected) else: self.gamedataval.trace_variable("w", self.on_gamedata_selected) self.gamedatabox = Combobox(gamedata, textvariable=self.gamedataval) self.gamedatabox["width"] = 12 self.gamedatabox.pack(fill=tk.X) self.gamedatabox["values"] = messages self.gamedatabox.current(0) self.manager.game_specific_message = self.gamedatabox.get() Tooltip.create( self.gamedatabox, "Use this selection box to simulate game specific data", ) gamedata.pack(side=tk.TOP) def _reset_robot(): for robot in self.manager.robots: robot.physics_controller.reset_position() button = tk.Button(ctrl_frame, text="Reset Robot", command=_reset_robot) button.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y) def _render_pcm(self): for k, data in sorted(hal_data["pcm"].items()): if k not in self.pcm: slot = tk.LabelFrame(self.csfm, text="Solenoid (PCM %s)" % k) solenoids = [] self.pcm[k] = solenoids for i in range(len(data)): label = tk.Label(slot, text=str(i)) c = int(i / 2) * 2 r = i % 2 label.grid(column=0 + c, row=r) pi = PanelIndicator(slot) pi.grid(column=1 + c, row=r) self.set_tooltip(pi, "solenoid", i) solenoids.append(pi) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) solenoids = self.pcm[k] for i, ch in enumerate(data): sol = solenoids[i] if not ch["initialized"]: sol.set_disabled() else: sol.set_value(ch["value"]) def _render_values(self): for k, v in hal_data["robot"].items(): if not k.endswith("_angle"): continue gyro_label = self.values.get(k) if not gyro_label: gyro_label = self._create_value(k, k, "Angle (Degrees)") gyro_label["text"] = "%.3f" % v for i, gyro in enumerate(hal_data["analog_gyro"]): if not gyro["initialized"]: continue k = "Gyro %s" % i gyro_label = self.values.get(k) if not gyro_label: gyro_label = self._create_value(k, k, "Angle (Degrees)") gyro_label["text"] = "%.3f" % gyro["angle"] for i, encoder in enumerate(hal_data["encoder"]): if not encoder["initialized"]: continue k = "Encoder %s" % i label = self.values.get(k) if not label: txt = "Encoder (%s,%s)" % ( encoder["config"]["ASource_Channel"], encoder["config"]["BSource_Channel"], ) label = self._create_value(k, txt, "Count / Distance") label["text"] = "%s / %.3f" % ( encoder["count"], encoder["count"] * encoder["distance_per_pulse"], ) for k, v in hal_data.get("custom", {}).items(): label = self.values.get(k) if not label: label = self._create_value(k, k, k) if isinstance(v, float): label["text"] = "%.3f" % v else: label["text"] = str(v) def _create_value(self, key, text, tooltip): slot = tk.LabelFrame(self.csfm, text=text) label = tk.Label(slot) label.pack(side=tk.TOP, fill=tk.BOTH) slot.pack(side=tk.TOP, fill=tk.BOTH, padx=5) self.values[key] = label Tooltip.create(label, tooltip) return label def idle_add(self, callable, *args): """Call this with a function as the argument, and that function will be called on the GUI thread via an event This function returns immediately """ self.queue.put((callable, args)) def __process_idle_events(self): """This should never be called directly, it is called via an event, and should always be on the GUI thread""" while True: try: callable, args = self.queue.get(block=False) except queue.Empty: break callable(*args) def run(self): # and launch the thread self.root.mainloop() # This call BLOCKS def timer_fired(self): """Polling loop for events from other threads""" self.__process_idle_events() # grab the simulation lock, gather all of the # wpilib objects, and display them on the screen self.update_widgets() # call next timer_fired (or we'll never call timer_fired again!) delay = 100 # milliseconds self.root.after(delay, self.timer_fired) # pause, then call timer_fired again def update_widgets(self): # TODO: support multiple slots? # joystick stuff if self.usb_joysticks is not None: self.usb_joysticks.update() # analog module for i, (ain, aout) in enumerate( zip(hal_data["analog_in"], hal_data["analog_out"])): aio = self.analog[i] if aio is not None: if ain["initialized"]: aio.set_disabled(False) ain["voltage"] = aio.get_value() elif aout["initialized"]: aio.set_value(aout["voltage"]) # digital module for i, ch in enumerate(hal_data["dio"]): dio = self.dio[i] if dio is not None: if not ch["initialized"]: dio.set_disabled() else: # determine which one changed, and set the appropriate one ret = dio.sync_value(ch["value"]) if ret is not None: ch["value"] = ret for i, ch in enumerate(hal_data["pwm"]): pwm = self.pwm[i] if pwm is not None: pwm.set_value(ch["value"]) for i, ch in enumerate(hal_data["relay"]): relay = self.relays[i] if relay is not None: if ch["fwd"]: relay.set_on() elif ch["rev"]: relay.set_back() else: relay.set_off() # solenoid self._render_pcm() # gyro/encoder self._render_values() # joystick/driver station # sticks = _core.DriverStation.GetInstance().sticks # stick_buttons = _core.DriverStation.GetInstance().stick_buttons for i, (axes, buttons, povs) in enumerate(self.joysticks): joy = hal_data["joysticks"][i] jaxes = joy["axes"] for j, ax in enumerate(axes): jaxes[j] = ax.get_value() jbuttons = joy["buttons"] for j, (ck, var) in enumerate(buttons): jbuttons[j + 1] = True if var.get() else False jpovs = joy["povs"] for j, pov in enumerate(povs): jpovs[j] = int(pov.get_value()) for extension in self.extensions: extension.update_tk_widgets(self) self.field.update_widgets() tm = self.fake_time.get() mode_tm = tm - self.mode_start_tm self.status.config(text="Time: %.03f mode, %.03f total" % (mode_tm, tm)) def set_tooltip(self, widget, cat, idx): tooltip = self.config_obj["pyfrc"][cat].get(str(idx)) if tooltip is not None: Tooltip.create(widget, tooltip) def set_joy_tooltip(self, widget, idx, typ, idx2): tooltip = self.config_obj["pyfrc"]["joysticks"][str(idx)][typ].get( str(idx2)) if tooltip is not None: Tooltip.create(widget, tooltip) def on_auton_choices(self, choices): self.autobox["values"] = choices[:] def on_auton_selection(self, selection): self.autobox.set(selection) def on_auton_selected(self, e): self.auton_ctrl.setSelected(self.autobox.get()) def on_gamedata_selected(self, *args): self.manager.game_specific_message = self.gamedatabox.get() def on_robot_mode_change(self, mode): self.mode.set(mode) self.mode_start_tm = self.fake_time.get() # this is not strictly true... a robot can actually receive joystick # commands from the driver station in disabled mode. However, most # people aren't going to use that functionality... controls_disabled = (False if mode == self.manager.MODE_OPERATOR_CONTROL else True) state = tk.DISABLED if controls_disabled else tk.NORMAL for axes, buttons, povs in self.joysticks: for axis in axes: axis.set_disabled(disabled=controls_disabled) for ck, var in buttons: ck.config(state=state) for pov in povs: pov.set_disabled(disabled=controls_disabled) if not self.manager.is_alive(): for button in self.state_buttons: button.config(state=tk.DISABLED) self.robot_dead.pack() # # Time related callbacks # def on_pause(self, pause): if pause: self.fake_time.pause() else: self.fake_time.resume() def on_step_time(self): val = self.step_entry.get() try: tm = float(self.step_entry.get()) except ValueError: tk.messagebox.showerror("Invalid step time", "'%s' is not a valid number" % val) return if tm > 0: self.fake_time.resume(tm)
def _setup_widgets(self, frame): top = tk.Frame(frame) top.grid(column=0, row=0) bottom = tk.Frame(frame) bottom.grid(column=0, row=1) self.field = RobotField(frame, self.manager, self.config_obj) self.field.grid(column=1, row=0, rowspan=2) # status bar self.status = tk.Label(frame, bd=1, relief=tk.SUNKEN, anchor=tk.E) self.status.grid(column=0, row=2, columnspan=2, sticky=tk.W + tk.E) # analog slot = tk.LabelFrame(top, text="Analog") self.analog = [] for i in range(len(hal_data["analog_in"])): if (hal_data["analog_in"][i]["initialized"] or hal_data["analog_out"][i]["initialized"]): label = tk.Label(slot, text=str(i)) label.grid(column=0, row=i + 1) vw = ValueWidget(slot, clickable=True, minval=-10.0, maxval=10.0) vw.grid(column=1, row=i + 1) self.set_tooltip(vw, "analog", i) else: vw = None self.analog.append(vw) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) # digital slot = tk.LabelFrame(top, text="Digital") label = tk.Label(slot, text="PWM") label.grid(column=0, columnspan=4, row=0) self.pwm = [] for i in range(len(hal_data["pwm"])): if hal_data["pwm"][i]["initialized"]: c = i // 10 label = tk.Label(slot, text=str(i)) label.grid(column=0 + 2 * c, row=1 + i % 10) vw = ValueWidget(slot) vw.grid(column=1 + 2 * c, row=1 + i % 10) self.set_tooltip(vw, "pwm", i) else: vw = None self.pwm.append(vw) label = tk.Label(slot, text="Digital I/O") label.grid(column=4, columnspan=6, row=0) self.dio = [] for i in range(len(hal_data["dio"])): if hal_data["dio"][i]["initialized"]: c = i // 9 label = tk.Label(slot, text=str(i)) label.grid(column=4 + c * 2, row=1 + i % 9) pi = PanelIndicator(slot, clickable=True) pi.grid(column=5 + c * 2, row=1 + i % 9) self.set_tooltip(pi, "dio", i) else: pi = None self.dio.append(pi) label = tk.Label(slot, text="Relay") label.grid(column=10, columnspan=2, row=0, padx=5) self.relays = [] for i in range(len(hal_data["relay"])): if hal_data["relay"][i]["initialized"]: label = tk.Label(slot, text=str(i)) label.grid(column=10, row=1 + i, sticky=tk.E) pi = PanelIndicator(slot) pi.grid(column=11, row=1 + i) self.set_tooltip(pi, "relay", i) else: pi = None self.relays.append(pi) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) self.csfm = csfm = tk.Frame(top) # solenoid (pcm) self.pcm = {} # values self.values = {} # CAN self.can_slot = tk.LabelFrame(csfm, text="CAN") self.can_slot.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, padx=5) self.can = {} csfm.pack(side=tk.LEFT, fill=tk.Y) # joysticks slot = tk.LabelFrame(bottom, text="Joysticks") self.joysticks = [] for i in range(4): axes = [] buttons = [] col = 1 + i * 3 row = 0 label = tk.Label(slot, text="Stick %s" % i) label.grid(column=col, columnspan=3, row=row) row += 1 # TODO: make this configurable for j, t in enumerate(["X", "Y", "Z", "T", "4", "5"]): label = tk.Label(slot, text=t) label.grid(column=col, row=row) vw = ValueWidget(slot, clickable=True, default=0.0) vw.grid(column=col + 1, row=row, columnspan=2) self.set_joy_tooltip(vw, i, "axes", t) axes.append(vw) row += 1 # POV: this needs improvement label = tk.Label(slot, text="POV") label.grid(column=col, row=row) pov = ValueWidget( slot, clickable=True, default=-1, minval=-1, maxval=360, step=45, round_to_step=True, ) pov.grid(column=col + 1, row=row, columnspan=2) row += 1 for j in range(1, 11): var = tk.IntVar() ck = tk.Checkbutton(slot, text=str(j), variable=var) ck.grid(column=col + 1 + (1 - j % 2), row=row + int((j - 1) / 2)) self.set_joy_tooltip(ck, i, "buttons", j) buttons.append((ck, var)) self.joysticks.append((axes, buttons, [pov])) slot.pack(side=tk.LEFT, fill=tk.Y, padx=5) ctrl_frame = tk.Frame(bottom) # timing control timing_control = tk.LabelFrame(ctrl_frame, text="Time") def _set_realtime(): if realtime_mode.get() == 0: step_button.pack_forget() step_entry.pack_forget() self.on_pause(False) else: step_button.pack(fill=tk.X) step_entry.pack() self.on_pause(True) realtime_mode = tk.IntVar() button = tk.Radiobutton( timing_control, text="Run", variable=realtime_mode, value=0, command=_set_realtime, ) button.pack(fill=tk.X) button = tk.Radiobutton( timing_control, text="Pause", variable=realtime_mode, value=1, command=_set_realtime, ) button.pack(fill=tk.X) step_button = tk.Button(timing_control, text="Step", command=self.on_step_time) self.step_entry = tk.StringVar() self.step_entry.set("0.025") step_entry = tk.Entry(timing_control, width=6, textvariable=self.step_entry) Tooltip.create(step_button, "Click this to increment time by the step value") Tooltip.create(step_entry, "Time to step (in seconds)") realtime_mode.set(0) timing_control.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # simulation control sim = tk.LabelFrame(ctrl_frame, text="Robot") self.state_buttons = [] self.mode = tk.IntVar() def _set_mode(): self.manager.set_mode(self.mode.get()) button = tk.Radiobutton( sim, text="Disabled", variable=self.mode, value=self.manager.MODE_DISABLED, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Autonomous", variable=self.mode, value=self.manager.MODE_AUTONOMOUS, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Teleoperated", variable=self.mode, value=self.manager.MODE_OPERATOR_CONTROL, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) button = tk.Radiobutton( sim, text="Test", variable=self.mode, value=self.manager.MODE_TEST, command=_set_mode, ) button.pack(fill=tk.X) self.state_buttons.append(button) self.robot_dead = tk.Label(sim, text="Robot died!", fg="red") sim.pack(side=tk.TOP, fill=tk.BOTH, expand=1) # # Set up a combo box that allows you to select an autonomous # mode in the simulator # try: from tkinter.ttk import Combobox except: pass else: auton = tk.LabelFrame(ctrl_frame, text="Autonomous") self.autobox = Combobox(auton, state="readonly") self.autobox.bind("<<ComboboxSelected>>", self.on_auton_selected) self.autobox["width"] = 12 self.autobox.pack(fill=tk.X) Tooltip.create( self.autobox, "Use robotpy_ext.autonomous.AutonomousModeSelector to use this selection box", ) from networktables.util import ChooserControl self.auton_ctrl = ChooserControl( "Autonomous Mode", lambda v: self.idle_add(self.on_auton_choices, v), lambda v: self.idle_add(self.on_auton_selection, v), ) auton.pack(side=tk.TOP) messages = self.config_obj["pyfrc"]["game_specific_messages"] if messages: gamedata = tk.LabelFrame(ctrl_frame, text="Game Data") self.gamedataval = tk.StringVar() if hasattr(self.gamedataval, "trace_add"): self.gamedataval.trace_add("write", self.on_gamedata_selected) else: self.gamedataval.trace_variable("w", self.on_gamedata_selected) self.gamedatabox = Combobox(gamedata, textvariable=self.gamedataval) self.gamedatabox["width"] = 12 self.gamedatabox.pack(fill=tk.X) self.gamedatabox["values"] = messages self.gamedatabox.current(0) self.manager.game_specific_message = self.gamedatabox.get() Tooltip.create( self.gamedatabox, "Use this selection box to simulate game specific data", ) gamedata.pack(side=tk.TOP) def _reset_robot(): for robot in self.manager.robots: robot.physics_controller.reset_position() button = tk.Button(ctrl_frame, text="Reset Robot", command=_reset_robot) button.pack(side=tk.TOP) ctrl_frame.pack(side=tk.LEFT, fill=tk.Y)