diff --git a/framework/datastreams.py b/framework/datastreams.py index 76ce4d9..bcc8166 100644 --- a/framework/datastreams.py +++ b/framework/datastreams.py @@ -52,7 +52,7 @@ def push(self, data, subsystem, autolock=False): #Check to see if we should automatically lock the stream to subsystem if autolock: - self._lock = subsystem + self.lock(subsystem) #Check lock if self._lock is not None and self._lock is not subsystem: diff --git a/framework/events.py b/framework/events.py index b558203..160962b 100644 --- a/framework/events.py +++ b/framework/events.py @@ -1,11 +1,16 @@ """ -This is an event system +This is an event system. Modules can add callbacks to events, which will be run in a new thread once triggered. +This is the primary mechanism for starting and stopping run loops. + +On the flip side, events can be either "triggered" which is a one-time call (For example: shoot_cannon or reset_gyro), + or they can be started and then stopped, for events with duration (For example: run, teleoperated, or enabled). + The main difference is that when modules are freshly loaded, the refresh_events method is called, which then finds all + of the "started" events and triggers them for the new module. """ import logging import threading import framework.module_engine -from framework.record import recorder __author__ = 'christian' diff --git a/framework/filesystem.py b/framework/filesystem.py index d43ea85..20cef4b 100644 --- a/framework/filesystem.py +++ b/framework/filesystem.py @@ -11,7 +11,7 @@ root_dir = "" #The directory to store all logs and recordings from this run session -log_dir = os.path.join(root_dir, "recs") +log_dir = os.path.join(root_dir, "logs") #The log file, which is a dump of all console output log_file = os.path.join(log_dir, "main.log") @@ -34,7 +34,7 @@ def gen_paths(): global log_dir, events_file, log_file, datastream_dir #The root log directory - log_root_dir = os.path.join(root_dir, "recs") + log_root_dir = os.path.join(root_dir, "logs") #Figure out what log file index we should use #The log file index is a 4-digit number corresponding to an unused log folder diff --git a/framework/record/__init__.py b/framework/record/__init__.py deleted file mode 100644 index 339709f..0000000 --- a/framework/record/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'christian' \ No newline at end of file diff --git a/framework/record/playback.py b/framework/record/playback.py deleted file mode 100644 index 32cb934..0000000 --- a/framework/record/playback.py +++ /dev/null @@ -1,109 +0,0 @@ -from framework import module_engine, datastreams, events -import json -import logging -import os -import time -import threading - -__author__ = 'christian' - -#TODO This stuff is currently junk. Everything has changed since this was built, so it will probably blow up if used - -starttime = 0 - - -def replay_recording(recpath="latest"): - if recpath is "latest": - rootdir = "recs" - dates = os.listdir(rootdir) - dates.sort() - times = os.listdir(os.path.join(rootdir, dates[0])) - if len(times) <= 1: - del(dates[0]) - times = os.listdir(os.path.join(rootdir, dates[0])) - times.sort(reverse=True) - recpath = os.path.join(rootdir, dates[0], times[1]) - - global starttime - logging.info("Playing recording at " + recpath) - starttime = time.time() - threading.Thread(target=replay_datastreams, args={os.path.join(recpath, "datastreams")}).start() - threading.Thread(target=replay_events, args={os.path.join(recpath, "events.rec")}).start() - - -def replay_datastreams(datastreampath): - finished = False - parsed_streams = dict() - stream_handles = dict() - - #parse it - for datastream in os.listdir(datastreampath): - filehandle = open(datastreampath + "/" + datastream, "r") - name = datastream.replace(".rec", "") - parsed_streams[name] = list() - lines = filehandle.readlines() - for line in lines: - line = line.rstrip() - splitdata = line.split(":", 1)[1].split(",", 3) - update = dict() - update["timestamp"] = float(splitdata[0][11:].strip()[1:-1]) - update["srcmod"] = splitdata[1][6:].strip()[1:-1] - if update["srcmod"] in module_engine.list_modules(): - continue - update["autolock"] = bool(splitdata[2][10:].strip()[1:-1]) - jsondata = splitdata[3][6:].strip()[1:-1] - update["data"] = json.loads(jsondata) - parsed_streams[name].append(update) - #do it - for stream in parsed_streams: - stream_handles[stream] = datastreams.get_stream(stream) - - while not finished: - timestamp = time.time() - starttime - finished = True - for stream in parsed_streams: - if len(parsed_streams[stream]) is 0: - continue - finished = False - if parsed_streams[stream][0]["timestamp"] < timestamp: - update = parsed_streams[stream][0] - del parsed_streams[stream][0] - stream_handles[stream].push(update["data"], update["srcmod"], autolock=update["autolock"]) - time.sleep(.1) - logging.info("Datastream Playback finished") - - -def replay_events(eventsfile): - finished = False - parsed_events = list() - - #parse it - filehandle = open(eventsfile, "r") - lines = filehandle.readlines() - for line in lines: - line = line.rstrip() - splitdata = line.split(",") - update = dict() - update["event"] = splitdata[0][6:].strip()[1:-1] - update["action"] = splitdata[1][7:].strip()[1:-1] - update["timestamp"] = float(splitdata[2][11:].strip()[1:-1]) - update["srcmod"] = splitdata[3][5:].strip()[1:-1] - if update["srcmod"] in module_engine.list_modules() or update["srcmod"] in ("modmaster", "RobotTrunk", "datastream"): - continue - parsed_events.append(update) - logging.info("Parsed events file " + eventsfile) - #do it - while not finished: - timestamp = time.time() - starttime - if len(parsed_events) is 0: - finished = True - continue - if parsed_events[0]["timestamp"] < timestamp: - event = parsed_events[0] - del parsed_events[0] - if event['action'] == "triggered": - events.trigger_event(event["event"]) - else: - events.set_event(event["event"], event["srcmod"], event["action"] is "on") - time.sleep(.1) - logging.info("Event Playback finished") \ No newline at end of file diff --git a/framework/record/recorder.py b/framework/record/recorder.py deleted file mode 100644 index 9db7361..0000000 --- a/framework/record/recorder.py +++ /dev/null @@ -1,55 +0,0 @@ -from framework import filesystem -import time -import os -import json -from decimal import * - -__author__ = 'christian' - -#TODO This stuff is currently junk. Everything has changed since this was built, so it will probably blow up if used - -datastream_files = dict() -last_datastream_updates = dict() -starttime = 0 -datastream_rate = .25 - -recording = False - -TWOPLACES = Decimal(10) ** -2 - - -def startRecording(): - global recording, starttime, event_file - filesystem.make_dirs() - event_file = open(filesystem.events_file, "w", 1) - recording = True - starttime = time.time() - - -def log_event(action, eventname, srcmod): - if not recording: - return - timestamp = time.time() - starttime - event_file.write("Event [{}], Action [{}], Timestamp [{}], From [{}]\n".format(eventname, action, Decimal(timestamp).quantize(TWOPLACES), srcmod)) - - -def update_datastream(name, data, srcmod, autolock): - if not recording: - return - if name not in datastream_files: - filename = os.path.join(filesystem.datastream_dir, name + ".rec") - datastream_files[name] = open(filename, "w", 1) - last_datastream_updates[name] = 0 - timestamp = time.time() - starttime - if last_datastream_updates[name] + datastream_rate < timestamp: - jsondata = json.dumps(data) - datastream_files[name].write("Datastream update: Timestamp [{}], From [{}], Autolock [{}], Data [{}]\n".format(Decimal(timestamp).quantize(TWOPLACES), srcmod, autolock, jsondata)) - last_datastream_updates[name] = timestamp - - - - - - - - diff --git a/framework/wpiwrap.py b/framework/wpiwrap.py index cc09bc1..dd97e21 100644 --- a/framework/wpiwrap.py +++ b/framework/wpiwrap.py @@ -268,6 +268,8 @@ def init_wpilib_refrence(self, name, port): def set_semi_period(self): #TODO Remove this try/except once pyfrc is updated with my commit + #I submitted a patch to pyfrc with the (previously missing) function stub. But it appears that it may not have fully propogated to the + # public release yet. try: self.wpiobject.SetSemiPeriodMode(True) except AttributeError: @@ -340,14 +342,18 @@ def init_wpilib_refrence(self, name, port): def get(self): angle = self.wpiobject.GetAngle() + + #Run the value by the dog self.statmsg, self.status = self.dog.sniff(angle) + #If the dog says it is a bad value, raise an error if not self.status: raise DeviceInErrorStateError(self.statmsg) else: return angle def reset(self): + #Let the dog know, so he doesen't alert when the value jumps to zero self.dog.reset() return self.wpiobject.Reset() diff --git a/modules/christian/auto/1-ball.py b/modules/christian/auto/1-ball.py index 31499a5..010a959 100644 --- a/modules/christian/auto/1-ball.py +++ b/modules/christian/auto/1-ball.py @@ -28,7 +28,7 @@ def __init__(self): #Register autonomous event callback events.add_callback("autonomous", self.subsystem, callback=self.run, inverse_callback=self.stop) - class EndAuto(Exception): + class EndAutoError(Exception): """This is just a way to stop the autonomous routine at any time if we are told to""" pass @@ -42,14 +42,13 @@ def run(self): config = self.autonomous_config.get({"second_shot": 7, "first_shot": 12, "distance_from_tape": 3, "start_position": 1}) #Configure navigator - self.navigator_config.push({"max_values": [5, 4], "cycles_per_second": 10, "precision": .2}, self.subsystem, autolock=True) + self.navigator_config.push({"max_values": [5, 5], "cycles_per_second": 10, "precision": .2}, self.subsystem, autolock=True) #Mark drivetrain events.trigger_event("drivetrain.mark", self.subsystem) #Drop Arms - self.intake_stream.lock(self.subsystem) - self.intake_stream.push({"arms_down": True, "flipper_out": True, "intake_motor": 0}, self.subsystem) + self.intake_stream.push({"arms_down": True, "flipper_out": True, "intake_motor": 0}, self.subsystem, autolock=True) #Trigger Vision wpilib.SmartDashboard.PutNumber("hot goal", 0) @@ -65,7 +64,7 @@ def run(self): # or the light sensor reports that we are on the line. while self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and self.light_sensor.get() < 2.5: if self.stop_flag: - raise self.EndAuto() + raise self.EndAutoError() time.sleep(.2) #Stop the bot @@ -98,7 +97,7 @@ def run(self): while self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and abs(pos["distance"] - shot_point) > 1: pos = self.drivetrain_state_stream.get({"distance": 0}) if self.stop_flag: - raise self.EndAuto() + raise self.EndAutoError() time.sleep(.1) #Shoot @@ -107,7 +106,7 @@ def run(self): #Wait for shooting to end time.sleep(.5) - except self.EndAuto: + except self.EndAutoError: pass #STOP!!! @@ -115,6 +114,7 @@ def run(self): def stop(self): logging.info("Stopping autonomous") + events.stop_event("navigator.run", self.subsystem) self.stop_flag = True diff --git a/modules/christian/auto/2-ball.py b/modules/christian/auto/2-ball.py index 60639aa..79aecd9 100644 --- a/modules/christian/auto/2-ball.py +++ b/modules/christian/auto/2-ball.py @@ -1,111 +1,134 @@ -from framework.module_engine import ModuleBase - __author__ = 'christian' from framework import events, datastreams, wpiwrap import time +import logging try: import wpilib except ImportError: from pyfrc import wpilib -class Module(ModuleBase): + +class Module(object): subsystem = "autonomous" + stop_flag = False def __init__(self): self.ballpresense_switch = wpiwrap.DigitalInput("Ball Presence Switch", self.subsystem, 13) self.navigator_config = datastreams.get_stream("navigator.config") + self.navigator_goals = datastreams.get_stream("navigator.goals") self.navigator_status = datastreams.get_stream("navigator.status") self.autonomous_config = datastreams.get_stream("auto_config") - self.position_stream = datastreams.get_stream("position") - self.arm_stream = datastreams.get_stream("arms") - self.intake_stream = datastreams.get_stream("intake") - self.light_sensor_stream = datastreams.get_stream("light_sensor") - events.add_callback("autonomous", self.subsystem, self.run) - events.add_callback("disabled", self.subsystem, self.disable) - - def disable(self): - self.stop_flag = True - self.stop_nav() + self.drivetrain_state_stream = datastreams.get_stream("position") + self.intake_control_stream = datastreams.get_stream("intake.control") + self.light_sensor = wpiwrap.AnalogInput("Light Sensor", self.subsystem, 1) + events.add_callback("autonomous", self.subsystem, callback=self.run, inverse_callback=self.stop) - def stop_nav(self): - events.set_event("navigator.run", self.subsystem, False) - events.trigger_event("navigator.stop") - #if self.navigator_status.get(1) is -1: - # raise Exception("Error in navigator execution") + class EndAutoError(Exception): + """This is just a way to stop the autonomous routine at any time if we are told to""" + pass def run(self): self.stop_flag = False auto_start_time = time.time() - config = self.autonomous_config.get({"first_shot": 12, "second_shot": 7, "distance_from_tape": 3, "start_position": 0}) - #Drop Arms - self.arm_stream.lock(self.subsystem) - self.arm_stream.push(False, self.subsystem) - - #Trigger Vision - wpilib.SmartDashboard.PutNumber("hot goal", 0) - wpilib.SmartDashboard.PutBoolean("do vision", True) - - #Drive to line - events.trigger_event("navigator.mark") - self.navigator_config.push({"mode": 2, "y-goal": config["distance_from_tape"], "max-speed": 2, "acceleration": 2, "iter-second": 10}, self.subsystem, autolock=True) - events.set_event("navigator.run", self.subsystem, True) - time.sleep(.2) - start_time = time.time() - while not self.stop_flag and self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and self.light_sensor_stream.get(1.5) < 2.5: - time.sleep(.5) - if self.stop_flag: return - self.stop_nav() - - #Fork for hot - time.sleep(1) - hot_goal = wpilib.SmartDashboard.GetNumber("hot goal") - current_time_elapsed = time.time() - auto_start_time - if config["start_position"] != hot_goal: - sleep_time = 5 - current_time_elapsed - if sleep_time > 0: - time.sleep(sleep_time) - - first_shot_drive = 18 - config["first_shot"] - second_shot_drive = 18 - config["second_shot"] - #Charge - events.trigger_event("navigator.mark") - self.navigator_config.push({"mode": 2, "y-goal": second_shot_drive, "max-speed": 5, "acceleration": 3, "iter-second": 10}, self.subsystem, autolock=True) - events.set_event("navigator.run", self.subsystem, True) - start_time = time.time() - pos = self.position_stream.get((0, 0)) - while not self.stop_flag and self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and abs(pos[1] - first_shot_drive) > 1: - pos = self.position_stream.get((0, 0)) - time.sleep(.2) - if self.stop_flag: return - if self.navigator_status.get(1) is -1: - raise Exception("Error in navigator execution") + try: + #Get auto config + config = self.autonomous_config.get({"second_shot": 7, "first_shot": 12, "distance_from_tape": 3, "start_position": 1}) - #Shoot - events.trigger_event("highShot") + #Configure navigator + self.navigator_config.push({"max_values": [5, 5], "cycles_per_second": 10, "precision": .2}, self.subsystem, autolock=True) - #Run intake - self.intake_stream.lock(self.subsystem) - self.intake_stream.push(.5, self.subsystem, True) + #Mark drivetrain + events.trigger_event("drivetrain.mark", self.subsystem) - #Wait for finish of drive arc - while not self.stop_flag and self.navigator_status.get(1) is 0 and time.time() - start_time < 7: - time.sleep(.5) - if self.stop_flag: return - self.stop_nav() + #Drop Arms + self.intake_control_stream.push({"arms_down": True, "flipper_out": True, "intake_motor": 0}, self.subsystem, autolock=True) - #wait for ball presence - while not self.stop_flag and not self.ballpresense_switch.get(): - time.sleep(.5) - if self.stop_flag: return + #Trigger Vision + wpilib.SmartDashboard.PutNumber("hot goal", 0) + wpilib.SmartDashboard.PutBoolean("do vision", True) - #Shoot - events.trigger_event("highShot") - - #stop intake - self.intake_stream.push(0, self.subsystem) + #Drive to line + self.navigator_goals.push([(0, config["distance_from_tape"])], self.subsystem, autolock=True) + events.start_event("navigator.run", self.subsystem) + time.sleep(.2) + start_time = time.time() + #Loop until either we are told to stop, the navigator is done, it has taken too much time, + # or the light sensor reports that we are on the line. + while self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and self.light_sensor.get() < 2.5: + if self.stop_flag: + raise self.EndAutoError() + time.sleep(.2) + + #Stop the bot + events.stop_event("navigator.run", self.subsystem) + + #Wait a bit for things to settle + time.sleep(1) + + #Check hot goal and wait if the goal we are on is cold + hot_goal = wpilib.SmartDashboard.GetNumber("hot goal") + if config["start_position"] != hot_goal: + #How long do we sleep if we want to wake up at the 5 second mark of autonomous mode? + sleep_time = time.time() - (5 + auto_start_time) + if sleep_time > 0: + time.sleep(sleep_time) + + #Charge! + + #What is the target for our shot? + current_distance = self.drivetrain_state_stream.get({"distance": 0})["distance"] + shot_point = (18 - config["first_shot"]) + current_distance + target_drive = (18 - config["second_shot"]) + current_distance + + self.navigator_goals.push([(0, target_drive)], self.subsystem, autolock=True) + + #Run navigator until we are at shot_point + events.start_event("navigator.run", self.subsystem) + start_time = time.time() + pos = self.drivetrain_state_stream.get({"distance": 0}) + while self.navigator_status.get(1) is 0 and time.time() - start_time < 5 and abs(pos["distance"] - shot_point) > 1: + pos = self.drivetrain_state_stream.get({"distance": 0}) + if self.stop_flag: + raise self.EndAutoError() + time.sleep(.1) + + #Shoot + events.trigger_event("shoot_cannon", self.subsystem) + + #Run intake + self.intake_control_stream.push({"arms_down": True, "flipper_out": True, "intake_motor": .5}, self.subsystem, autolock=True) + + #Wait for finish of drive arc + while self.navigator_status.get(1) is 0 and time.time() - start_time < 7: + time.sleep(.5) + if self.stop_flag: + raise self.EndAutoError() + + #wait for ball presence + while not self.ballpresense_switch.get(): + if self.stop_flag: + raise self.EndAutoError() + time.sleep(.2) + + #Shoot + events.trigger_event("shoot_cannon", self.subsystem) + + except self.EndAutoError: + pass + + #Stop navigator + events.stop_event("navigator.run", self.subsystem) + + #Stop intake + self.intake_control_stream.push({"arms_down": True, "flipper_out": True, "intake_motor": 0}, self.subsystem, autolock=True) + + def stop(self): + logging.info("Stopping autonomous") + self.stop_flag = True + events.stop_event("navigator.run", self.subsystem) diff --git a/modules/christian/basic_arcade_drive.py b/modules/christian/basic_arcade_drive.py index fade316..a776e67 100644 --- a/modules/christian/basic_arcade_drive.py +++ b/modules/christian/basic_arcade_drive.py @@ -4,6 +4,7 @@ class Module: + """This is a basic version of the drivetrain module, using pure estimation rather than sensors for state feedback""" subsystem = "drivetrain" stop_flag = False @@ -29,6 +30,7 @@ def mark(self): self.current_angle = 0 def run_loop(self): + """Listen to joystick input and the control datastream to determine what to do with the drivetrain""" self.stop_flag = False while not self.stop_flag: joystick_input_x = self.joystick.get_axis(0) diff --git a/modules/christian/dashboard.py b/modules/christian/dashboard.py index 2af67d2..37691f4 100644 --- a/modules/christian/dashboard.py +++ b/modules/christian/dashboard.py @@ -5,47 +5,50 @@ except ImportError: from pyfrc import wpilib import time -import json import logging import traceback class Module: + """This manages the smart dashboard output and autonomous selection""" subsystem = "dashboard" stop_flag = False def __init__(self): wpilib.SmartDashboard.init() - self.sensor = wpiwrap.AnalogInput("Light Sensor", self.subsystem, 1) + + #Initialize the ultra sonic sensor here self.ultrasonic = wpiwrap.Counter("Ultrasonic_Sensor", self.subsystem, 11) self.ultrasonic.set_semi_period() + #Get datastream for autonomous config self.autonomous_conf_stream = datastreams.get_stream("autonomous_config") - events.add_callback("run", self.subsystem, callback=self.run, inverse_callback=self.stop) + + #Initialize smart dashboard variables wpilib.SmartDashboard.PutNumber("Auto Routine", 2) wpilib.SmartDashboard.PutNumber("1st Shoot Distance", 12) wpilib.SmartDashboard.PutNumber("2nd Shoot Distance", 7) wpilib.SmartDashboard.PutNumber("Distance from Tape", 3) wpilib.SmartDashboard.PutNumber("Start Position", 1) + #Register callback + events.add_callback("run", self.subsystem, callback=self.run, inverse_callback=self.stop) + def run(self): last_auto_routine = 5 self.stop_flag = False while not self.stop_flag: + #Use wpiwrap's function to automatically report all sensor values try: wpiwrap.publish_values() except Exception as e: logging.error("Error: " + str(e) + "\n" + traceback.format_exc()) - #default = {"buttons": (False, False, False, False, False, False, False, False, False, False), "axes": (0,0,0,0)} - #joy1string = json.dumps(self.joystick1.get(default)) - #joy2string = json.dumps(self.joystick2.get(default)) - #wpilib.SmartDashboard.PutString("joystick1", joy1string) - #wpilib.SmartDashboard.PutString("joystick2", joy2string) - #wpilib.SmartDashboard.PutNumber("Ultrasonic Sensor", self.ultrasonic_stream.get(0)) + #Manage autonomous routine selection and configuration auto_routine = wpilib.SmartDashboard.GetNumber("Auto Routine") if auto_routine != last_auto_routine: + #If auto_routine has changed, load the appropriate autonomous module if auto_routine == 0: try: module_engine.get_modules("autonomous").load("modules.christian.auto.dead_auto") @@ -72,12 +75,15 @@ def run(self): last_auto_routine = auto_routine + #Get all of the config values from the dashboard and send them to the autonomous config datastream. + first_shot = wpilib.SmartDashboard.GetNumber("1st Shoot Distance") second_shot = wpilib.SmartDashboard.GetNumber("2nd Shoot Distance") distance_from_tape = wpilib.SmartDashboard.GetNumber("Distance from Tape") start_position = wpilib.SmartDashboard.GetNumber("Start Position") - self.autonomous_conf_stream.push({"first_shot": first_shot, "second_shot": second_shot, "distance_from_tape": distance_from_tape, "start_position": start_position}, self.subsystem, autolock=True) + data = {"first_shot": first_shot, "second_shot": second_shot, "distance_from_tape": distance_from_tape, "start_position": start_position} + self.autonomous_conf_stream.push(data, self.subsystem, autolock=True) time.sleep(.5) diff --git a/modules/christian/debugging.py b/modules/christian/debugging.py index fe132a6..7546716 100644 --- a/modules/christian/debugging.py +++ b/modules/christian/debugging.py @@ -4,6 +4,7 @@ class Module: + """This module contains various debugging utilities (currently 1) bound to joystick buttons.""" subsystem = "debugging" stop_flag = False @@ -13,13 +14,17 @@ def __init__(self): def run(self): self.stop_flag = False + #Initializing this to true to avoid a perpetual loop when this module gets reloaded! last_reload_button = True while not self.stop_flag: + #If the reload button is pressed, reload all modules. reload_button = self.joystick.get_button(10) if reload_button and not last_reload_button: module_engine.reload_mods() + + #Save value and sleep last_reload_button = reload_button - time.sleep(.2) + time.sleep(.5) def stop(self): self.stop_flag = True \ No newline at end of file diff --git a/modules/christian/enhanced_arcade_drive.py b/modules/christian/enhanced_arcade_drive.py index e05c706..c28a3ac 100644 --- a/modules/christian/enhanced_arcade_drive.py +++ b/modules/christian/enhanced_arcade_drive.py @@ -4,6 +4,10 @@ class Module: + """ + This handles just about everything related to the drivetrain, + which amounts to motors, encoders, and a lonely gyro. + """ subsystem = "drivetrain" _stop_drive_loop = False @@ -16,7 +20,7 @@ def __init__(self): self.right_encoder = wpiwrap.Encoder("Right Encoder", self.subsystem, 1, 2, 360, 20) self.left_encoder = wpiwrap.Encoder("Left Encoder", self.subsystem, 4, 3, 360, 20) - self.gyroscope = wpiwrap.Gyro("Gyroscope", self.subsystem, 2, 300) + self.gyroscope = wpiwrap.Gyro("Gyroscope", self.subsystem, 2, 500) events.add_callback("drivetrain.mark", self.subsystem, callback=self.mark) events.add_callback("enabled", self.subsystem, callback=self.drive_loop, inverse_callback=self.stop_drive_loop) @@ -45,6 +49,7 @@ def stop_state_loop(self): self._stop_state_loop = True def drive_loop(self): + """Listen to joystick input and the control datastream to determine what to do with the drivetrain""" self._stop_drive_loop = False while not self._stop_drive_loop: joystick_input_x = self.joystick.get_axis(0) @@ -71,6 +76,7 @@ def drive_loop(self): input_y = control_input[1]/5 input_x = control_input[0]/5 + #Convert input_x and input_y into output_left and output_right output_left = input_x - input_y if abs(output_left) > 1: @@ -81,6 +87,7 @@ def drive_loop(self): if abs(output_right) > 1: output_right = abs(output_right)/output_right + #Drive the motors self.left_motor.set(output_left) self.right_motor.set(output_right) diff --git a/modules/christian/framework_remote.py b/modules/christian/framework_remote.py index b20b6a1..549c707 100644 --- a/modules/christian/framework_remote.py +++ b/modules/christian/framework_remote.py @@ -14,40 +14,61 @@ class Module: + """ + This module interfaces with the python application under scripts/remote.py. + It publishes data over Network Tables and obeys commands sent back to it. + """ subsystem = "remote" stop_flag = False - index = 1 + #The last-used command index + command_index = 1 def __init__(self): - events.add_callback("run", self.subsystem, callback=self.run, inverse_callback=self.stop) + #Initialize Network Tables wpilib.SmartDashboard.init() self.table = wpilib.NetworkTable.GetTable("framework_remote") self.table.PutString("frameworkcommands", "{}") + #Setup callback + events.add_callback("run", self.subsystem, callback=self.run, inverse_callback=self.stop) + def run(self): self.stop_flag = False while not self.stop_flag: + #Get a list of all modules and send it to the table modnames = module_engine.list_modules() self.table.PutString("modlist", json.dumps(modnames)) + + #Loop through all loaded modules and get some info about them, + # then publish it to the table as a json string. for name in modnames: mod = module_engine.get_modules(name) modsummary = {"name": mod.subsystem, "filename": mod.filename, "runningTasks": mod.running_events, "fallbackList": mod.fallback_list} self.table.PutString("mod." + name, json.dumps(modsummary)) + #Try to update our command_index if there is a larger remote one. + # this is to ensure that we do not run the same command again if we restart. try: remoteindex = self.table.GetNumber("globalCommandIndex") - if self.index < remoteindex: - self.index = remoteindex + if self.command_index < remoteindex: + self.command_index = remoteindex except wpilib.TableKeyNotDefinedException: pass + #Get the commands data from the table and parse it if there is anything to parse. commandsString = self.table.GetString("frameworkcommands") if not commandsString == "{}": commands = json.loads(commandsString) + + #For each command, check if it is greater than our command_index, and if so, do it! for command in commands: - if int(command) >= self.index: - self.index = int(command) + 1 - self.table.PutNumber("globalCommandIndex", self.index) + if int(command) >= self.command_index: + self.command_index = int(command) + 1 + + #Update the global command index + self.table.PutNumber("globalCommandIndex", self.command_index) + + #Find and run the command try: if commands[command]["command"] == "reload module": module_engine.get_modules(commands[command]["target"]).load() @@ -58,6 +79,7 @@ def run(self): else: logging.error("Framework Remote: No such command - " + commands[command]["command"]) except Exception as e: + #Oops, my bad. :L logging.error("Error running command: " + commands[command]["command"] + ": " + str(e) + "\n" + traceback.format_exc()) time.sleep(.5) diff --git a/modules/mods.conf b/modules/mods.conf index 3fe59cc..9271522 100644 --- a/modules/mods.conf +++ b/modules/mods.conf @@ -16,7 +16,7 @@ modules.christian.debugging modules.christian.basic_navigator [autonomous] -#modules.christian.auto.2-ball +modules.christian.auto.2-ball modules.christian.auto.1-ball modules.christian.auto.stupid_auto diff --git a/robot.py b/robot.py index ccfa46c..51e2034 100644 --- a/robot.py +++ b/robot.py @@ -1,8 +1,14 @@ +""" +This is the starting point for any RobotPy program, and is where robot code would normally be built. + +Instead, we use this to start the framework and control robot state events, such as enabled, disabled, + autonomous, or teleoperated. +""" + #This is a little setup to catch and log errors that crash the interpreter, shamelessly copied from # the internet. #Load just the minimum stuff to start with, so we hopefully won't crash before we have this hook in-place - import sys import os import traceback @@ -20,6 +26,7 @@ def my_excepthook(type, value, tb): sys.excepthook = my_excepthook #Now we actually start doing things! +#---------------------------------- from framework import module_engine, events, filesystem @@ -32,54 +39,73 @@ def my_excepthook(type, value, tb): class RobotTrunk(wpilib.SimpleRobot): def __init__(self): + #This is called on bot start-up. First we call the parent class's init function. super().__init__() + + #Get the logging stuff setup: figure out what the project directory is, + # then calculate all of the paths and start the logs! filesystem.root_dir = os.path.dirname(os.path.realpath(__file__)) filesystem.gen_paths() filesystem.init_logs() + #Load all modules specified under "StartupMods" in the config file modules/mods.conf module_engine.load_startup_mods(os.path.join(filesystem.root_dir, "modules", "mods.conf")) - #Toggle the run event + #Start the run event events.start_event("run", "robot_main") - self.reaper = module_engine.Janitor() - self.reaper.start() + #The janitor is a process who's job is to stop all of the modules when the main thread stops. + # we basically call janitor.delay_death() every time we loop. If that function is not called in about 2 seconds, + # all modules get automatically unloaded. + self.janitor = module_engine.Janitor() + self.janitor.start() def Disabled(self): """Called when the robot is disabled""" + #Start the disabled event events.start_event("disabled", "robot_main") while self.IsDisabled(): - self.reaper.delay_death() + #Let the janitor know we are still alive. + self.janitor.delay_death() wpilib.Wait(0.1) + #Stop the disabled event events.stop_event("disabled", "robot_main") - def Autonomous(self): """Called when autonomous mode is enabled""" + #Start the enabled and autonomous events events.start_event("enabled", "robot_main") events.start_event("autonomous", "robot_main") while self.IsAutonomous() and self.IsEnabled(): - self.reaper.delay_death() + #Let the janitor know we are still alive. + self.janitor.delay_death() wpilib.Wait(0.1) + #Stop the autonomous and enabled events events.stop_event("enabled", "robot_main") events.stop_event("autonomous", "robot_main") def OperatorControl(self): """Called when operation control mode is enabled""" + #Start the enabled and teleoperated events events.start_event("enabled", "robot_main") events.start_event("teleoperated", "robot_main") while self.IsOperatorControl() and self.IsEnabled(): - self.reaper.delay_death() - wpilib.Wait(0.04) + #Let the janitor know we are still alive. + self.janitor.delay_death() + wpilib.Wait(0.1) + #Stop the enabled and teleoperated events events.stop_event("enabled", "robot_main") events.stop_event("teleoperated", "robot_main") + +#This is the actual entry-point of the application. def run(): """Main loop""" robot = RobotTrunk() robot.StartCompetition() return robot +#This bit of code is used to configure pyfrc, if used. if __name__ == '__main__': import physics wpilib.internal.physics_controller.setup(physics) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 4b31e84..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'christian' diff --git a/tests/pyfrc_test.py b/tests/pyfrc_test.py deleted file mode 100644 index 3071d3e..0000000 --- a/tests/pyfrc_test.py +++ /dev/null @@ -1,7 +0,0 @@ - -''' - This test module imports tests that come with pyfrc, and can be used - to test basic functionality of just about any robot. -''' - -from pyfrc.tests.simple import * \ No newline at end of file