Merge "Add led_indicator to 2023"
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index c547833..e308954 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -188,7 +188,7 @@
data = glob([
"field_images/*.png",
"field_images/*.svg",
- ]),
+ ]) + ["//third_party/y2023/field:pictures"],
legacy_create_init = False,
target_compatible_with = ["@platforms//cpu:x86_64"],
visibility = ["//visibility:public"],
diff --git a/frc971/control_loops/python/constants.py b/frc971/control_loops/python/constants.py
index b2d9d57..7bc45db 100644
--- a/frc971/control_loops/python/constants.py
+++ b/frc971/control_loops/python/constants.py
@@ -15,6 +15,9 @@
ROBOT_SIDE_TO_HATCH_PANEL = 0.1
HATCH_PANEL_WIDTH = 0.4826
+# field_id is either just a file prefix for a .png in field_images/ or is a
+# full path preceded by // specifying a location relative to the root of the
+# repository.
FieldType = namedtuple(
'Field', ['name', 'tags', 'year', 'width', 'length', 'robot', 'field_id'])
RobotType = namedtuple("Robot", ['width', 'length'])
@@ -33,6 +36,7 @@
Robot2020 = RobotType(width=0.8128, length=0.8636) # 32 in x 34 in
Robot2021 = Robot2020
Robot2022 = RobotType(width=0.8763, length=0.96647)
+Robot2023 = RobotType(width=0.6061, length=0.77581)
FIELDS = {
"2019 Field":
@@ -115,9 +119,17 @@
length=8.2296,
robot=Robot2022,
field_id="2022"),
+ "2023 Field":
+ FieldType("2023 Field",
+ tags=[],
+ year=2023,
+ width=16.59255,
+ length=8.10895,
+ robot=Robot2023,
+ field_id="//third_party/y2023/field/2023.png"),
}
-FIELD = FIELDS["2022 Field"]
+FIELD = FIELDS["2023 Field"]
def get_json_folder(field):
@@ -125,6 +137,8 @@
return "y2020/actors/splines"
elif field.year == 2022:
return "y2022/actors/splines"
+ elif field.year == 2023:
+ return "y2023/autonomous/splines"
else:
return "frc971/control_loops/python/spline_jsons"
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index 2b55e94..86777e5 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -81,9 +81,13 @@
def set_field(self, field):
self.field = field
try:
- self.field_png = cairo.ImageSurface.create_from_png(
- "frc971/control_loops/python/field_images/" +
- self.field.field_id + ".png")
+ if self.field.field_id.startswith('//'):
+ self.field_png = cairo.ImageSurface.create_from_png(
+ self.field.field_id[2:])
+ else:
+ self.field_png = cairo.ImageSurface.create_from_png(
+ "frc971/control_loops/python/field_images/" +
+ self.field.field_id + ".png")
except cairo.Error:
self.field_png = None
diff --git a/frc971/control_loops/python/spline_graph.py b/frc971/control_loops/python/spline_graph.py
index fbe43bf..ce5efe1 100755
--- a/frc971/control_loops/python/spline_graph.py
+++ b/frc971/control_loops/python/spline_graph.py
@@ -107,7 +107,7 @@
self.file_name_box = Gtk.Entry()
self.file_name_box.set_size_request(50, 40)
- self.file_name_box.set_text(FIELD.field_id + ".json")
+ self.file_name_box.set_text("test.json")
self.file_name_box.set_editable(True)
self.long_input = Gtk.SpinButton()
diff --git a/frc971/vision/charuco_lib.cc b/frc971/vision/charuco_lib.cc
index ac708f6..89ebe19 100644
--- a/frc971/vision/charuco_lib.cc
+++ b/frc971/vision/charuco_lib.cc
@@ -24,6 +24,8 @@
DEFINE_uint32(
min_charucos, 10,
"The mininum number of aruco targets in charuco board required to match.");
+DEFINE_uint32(min_id, 12, "Minimum valid charuco id");
+DEFINE_uint32(max_id, 15, "Minimum valid charuco id");
DEFINE_bool(visualize, false, "Whether to visualize the resulting data.");
DEFINE_bool(
draw_axes, false,
@@ -426,24 +428,47 @@
square_length_ / marker_length_,
diamond_corners, diamond_ids);
- // Check to see if we found any diamond targets
- if (diamond_ids.size() > 0) {
- cv::aruco::drawDetectedDiamonds(rgb_image, diamond_corners,
- diamond_ids);
+ // Check that we have exactly one charuco diamond. For calibration, we
+ // can constrain things so that this is the case
+ if (diamond_ids.size() == 1) {
+ // TODO<Jim>: Could probably make this check more general than requiring
+ // range of ids
+ bool all_valid_ids = true;
+ for (uint i = 0; i < 4; i++) {
+ uint id = diamond_ids[0][i];
+ if ((id < FLAGS_min_id) || (id > FLAGS_max_id)) {
+ all_valid_ids = false;
+ LOG(INFO) << "Got invalid charuco id: " << id;
+ }
+ }
+ if (all_valid_ids) {
+ cv::aruco::drawDetectedDiamonds(rgb_image, diamond_corners,
+ diamond_ids);
- // estimate pose for diamonds doesn't return valid, so marking true
- valid = true;
- std::vector<cv::Vec3d> rvecs, tvecs;
- cv::aruco::estimatePoseSingleMarkers(
- diamond_corners, square_length_, calibration_.CameraIntrinsics(),
- calibration_.CameraDistCoeffs(), rvecs, tvecs);
- DrawTargetPoses(rgb_image, rvecs, tvecs);
+ // estimate pose for diamonds doesn't return valid, so marking true
+ valid = true;
+ std::vector<cv::Vec3d> rvecs, tvecs;
+ cv::aruco::estimatePoseSingleMarkers(
+ diamond_corners, square_length_, calibration_.CameraIntrinsics(),
+ calibration_.CameraDistCoeffs(), rvecs, tvecs);
+ DrawTargetPoses(rgb_image, rvecs, tvecs);
- PackPoseResults(rvecs, tvecs, &rvecs_eigen, &tvecs_eigen);
- result_ids = diamond_ids;
- result_corners = diamond_corners;
+ PackPoseResults(rvecs, tvecs, &rvecs_eigen, &tvecs_eigen);
+ result_ids = diamond_ids;
+ result_corners = diamond_corners;
+ } else {
+ LOG(INFO) << "Not all charuco ids were valid, so skipping";
+ }
} else {
- VLOG(2) << "Found aruco markers, but no charuco diamond targets";
+ if (diamond_ids.size() == 0) {
+ // OK to not see any markers sometimes
+ VLOG(2)
+ << "Found aruco markers, but no valid charuco diamond targets";
+ } else {
+ // But should never detect multiple
+ LOG(FATAL) << "Found multiple charuco diamond markers. Should only "
+ "be one";
+ }
}
} else {
LOG(FATAL) << "Unknown target type: "
diff --git a/scouting/deploy/scouting.service b/scouting/deploy/scouting.service
index 94582cd..2c55676 100644
--- a/scouting/deploy/scouting.service
+++ b/scouting/deploy/scouting.service
@@ -9,7 +9,7 @@
WorkingDirectory=/opt/frc971/scouting_server
Environment=RUNFILES_DIR=/opt/frc971/scouting_server
# Add "julia" to the PATH.
-Environment=PATH=/opt/frc971/scouting/julia_runtime/bin:/usr/local/bin:/usr/bin:/bin
+Environment=PATH=/opt/frc971/julia_runtime/bin:/usr/local/bin:/usr/bin:/bin
# Use the Julia cache set up by the frc971-scouting-julia package.
Environment=JULIA_DEPOT_PATH=/var/frc971/scouting/julia_depot/
Environment=JULIA_PROJECT=/opt/frc971/julia_manifest
diff --git a/y2023/www/2023.png b/third_party/y2023/field/2023.png
similarity index 100%
rename from y2023/www/2023.png
rename to third_party/y2023/field/2023.png
Binary files differ
diff --git a/third_party/y2023/field/BUILD b/third_party/y2023/field/BUILD
index e7ba4e2..de1d382 100644
--- a/third_party/y2023/field/BUILD
+++ b/third_party/y2023/field/BUILD
@@ -1,11 +1,14 @@
-# Pictures from FIRST modified by Tea Fazio.
-# https://firstfrc.blob.core.windows.net/frc2023/Manual/2023FRCGameManual.pdf
-# Copyright 2023 FIRST
-
filegroup(
name = "pictures",
srcs = [
- "field.jpg",
- ],
+ # Picture from the FIRST inspires field drawings.
+ # https://www.firstinspires.org/robotics/frc/playing-field
+ # Copyright 2023 FIRST
+ "2023.png",
+ # Picture from FIRST modified by Tea Fazio.
+ # https://firstfrc.blob.core.windows.net/frc2023/Manual/2023FRCGameManual.pdf
+ # Copyright 2023 FIRST
+ "field.jpg",
+ ],
visibility = ["//visibility:public"],
)
diff --git a/y2023/BUILD b/y2023/BUILD
index a6fec71..d897c06 100644
--- a/y2023/BUILD
+++ b/y2023/BUILD
@@ -81,6 +81,7 @@
"//aos/network:web_proxy_main",
"//aos/starter:irq_affinity",
"//y2023/vision:camera_reader",
+ "//y2023/vision:image_logger",
"//aos/events/logging:logger_main",
"//y2023/vision:game_pieces_detector",
],
diff --git a/y2023/autonomous/splines/README.md b/y2023/autonomous/splines/README.md
new file mode 100644
index 0000000..c655416
--- /dev/null
+++ b/y2023/autonomous/splines/README.md
@@ -0,0 +1,3 @@
+# Spline Descriptions
+This folder contains reference material for what each spline does
+
diff --git a/y2023/autonomous/splines/spline_1.json b/y2023/autonomous/splines/spline_1.json
new file mode 100644
index 0000000..e6e24ed
--- /dev/null
+++ b/y2023/autonomous/splines/spline_1.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [-6.450466090790975, -6.021160733648118, -5.591855376505261, -3.3652785421474576, -2.7749836760760287, -1.7732711760760287], "spline_y": [0.9493418961252269, 0.9493418961252269, 0.9314541729109411, 0.5975544198946889, 0.5975544198946889, 0.5796666966804032], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 10}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline_2.json b/y2023/autonomous/splines/spline_2.json
new file mode 100644
index 0000000..032a081
--- /dev/null
+++ b/y2023/autonomous/splines/spline_2.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [-1.7732711760760287, -2.7749836760760287, -3.3652785421474576, -5.591855376505261, -6.021160733648118, -6.450466090790975], "spline_y": [0.5796666966804032, 0.5975544198946889, 0.5975544198946889, 0.40105062588141127, 0.41893834909569705, 0.41893834909569705], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 10}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline_3.json b/y2023/autonomous/splines/spline_3.json
new file mode 100644
index 0000000..4ca06a8
--- /dev/null
+++ b/y2023/autonomous/splines/spline_3.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [-6.450466090790975, -6.021160733648118, -5.591855376505261, -3.605574338541678, -3.0269522872367363, -1.6929070022836754], "spline_y": [0.41893834909569705, 0.41893834909569705, 0.40105062588141127, 0.5475210271634618, 0.515375357646521, -0.3364848845524211], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 10}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline_4.json b/y2023/autonomous/splines/spline_4.json
new file mode 100644
index 0000000..a56d24e
--- /dev/null
+++ b/y2023/autonomous/splines/spline_4.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [-1.6929070022836754, -3.0269522872367363, -3.605574338541678, -5.591855376505261, -6.021160733648118, -6.450466090790975], "spline_y": [-0.3364848845524211, 0.515375357646521, 0.5475210271634618, 0.40105062588141127, 0.41893834909569705, 0.41893834909569705], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 10}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/spline_5.json b/y2023/autonomous/splines/spline_5.json
new file mode 100644
index 0000000..4eee822
--- /dev/null
+++ b/y2023/autonomous/splines/spline_5.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [-6.450466090790975, -6.448323209188465, -6.468936183333308, -5.63485982210851, -5.224861021501398, -4.383040925048516], "spline_y": [0.41893834909569705, -0.2089748700255587, -1.0435424455861884, -0.6390449134590346, -0.8779649804883709, -0.8766708249234052], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2}, {"constraint_type": "VOLTAGE", "value": 10}]}
\ No newline at end of file
diff --git a/y2023/autonomous/splines/test_spline.json b/y2023/autonomous/splines/test_spline.json
index 7672596..733d516 100644
--- a/y2023/autonomous/splines/test_spline.json
+++ b/y2023/autonomous/splines/test_spline.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [6.22420997455908, 6.1347950111487386, 6.080329974810555, 6.023577036950107, 5.9617203084135255, 5.81469341092744], "spline_y": [-2.63127733767268, -2.63127733767268, -2.656484781970896, -2.656484781970896, -2.6668098529078925, -2.6448802602350456], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 2}, {"constraint_type": "LATERAL_ACCELERATION", "value": 1}, {"constraint_type": "VOLTAGE", "value": 4}]}
+{"spline_count": 1, "spline_x": [0, 0.4, 0.4, 0.6, 0.6, 1.0], "spline_y": [0, 0, 0.05, 0.1, 0.15, 0.15], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 1}, {"constraint_type": "LATERAL_ACCELERATION", "value": 1}, {"constraint_type": "VOLTAGE", "value": 2}]}
diff --git a/y2023/constants/simulated_constants_sender.cc b/y2023/constants/simulated_constants_sender.cc
index f18c3c1..8bfc17d 100644
--- a/y2023/constants/simulated_constants_sender.cc
+++ b/y2023/constants/simulated_constants_sender.cc
@@ -5,7 +5,7 @@
#include "frc971/constants/constants_sender_lib.h"
namespace y2023 {
-void SendSimulationConstants(aos::SimulatedEventLoopFactory *factory, int team,
+bool SendSimulationConstants(aos::SimulatedEventLoopFactory *factory, int team,
std::string constants_path) {
for (const aos::Node *node : factory->nodes()) {
std::unique_ptr<aos::EventLoop> event_loop =
@@ -13,5 +13,6 @@
frc971::constants::ConstantSender<Constants, ConstantsList> sender(
event_loop.get(), constants_path, team, "/constants");
}
+ return true;
}
} // namespace y2023
diff --git a/y2023/constants/simulated_constants_sender.h b/y2023/constants/simulated_constants_sender.h
index 44a868c..096ee20 100644
--- a/y2023/constants/simulated_constants_sender.h
+++ b/y2023/constants/simulated_constants_sender.h
@@ -5,7 +5,9 @@
#include "aos/testing/path.h"
namespace y2023 {
-void SendSimulationConstants(
+// Returns true, to allow this to be easily called in the initializer list of a
+// constructor.
+bool SendSimulationConstants(
aos::SimulatedEventLoopFactory *factory, int team,
std::string constants_path =
aos::testing::ArtifactPath("y2023/constants/test_constants.json"));
diff --git a/y2023/constants/test_data/test_team.json b/y2023/constants/test_data/test_team.json
index f09b23e..a1e77af 100644
--- a/y2023/constants/test_data/test_team.json
+++ b/y2023/constants/test_data/test_team.json
@@ -14,5 +14,19 @@
}
],
"target_map": {% include 'y2023/constants/test_data/target_map.json' %},
- "scoring_map": {% include 'y2023/constants/test_data/scoring_map.json' %}
+ "scoring_map": {% include 'y2023/constants/test_data/scoring_map.json' %},
+ "robot": {
+ "tof": {
+ "interpolation_table": [
+ {
+ "tof_reading": 0.1,
+ "lateral_position": 0.2
+ },
+ {
+ "tof_reading": 0.90,
+ "lateral_position": -0.2
+ }
+ ]
+ }
+ }
}
diff --git a/y2023/control_loops/drivetrain/target_selector.cc b/y2023/control_loops/drivetrain/target_selector.cc
index 16a0090..1b70ca1 100644
--- a/y2023/control_loops/drivetrain/target_selector.cc
+++ b/y2023/control_loops/drivetrain/target_selector.cc
@@ -24,16 +24,6 @@
CHECK(constants_fetcher_.constants().has_scoring_map());
CHECK(constants_fetcher_.constants().scoring_map()->has_red());
CHECK(constants_fetcher_.constants().scoring_map()->has_blue());
- event_loop->MakeWatcher(
- "/superstructure",
- [this](const y2023::control_loops::superstructure::Position &msg) {
- // Technically this means that even if we have a cube we are relying on
- // getting a Position message before updating the game_piece_position_
- // to zero. But if we aren't getting position messages, then things are
- // very broken.
- game_piece_position_ =
- LateralOffsetForTimeOfFlight(msg.cone_position());
- });
event_loop->AddPhasedLoop(
[this](int) {
@@ -172,42 +162,15 @@
} else {
drive_direction_ = Side::DONT_CARE;
}
+ // Only update the game piece position when we reassign the target.
+ superstructure_status_fetcher_.Fetch();
+ if (superstructure_status_fetcher_.get() != nullptr) {
+ game_piece_position_ =
+ superstructure_status_fetcher_->game_piece_position();
+ }
}
CHECK(target_pose_.has_value());
return true;
}
-// TODO: Maybe this already handles field side correctly? Unsure if the line
-// follower ends up having positive as being robot frame relative or robot
-// direction relative...
-double TargetSelector::LateralOffsetForTimeOfFlight(double reading) {
- superstructure_status_fetcher_.Fetch();
- if (superstructure_status_fetcher_.get() != nullptr) {
- switch (superstructure_status_fetcher_->game_piece()) {
- case vision::Class::NONE:
- case vision::Class::CUBE:
- return 0.0;
- case vision::Class::CONE_UP:
- // execute logic below.
- break;
- case vision::Class::CONE_DOWN:
- // execute logic below.
- break;
- }
- } else {
- return 0.0;
- }
- const TimeOfFlight *calibration =
- CHECK_NOTNULL(constants_fetcher_.constants().robot()->tof());
- // TODO(james): Use a generic interpolation table class.
- auto table = CHECK_NOTNULL(calibration->interpolation_table());
- CHECK_EQ(2u, table->size());
- double x1 = table->Get(0)->tof_reading();
- double x2 = table->Get(1)->tof_reading();
- double y1 = table->Get(0)->lateral_position();
- double y2 = table->Get(1)->lateral_position();
- return frc971::shooter_interpolation::Blend((reading - x1) / (x2 - x1), y1,
- y2);
-}
-
} // namespace y2023::control_loops::drivetrain
diff --git a/y2023/control_loops/drivetrain/target_selector.h b/y2023/control_loops/drivetrain/target_selector.h
index bed56ce..5e7f015 100644
--- a/y2023/control_loops/drivetrain/target_selector.h
+++ b/y2023/control_loops/drivetrain/target_selector.h
@@ -47,8 +47,6 @@
private:
void UpdateAlliance();
- // Returns the Y coordinate of a game piece given the time-of-flight reading.
- double LateralOffsetForTimeOfFlight(double reading);
std::optional<Pose> target_pose_;
aos::Fetcher<aos::JoystickState> joystick_state_fetcher_;
aos::Fetcher<TargetSelectorHint> hint_fetcher_;
diff --git a/y2023/control_loops/python/graph_edit.py b/y2023/control_loops/python/graph_edit.py
index f18a0b6..e47128a 100644
--- a/y2023/control_loops/python/graph_edit.py
+++ b/y2023/control_loops/python/graph_edit.py
@@ -13,7 +13,7 @@
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, Gtk
import cairo
-from y2023.control_loops.python.graph_tools import to_theta, to_xy, alpha_blend, shift_angles
+from y2023.control_loops.python.graph_tools import to_theta, to_xy, alpha_blend, shift_angles, get_xy
from y2023.control_loops.python.graph_tools import l1, l2, joint_center
from y2023.control_loops.python.graph_tools import DRIVER_CAM_POINTS
from y2023.control_loops.python import graph_paths
@@ -192,10 +192,45 @@
DRIVER_CAM_HEIGHT = DRIVER_CAM_POINTS[-1][1] - DRIVER_CAM_POINTS[0][1]
+class SegmentSelector(basic_window.BaseWindow):
+
+ def __init__(self, segments):
+ super(SegmentSelector, self).__init__()
+
+ self.window = Gtk.Window()
+ self.window.set_title("Segment Selector")
+
+ self.segments = segments
+
+ self.segment_store = Gtk.ListStore(int, str)
+
+ for i, segment in enumerate(segments):
+ self.segment_store.append([i, segment.name])
+
+ self.segment_box = Gtk.ComboBox.new_with_model_and_entry(
+ self.segment_store)
+ self.segment_box.connect("changed", self.on_combo_changed)
+ self.segment_box.set_entry_text_column(1)
+
+ self.current_path_index = None
+
+ self.window.add(self.segment_box)
+ self.window.show_all()
+
+ def on_combo_changed(self, combo):
+ iter = combo.get_active_iter()
+
+ if iter is not None:
+ model = combo.get_model()
+ id, name = model[iter][:2]
+ print("Selected: ID=%d, name=%s" % (id, name))
+ self.current_path_index = id
+
+
# Create a GTK+ widget on which we will draw using Cairo
class ArmUi(basic_window.BaseWindow):
- def __init__(self):
+ def __init__(self, segments):
super(ArmUi, self).__init__()
self.window = Gtk.Window()
@@ -221,7 +256,7 @@
self.circular_index_select = 1
# Extra stuff for drawing lines.
- self.segments = []
+ self.segments = segments
self.prev_segment_pt = None
self.now_segment_pt = None
self.spline_edit = 0
@@ -247,6 +282,13 @@
[DRIVER_CAM_X, DRIVER_CAM_Y],
DRIVER_CAM_WIDTH, DRIVER_CAM_HEIGHT)
+ self.segment_selector = SegmentSelector(self.segments)
+ self.segment_selector.show()
+
+ self.show_indicators = True
+ # Lets you only view selected path
+ self.view_current = False
+
def _do_button_press_internal(self, event):
o_x = event.x
o_y = event.y
@@ -295,6 +337,8 @@
# Handle the expose-event by drawing
def handle_draw(self, cr):
# use "with px(cr): blah;" to transform to pixel coordinates.
+ if self.segment_selector.current_path_index is not None:
+ self.index = self.segment_selector.current_path_index
# Fill the background color of the window with grey
set_color(cr, palette["GREY"])
@@ -370,21 +414,43 @@
self.outline.draw_theta(cr)
set_color(cr, Color(0.0, 0.5, 1.0))
- for i in range(len(self.segments)):
- color = None
- if i == self.index:
- continue
- color = [0, random.random(), 1]
- random.shuffle(color)
- set_color(cr, Color(color[0], color[1], color[2]))
- self.segments[i].DrawTo(cr, self.theta_version)
- with px(cr):
- cr.stroke()
+ if not self.view_current:
+ for i in range(len(self.segments)):
+ color = None
+ if i == self.index:
+ continue
+ color = [0, random.random(), 1]
+ random.shuffle(color)
+ set_color(cr, Color(color[0], color[1], color[2]))
+ self.segments[i].DrawTo(cr, self.theta_version)
+
+ with px(cr):
+ cr.stroke()
# Draw current spline in black
color = [0, 0, 0]
set_color(cr, Color(color[0], color[1], color[2]))
self.segments[self.index].DrawTo(cr, self.theta_version)
+
+ with px(cr):
+ cr.stroke()
+ control1 = get_xy(self.segments[self.index].control1)
+ control2 = get_xy(self.segments[self.index].control2)
+
+ if self.theta_version:
+ control1 = shift_angles(self.segments[self.index].control1)
+ control2 = shift_angles(self.segments[self.index].control2)
+
+ if self.show_indicators:
+ set_color(cr, Color(1.0, 0.0, 1.0))
+ cr.move_to(control1[0] + 0.02, control1[1])
+ cr.arc(control1[0], control1[1], 0.02, 0, 2.0 * np.pi)
+ with px(cr):
+ cr.stroke()
+ set_color(cr, Color(1.0, 0.7, 0.0))
+ cr.move_to(control2[0] + 0.02, control2[1])
+ cr.arc(control2[0], control2[1], 0.02, 0, 2.0 * np.pi)
+
with px(cr):
cr.stroke()
@@ -498,12 +564,18 @@
print("Switched to segment:", self.segments[self.index].name)
self.segments[self.index].Print(graph_paths.points)
+ elif keyval == Gdk.KEY_i:
+ self.show_indicators = not self.show_indicators
+
elif keyval == Gdk.KEY_n:
self.index += 1
self.index = self.index % len(self.segments)
print("Switched to segment:", self.segments[self.index].name)
self.segments[self.index].Print(graph_paths.points)
+ elif keyval == Gdk.KEY_l:
+ self.view_current = not self.view_current
+
elif keyval == Gdk.KEY_t:
# Toggle between theta and xy renderings
if self.theta_version:
@@ -563,8 +635,7 @@
self.redraw()
-arm_ui = ArmUi()
-arm_ui.segments = graph_paths.segments
+arm_ui = ArmUi(graph_paths.segments)
print('Starting with segment: ', arm_ui.segments[arm_ui.index].name)
arm_ui.segments[arm_ui.index].Print(graph_paths.points)
basic_window.RunApp()
diff --git a/y2023/control_loops/superstructure/BUILD b/y2023/control_loops/superstructure/BUILD
index 6df0830..e5bc830 100644
--- a/y2023/control_loops/superstructure/BUILD
+++ b/y2023/control_loops/superstructure/BUILD
@@ -101,9 +101,13 @@
":superstructure_status_fbs",
"//aos:flatbuffer_merge",
"//aos/events:event_loop",
+ "//frc971/constants:constants_sender_lib",
"//frc971/control_loops:control_loop",
"//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+ "//frc971/shooter_interpolation:interpolation",
"//y2023:constants",
+ "//y2023/constants:constants_fbs",
+ "//y2023/constants:simulated_constants_sender",
"//y2023/control_loops/drivetrain:drivetrain_can_position_fbs",
"//y2023/control_loops/superstructure/arm",
"//y2023/control_loops/superstructure/arm:arm_trajectories_fbs",
diff --git a/y2023/control_loops/superstructure/arm/arm.cc b/y2023/control_loops/superstructure/arm/arm.cc
index 6cd8d0d..fd3028c 100644
--- a/y2023/control_loops/superstructure/arm/arm.cc
+++ b/y2023/control_loops/superstructure/arm/arm.cc
@@ -292,7 +292,6 @@
follower_.Update(X_hat, disable, constants::Values::kArmDt(), vmax_,
max_operating_voltage);
- AOS_LOG(INFO, "Max voltage: %f\n", max_operating_voltage);
arm_ekf_.Predict(follower_.U().head<2>(), constants::Values::kArmDt());
roll_joint_loop_.UpdateObserver(follower_.U().tail<1>(),
diff --git a/y2023/control_loops/superstructure/superstructure.cc b/y2023/control_loops/superstructure/superstructure.cc
index 9b9b119..0f19a1e 100644
--- a/y2023/control_loops/superstructure/superstructure.cc
+++ b/y2023/control_loops/superstructure/superstructure.cc
@@ -3,6 +3,7 @@
#include "aos/events/event_loop.h"
#include "aos/flatbuffer_merge.h"
#include "aos/network/team_number.h"
+#include "frc971/shooter_interpolation/interpolation.h"
#include "frc971/zeroing/wrap.h"
#include "y2023/control_loops/superstructure/arm/arm_trajectories_generated.h"
@@ -27,6 +28,7 @@
: frc971::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
name),
values_(values),
+ constants_fetcher_(event_loop),
drivetrain_status_fetcher_(
event_loop->MakeFetcher<frc971::control_loops::drivetrain::Status>(
"/drivetrain")),
@@ -100,6 +102,11 @@
status_builder.add_end_effector_state(end_effector_.state());
// TODO(milind): integrate this with ML game piece detection somehow
status_builder.add_game_piece(end_effector_.game_piece());
+ const std::optional<double> game_piece_position =
+ LateralOffsetForTimeOfFlight(position->cone_position());
+ if (game_piece_position.has_value()) {
+ status_builder.add_game_piece_position(game_piece_position.value());
+ }
(void)status->Send(status_builder.Finish());
}
@@ -110,6 +117,36 @@
: 0.0);
}
+std::optional<double> Superstructure::LateralOffsetForTimeOfFlight(
+ double reading) {
+ switch (end_effector_.game_piece()) {
+ case vision::Class::NONE:
+ return std::nullopt;
+ case vision::Class::CUBE:
+ // Cubes are definitionally centered.
+ return 0.0;
+ case vision::Class::CONE_UP:
+ case vision::Class::CONE_DOWN:
+ // execute logic below.
+ break;
+ }
+ constexpr double kInvalidReading = 0.93;
+ if (reading > kInvalidReading) {
+ return std::nullopt;
+ }
+ const TimeOfFlight *calibration = CHECK_NOTNULL(
+ CHECK_NOTNULL(constants_fetcher_.constants().robot())->tof());
+ // TODO(james): Use a generic interpolation table class.
+ auto table = CHECK_NOTNULL(calibration->interpolation_table());
+ CHECK_EQ(2u, table->size());
+ double x1 = table->Get(0)->tof_reading();
+ double x2 = table->Get(1)->tof_reading();
+ double y1 = table->Get(0)->lateral_position();
+ double y2 = table->Get(1)->lateral_position();
+ return frc971::shooter_interpolation::Blend((reading - x1) / (x2 - x1), y1,
+ y2);
+}
+
} // namespace superstructure
} // namespace control_loops
} // namespace y2023
diff --git a/y2023/control_loops/superstructure/superstructure.h b/y2023/control_loops/superstructure/superstructure.h
index 0c8b2c0..1100b86 100644
--- a/y2023/control_loops/superstructure/superstructure.h
+++ b/y2023/control_loops/superstructure/superstructure.h
@@ -3,9 +3,11 @@
#include "aos/events/event_loop.h"
#include "aos/json_to_flatbuffer.h"
+#include "frc971/constants/constants_sender_lib.h"
#include "frc971/control_loops/control_loop.h"
#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
#include "y2023/constants.h"
+#include "y2023/constants/constants_generated.h"
#include "y2023/control_loops/drivetrain/drivetrain_can_position_generated.h"
#include "y2023/control_loops/superstructure/arm/arm.h"
#include "y2023/control_loops/superstructure/arm/arm_trajectories_generated.h"
@@ -63,7 +65,11 @@
aos::Sender<Status>::Builder *status) override;
private:
+ // Returns the Y coordinate of a game piece given the time-of-flight reading.
+ std::optional<double> LateralOffsetForTimeOfFlight(double reading);
+
std::shared_ptr<const constants::Values> values_;
+ frc971::constants::ConstantsFetcher<Constants> constants_fetcher_;
aos::Fetcher<frc971::control_loops::drivetrain::Status>
drivetrain_status_fetcher_;
diff --git a/y2023/control_loops/superstructure/superstructure_lib_test.cc b/y2023/control_loops/superstructure/superstructure_lib_test.cc
index e2524c1..c1c20a3 100644
--- a/y2023/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2023/control_loops/superstructure/superstructure_lib_test.cc
@@ -8,6 +8,7 @@
#include "frc971/control_loops/subsystem_simulator.h"
#include "frc971/control_loops/team_number_test_environment.h"
#include "gtest/gtest.h"
+#include "y2023/constants/simulated_constants_sender.h"
#include "y2023/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
#include "y2023/control_loops/superstructure/roll/integral_roll_plant.h"
#include "y2023/control_loops/superstructure/superstructure.h"
@@ -240,8 +241,7 @@
position_builder.add_wrist(wrist_offset);
position_builder.add_end_effector_cube_beam_break(
end_effector_cube_beam_break_);
- // TODO(milind): put into our state
- position_builder.add_cone_position(0.95);
+ position_builder.add_cone_position(cone_position_);
CHECK_EQ(builder.Send(position_builder.Finish()),
aos::RawSender::Error::kOk);
}
@@ -250,6 +250,10 @@
end_effector_cube_beam_break_ = triggered;
}
+ void set_cone_position(double cone_position) {
+ cone_position_ = cone_position;
+ }
+
private:
::aos::EventLoop *event_loop_;
const chrono::nanoseconds dt_;
@@ -265,6 +269,7 @@
::aos::Fetcher<Output> superstructure_output_fetcher_;
bool first_ = true;
+ double cone_position_ = 0.95;
};
class SuperstructureTest : public ::frc971::testing::ControlLoopTest {
@@ -274,6 +279,8 @@
aos::configuration::ReadConfig("y2023/aos_config.json"),
std::chrono::microseconds(5050)),
values_(std::make_shared<constants::Values>(constants::MakeValues())),
+ simulated_constants_dummy_(SendSimulationConstants(
+ event_loop_factory(), 7971, "y2023/constants/test_constants.json")),
roborio_(aos::configuration::GetNode(configuration(), "roborio")),
logger_pi_(aos::configuration::GetNode(configuration(), "logger")),
arm_trajectories_(superstructure::Superstructure::GetArmTrajectories(
@@ -410,6 +417,7 @@
}
std::shared_ptr<const constants::Values> values_;
+ const bool simulated_constants_dummy_;
const aos::Node *const roborio_;
const aos::Node *const logger_pi_;
@@ -560,6 +568,42 @@
CheckIfZeroed();
}
+// Tests that the cone position ocnversion works.
+TEST_F(SuperstructureTest, ConePositionConversion) {
+ // Get ourselves into CONE mode.
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_arm_goal_position(arm::NeutralIndex());
+ goal_builder.add_trajectory_override(false);
+ goal_builder.add_roller_goal(RollerGoal::INTAKE_CONE_UP);
+ builder.CheckOk(builder.Send(goal_builder.Finish()));
+ }
+ superstructure_plant_.set_cone_position(1.0);
+ RunFor(chrono::seconds(1));
+ // Game piece position should not be populated when invalid.
+ ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
+ EXPECT_EQ(vision::Class::CONE_UP, superstructure_status_fetcher_->game_piece());
+ EXPECT_FALSE(superstructure_status_fetcher_->has_game_piece_position());
+
+ // And then send a valid cone position.
+ superstructure_plant_.set_cone_position(0.5);
+ RunFor(chrono::seconds(1));
+ ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
+ EXPECT_EQ(vision::Class::CONE_UP, superstructure_status_fetcher_->game_piece());
+ EXPECT_TRUE(superstructure_status_fetcher_->has_game_piece_position());
+ EXPECT_FLOAT_EQ(0.0, superstructure_status_fetcher_->game_piece_position());
+
+ superstructure_plant_.set_cone_position(0.1);
+ RunFor(chrono::seconds(1));
+ ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
+ EXPECT_EQ(vision::Class::CONE_UP, superstructure_status_fetcher_->game_piece());
+ EXPECT_TRUE(superstructure_status_fetcher_->has_game_piece_position());
+ EXPECT_FLOAT_EQ(0.2, superstructure_status_fetcher_->game_piece_position());
+}
+
class SuperstructureBeambreakTest
: public SuperstructureTest,
public ::testing::WithParamInterface<vision::Class> {
diff --git a/y2023/control_loops/superstructure/superstructure_position.fbs b/y2023/control_loops/superstructure/superstructure_position.fbs
index 3c7a33b..83ca2b6 100644
--- a/y2023/control_loops/superstructure/superstructure_position.fbs
+++ b/y2023/control_loops/superstructure/superstructure_position.fbs
@@ -47,7 +47,13 @@
// Positive position would be upwards
wrist:frc971.AbsolutePosition (id: 1);
- // If this is true, the cone beam break is triggered.
+ // Estimated position of a cone in the gripper from the time-of-flight
+ // sensors.
+ // If greater than 0.9, indicates that we cannot see a cone.
+ // Will be larger when the cone is farther forwards on the robot when
+ // the wrist and arm positions are all at zero (this will typically mean
+ // that it is larger when the cone is to the robot's current right when
+ // trying to core).
cone_position:double (id: 2);
// If this is true, the cube beam break is triggered.
diff --git a/y2023/control_loops/superstructure/superstructure_status.fbs b/y2023/control_loops/superstructure/superstructure_status.fbs
index 5381b0a..8d74bfe 100644
--- a/y2023/control_loops/superstructure/superstructure_status.fbs
+++ b/y2023/control_loops/superstructure/superstructure_status.fbs
@@ -89,7 +89,15 @@
wrist:frc971.control_loops.AbsoluteEncoderProfiledJointStatus (id: 3);
end_effector_state:EndEffectorState (id: 4);
+
game_piece:vision.Class (id: 5);
+
+ // Indicates the current lateral position of the game piece, in meters.
+ // This number will be zero when the game piece is centered, and positive if
+ // the arm + wrist are all at zero and the game piece is towards the back
+ // of the robot. This will typically mean that positive is to the robot's
+ // left
+ game_piece_position:double (id: 6);
}
root_type Status;
diff --git a/y2023/joystick_reader.cc b/y2023/joystick_reader.cc
index 68fe6af..4c4b1e3 100644
--- a/y2023/joystick_reader.cc
+++ b/y2023/joystick_reader.cc
@@ -43,7 +43,7 @@
namespace joysticks {
// TODO(milind): add correct locations
-const ButtonLocation kScore(4, 4);
+const ButtonLocation kDriverSpit(2, 1);
const ButtonLocation kSpit(4, 13);
const ButtonLocation kHighConeScoreLeft(4, 14);
@@ -68,6 +68,7 @@
const ButtonLocation kBack(4, 12);
const ButtonLocation kWrist(4, 10);
+const ButtonLocation kStayIn(3, 4);
namespace superstructure = y2023::control_loops::superstructure;
namespace arm = superstructure::arm;
@@ -386,9 +387,12 @@
// And, pull the bits out of it.
if (current_setpoint_ != nullptr) {
- wrist_goal = current_setpoint_->wrist_goal;
- arm_goal_position_ = current_setpoint_->index;
- score_wrist_goal = current_setpoint_->score_wrist_goal;
+ if (!data.IsPressed(kStayIn)) {
+ wrist_goal = current_setpoint_->wrist_goal;
+ arm_goal_position_ = current_setpoint_->index;
+ score_wrist_goal = current_setpoint_->score_wrist_goal;
+ }
+
placing_row = current_setpoint_->row_hint;
}
@@ -396,7 +400,7 @@
if (data.IsPressed(kSuck)) {
roller_goal = RollerGoal::INTAKE_LAST;
- } else if (data.IsPressed(kSpit)) {
+ } else if (data.IsPressed(kSpit) || data.IsPressed(kDriverSpit)) {
if (score_wrist_goal.has_value()) {
wrist_goal = score_wrist_goal.value();
diff --git a/y2023/vision/BUILD b/y2023/vision/BUILD
index 68ba833..1cdbe36 100644
--- a/y2023/vision/BUILD
+++ b/y2023/vision/BUILD
@@ -247,3 +247,22 @@
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:public"],
)
+
+cc_binary(
+ name = "image_logger",
+ srcs = [
+ "image_logger.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//aos:configuration",
+ "//aos:init",
+ "//aos/events:shm_event_loop",
+ "//aos/events/logging:log_writer",
+ "//aos/logging:log_namer",
+ "//frc971/input:joystick_state_fbs",
+ "@com_github_gflags_gflags//:gflags",
+ "@com_github_google_glog//:glog",
+ ],
+)
diff --git a/y2023/vision/image_logger.cc b/y2023/vision/image_logger.cc
new file mode 100644
index 0000000..b87cec0
--- /dev/null
+++ b/y2023/vision/image_logger.cc
@@ -0,0 +1,95 @@
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#include "aos/configuration.h"
+#include "aos/events/logging/log_writer.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/logging/log_namer.h"
+#include "frc971/input/joystick_state_generated.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+DEFINE_string(config, "aos_config.json", "Config file to use.");
+
+DEFINE_double(rotate_every, 0.0,
+ "If set, rotate the logger after this many seconds");
+DECLARE_int32(flush_size);
+DEFINE_double(disabled_time, 5.0,
+ "Continue logging if disabled for this amount of time or less");
+
+std::unique_ptr<aos::logger::MultiNodeLogNamer> MakeLogNamer(
+ aos::EventLoop *event_loop) {
+ return std::make_unique<aos::logger::MultiNodeLogNamer>(
+ absl::StrCat(aos::logging::GetLogName("fbs_log"), "/"), event_loop);
+}
+
+int main(int argc, char *argv[]) {
+ gflags::SetUsageMessage(
+ "This program provides a simple logger binary that logs all SHMEM data "
+ "directly to a file specified at the command line when the robot is "
+ "enabled and for a bit of time after.");
+ aos::InitGoogle(&argc, &argv);
+
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig(FLAGS_config);
+
+ aos::ShmEventLoop event_loop(&config.message());
+
+ bool logging = false;
+ bool enabled = false;
+ aos::monotonic_clock::time_point last_disable_time =
+ event_loop.monotonic_now();
+ aos::monotonic_clock::time_point last_rotation_time =
+ event_loop.monotonic_now();
+ aos::logger::Logger logger(&event_loop);
+
+ if (FLAGS_rotate_every != 0.0) {
+ logger.set_on_logged_period([&] {
+ const auto now = event_loop.monotonic_now();
+ if (logging && now > last_rotation_time + std::chrono::duration<double>(
+ FLAGS_rotate_every)) {
+ logger.Rotate();
+ last_rotation_time = now;
+ }
+ });
+ }
+
+ event_loop.OnRun([]() {
+ errno = 0;
+ setpriority(PRIO_PROCESS, 0, -20);
+ PCHECK(errno == 0) << ": Renicing to -20 failed.";
+ });
+
+ event_loop.MakeWatcher(
+ "/roborio/aos", [&](const aos::JoystickState &joystick_state) {
+ const auto timestamp = event_loop.context().monotonic_event_time;
+ // Store the last time we got disabled
+ if (enabled && !joystick_state.enabled()) {
+ last_disable_time = timestamp;
+ }
+ enabled = joystick_state.enabled();
+
+ if (!logging && enabled) {
+ // Start logging if we just got enabled
+ LOG(INFO) << "Starting logging";
+ logger.StartLogging(MakeLogNamer(&event_loop));
+ logging = true;
+ last_rotation_time = event_loop.monotonic_now();
+ } else if (logging && !enabled &&
+ (timestamp - last_disable_time) >
+ std::chrono::duration<double>(FLAGS_disabled_time)) {
+ // Stop logging if we've been disabled for a non-negligible amount of
+ // time
+ LOG(INFO) << "Stopping logging";
+ logger.StopLogging(event_loop.monotonic_now());
+ logging = false;
+ }
+ });
+
+ event_loop.Run();
+
+ LOG(INFO) << "Shutting down";
+
+ return 0;
+}
diff --git a/y2023/www/BUILD b/y2023/www/BUILD
index f8b706c..63089b5 100644
--- a/y2023/www/BUILD
+++ b/y2023/www/BUILD
@@ -7,10 +7,17 @@
"**/*.html",
"**/*.css",
"**/*.png",
- ]),
+ ]) + ["2023.png"],
visibility = ["//visibility:public"],
)
+genrule(
+ name = "2023_field_png",
+ srcs = ["//third_party/y2023/field:pictures"],
+ outs = ["2023.png"],
+ cmd = "cp third_party/y2023/field/2023.png $@",
+)
+
ts_project(
name = "field_main",
srcs = [
@@ -23,8 +30,10 @@
"//aos/network:connect_ts_fbs",
"//aos/network:web_proxy_ts_fbs",
"//aos/network/www:proxy",
+ "//frc971/control_loops:control_loops_ts_fbs",
"//frc971/control_loops/drivetrain:drivetrain_status_ts_fbs",
"//frc971/control_loops/drivetrain/localization:localizer_output_ts_fbs",
+ "//y2023/control_loops/superstructure:superstructure_status_ts_fbs",
"//y2023/localizer:status_ts_fbs",
"//y2023/localizer:visualization_ts_fbs",
"@com_github_google_flatbuffers//ts:flatbuffers_ts",
diff --git a/y2023/www/constants.ts b/y2023/www/constants.ts
index b94d7a7..d6ecfaf 100644
--- a/y2023/www/constants.ts
+++ b/y2023/www/constants.ts
@@ -2,6 +2,7 @@
export const IN_TO_M = 0.0254;
export const FT_TO_M = 0.3048;
// Dimensions of the field in meters
-export const FIELD_WIDTH = 26 * FT_TO_M + 11.25 * IN_TO_M;
-export const FIELD_LENGTH = 52 * FT_TO_M + 5.25 * IN_TO_M;
+// Numbers are slightly hand-tuned to match the PNG that we are using.
+export const FIELD_WIDTH = 26 * FT_TO_M + 7.25 * IN_TO_M;
+export const FIELD_LENGTH = 54 * FT_TO_M + 5.25 * IN_TO_M;
diff --git a/y2023/www/field.html b/y2023/www/field.html
index 72d8f54..a63c6f5 100644
--- a/y2023/www/field.html
+++ b/y2023/www/field.html
@@ -34,6 +34,65 @@
<td id="images_accepted"> NA </td>
</tr>
</table>
+
+ <table>
+ <tr>
+ <th colspan="2">Superstructure</th>
+ </tr>
+ <tr>
+ <td>End Effector State</td>
+ <td id="end_effector_state"> NA </td>
+ </tr>
+ <tr>
+ <td>Wrist</td>
+ <td id="wrist"> NA </td>
+ </tr>
+ </table>
+ <table>
+ <tr>
+ <th colspan="2">Game Piece</th>
+ </tr>
+ <tr>
+ <td>Game Piece Held</td>
+ <td id="game_piece"> NA </td>
+ </tr>
+ </table>
+
+ <table>
+ <tr>
+ <th colspan="2">Arm</th>
+ </tr>
+ <tr>
+ <td>State</td>
+ <td id="arm_state"> NA </td>
+ </tr>
+ <tr>
+ <td>X</td>
+ <td id="arm_x"> NA </td>
+ </tr>
+ <tr>
+ <td>Y</td>
+ <td id="arm_y"> NA </td>
+ </tr>
+ <tr>
+ <td>Circular Index</td>
+ <td id="arm_circular_index"> NA </td>
+ </tr>
+ <tr>
+ <td>Roll</td>
+ <td id="arm_roll"> NA </td>
+ </tr>
+ <tr>
+ <td>Proximal</td>
+ <td id="arm_proximal"> NA </td>
+ </tr>
+ <tr>
+ <td>Distal</td>
+ <td id="arm_distal"> NA </td>
+ </tr>
+ </table>
+ <h3>Zeroing Faults:</h3>
+ <p id="zeroing_faults"> NA </p>
</div>
<div id="vision_readouts">
</div>
diff --git a/y2023/www/field_handler.ts b/y2023/www/field_handler.ts
index 9e2d9ed..65b9a20 100644
--- a/y2023/www/field_handler.ts
+++ b/y2023/www/field_handler.ts
@@ -3,6 +3,9 @@
import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated';
import {RejectionReason} from '../localizer/status_generated';
import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated';
+import {Status as SuperstructureStatus, EndEffectorState, ArmState, ArmStatus} from '../control_loops/superstructure/superstructure_status_generated'
+import {Class} from '../vision/game_pieces_generated'
+import {ZeroingError} from '../../frc971/control_loops/control_loops_generated';
import {Visualization, TargetEstimateDebug} from '../localizer/visualization_generated';
import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
@@ -21,6 +24,7 @@
private canvas = document.createElement('canvas');
private localizerOutput: LocalizerOutput|null = null;
private drivetrainStatus: DrivetrainStatus|null = null;
+ private superstructureStatus: SuperstructureStatus|null = null;
// Image information indexed by timestamp (seconds since the epoch), so that
// we can stop displaying images after a certain amount of time.
@@ -33,6 +37,28 @@
(document.getElementById('images_accepted') as HTMLElement);
private rejectionReasonCells: HTMLElement[] = [];
private fieldImage: HTMLImageElement = new Image();
+ private endEffectorState: HTMLElement =
+ (document.getElementById('end_effector_state') as HTMLElement);
+ private wrist: HTMLElement =
+ (document.getElementById('wrist') as HTMLElement);
+ private armState: HTMLElement =
+ (document.getElementById('arm_state') as HTMLElement);
+ private gamePiece: HTMLElement =
+ (document.getElementById('game_piece') as HTMLElement);
+ private armX: HTMLElement =
+ (document.getElementById('arm_x') as HTMLElement);
+ private armY: HTMLElement =
+ (document.getElementById('arm_y') as HTMLElement);
+ private circularIndex: HTMLElement =
+ (document.getElementById('arm_circular_index') as HTMLElement);
+ private roll: HTMLElement =
+ (document.getElementById('arm_roll') as HTMLElement);
+ private proximal: HTMLElement =
+ (document.getElementById('arm_proximal') as HTMLElement);
+ private distal: HTMLElement =
+ (document.getElementById('arm_distal') as HTMLElement);
+ private zeroingFaults: HTMLElement =
+ (document.getElementById('zeroing_faults') as HTMLElement);_
constructor(private readonly connection: Connection) {
(document.getElementById('field') as HTMLElement).appendChild(this.canvas);
@@ -81,6 +107,11 @@
'/localizer', "frc971.controls.LocalizerOutput", (data) => {
this.handleLocalizerOutput(data);
});
+ this.connection.addHandler(
+ '/superstructure', "y2023.control_loops.superstructure.Status",
+ (data) => {
+ this.handleSuperstructureStatus(data)
+ });
});
}
@@ -117,6 +148,11 @@
this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
}
+ private handleSuperstructureStatus(data: Uint8Array): void {
+ const fbBuffer = new ByteBuffer(data);
+ this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
+ }
+
drawField(): void {
const ctx = this.canvas.getContext('2d');
ctx.save();
@@ -179,6 +215,25 @@
div.classList.remove('near');
}
+ setEstopped(div: HTMLElement): void {
+ div.innerHTML = 'estopped';
+ div.classList.add('faulted');
+ div.classList.remove('zeroing');
+ div.classList.remove('near');
+ }
+
+ setTargetValue(
+ div: HTMLElement, target: number, val: number, tolerance: number): void {
+ div.innerHTML = val.toFixed(4);
+ div.classList.remove('faulted');
+ div.classList.remove('zeroing');
+ if (Math.abs(target - val) < tolerance) {
+ div.classList.add('near');
+ } else {
+ div.classList.remove('near');
+ }
+ }
+
setValue(div: HTMLElement, val: number): void {
div.innerHTML = val.toFixed(4);
div.classList.remove('faulted');
@@ -193,6 +248,56 @@
// Draw the matches with debugging information from the localizer.
const now = Date.now() / 1000.0;
+ if (this.superstructureStatus) {
+ this.endEffectorState.innerHTML =
+ EndEffectorState[this.superstructureStatus.endEffectorState()];
+ if (!this.superstructureStatus.wrist() ||
+ !this.superstructureStatus.wrist().zeroed()) {
+ this.setZeroing(this.wrist);
+ } else if (this.superstructureStatus.wrist().estopped()) {
+ this.setEstopped(this.wrist);
+ } else {
+ this.setTargetValue(
+ this.wrist,
+ this.superstructureStatus.wrist().unprofiledGoalPosition(),
+ this.superstructureStatus.wrist().estimatorState().position(),
+ 1e-3);
+ }
+ this.armState.innerHTML =
+ ArmState[this.superstructureStatus.arm().state()];
+ this.gamePiece.innerHTML =
+ Class[this.superstructureStatus.gamePiece()];
+ this.armX.innerHTML =
+ this.superstructureStatus.arm().armX().toFixed(2);
+ this.armY.innerHTML =
+ this.superstructureStatus.arm().armY().toFixed(2);
+ this.circularIndex.innerHTML =
+ this.superstructureStatus.arm().armCircularIndex().toFixed(0);
+ this.roll.innerHTML =
+ this.superstructureStatus.arm().rollJointEstimatorState().position().toFixed(2);
+ this.proximal.innerHTML =
+ this.superstructureStatus.arm().proximalEstimatorState().position().toFixed(2);
+ this.distal.innerHTML =
+ this.superstructureStatus.arm().distalEstimatorState().position().toFixed(2);
+ let zeroingErrors: string = "Roll Joint Errors:"+'<br/>';
+ for (let i = 0; i < this.superstructureStatus.arm().rollJointEstimatorState().errors.length; i++) {
+ zeroingErrors += ZeroingError[this.superstructureStatus.arm().rollJointEstimatorState().errors(i)]+'<br/>';
+ }
+ zeroingErrors += '<br/>'+"Proximal Joint Errors:"+'<br/>';
+ for (let i = 0; i < this.superstructureStatus.arm().proximalEstimatorState().errors.length; i++) {
+ zeroingErrors += ZeroingError[this.superstructureStatus.arm().proximalEstimatorState().errors(i)]+'<br/>';
+ }
+ zeroingErrors += '<br/>'+"Distal Joint Errors:"+'<br/>';
+ for (let i = 0; i < this.superstructureStatus.arm().distalEstimatorState().errors.length; i++) {
+ zeroingErrors += ZeroingError[this.superstructureStatus.arm().distalEstimatorState().errors(i)]+'<br/>';
+ }
+ zeroingErrors += '<br/>'+"Wrist Errors:"+'<br/>';
+ for (let i = 0; i < this.superstructureStatus.wrist().estimatorState().errors.length; i++) {
+ zeroingErrors += ZeroingError[this.superstructureStatus.wrist().estimatorState().errors(i)]+'<br/>';
+ }
+ this.zeroingFaults.innerHTML = zeroingErrors;
+ }
+
if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
this.drawRobot(
this.drivetrainStatus.trajectoryLogging().x(),
diff --git a/y2023/y2023_logger.json b/y2023/y2023_logger.json
index f4ac45e..55d201d 100644
--- a/y2023/y2023_logger.json
+++ b/y2023/y2023_logger.json
@@ -446,8 +446,7 @@
},
{
"name": "image_logger",
- "executable_name": "logger_main",
- "autostart": false,
+ "executable_name": "image_logger",
"user": "pi",
"args": [
"--logging_folder",
diff --git a/y2023/y2023_pi_template.json b/y2023/y2023_pi_template.json
index baf7031..113e48f 100644
--- a/y2023/y2023_pi_template.json
+++ b/y2023/y2023_pi_template.json
@@ -392,8 +392,7 @@
},
{
"name": "image_logger",
- "executable_name": "logger_main",
- "autostart": false,
+ "executable_name": "image_logger",
"args": [
"--logging_folder",
"",
@@ -402,6 +401,7 @@
"--direct",
"--flush_size=4194304"
],
+ "user": "pi",
"nodes": [
"pi{{ NUM }}"
]
diff --git a/y2023/y2023_roborio.json b/y2023/y2023_roborio.json
index 03985c0..f3697ac 100644
--- a/y2023/y2023_roborio.json
+++ b/y2023/y2023_roborio.json
@@ -28,6 +28,42 @@
"timestamp_logger_nodes": [
"roborio"
]
+ },
+ {
+ "name": "pi1",
+ "priority": 5,
+ "time_to_live": 50000000,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": [
+ "roborio"
+ ]
+ },
+ {
+ "name": "pi2",
+ "priority": 5,
+ "time_to_live": 50000000,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": [
+ "roborio"
+ ]
+ },
+ {
+ "name": "pi3",
+ "priority": 5,
+ "time_to_live": 50000000,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": [
+ "roborio"
+ ]
+ },
+ {
+ "name": "pi4",
+ "priority": 5,
+ "time_to_live": 50000000,
+ "timestamp_logger": "LOCAL_AND_REMOTE_LOGGER",
+ "timestamp_logger_nodes": [
+ "roborio"
+ ]
}
]
},
@@ -50,6 +86,42 @@
"max_size": 200
},
{
+ "name": "/roborio/aos/remote_timestamps/pi1/roborio/aos/aos-JoystickState",
+ "type": "aos.message_bridge.RemoteMessage",
+ "source_node": "roborio",
+ "logger": "NOT_LOGGED",
+ "frequency": 300,
+ "num_senders": 2,
+ "max_size": 200
+ },
+ {
+ "name": "/roborio/aos/remote_timestamps/pi2/roborio/aos/aos-JoystickState",
+ "type": "aos.message_bridge.RemoteMessage",
+ "source_node": "roborio",
+ "logger": "NOT_LOGGED",
+ "frequency": 300,
+ "num_senders": 2,
+ "max_size": 200
+ },
+ {
+ "name": "/roborio/aos/remote_timestamps/pi3/roborio/aos/aos-JoystickState",
+ "type": "aos.message_bridge.RemoteMessage",
+ "source_node": "roborio",
+ "logger": "NOT_LOGGED",
+ "frequency": 300,
+ "num_senders": 2,
+ "max_size": 200
+ },
+ {
+ "name": "/roborio/aos/remote_timestamps/pi4/roborio/aos/aos-JoystickState",
+ "type": "aos.message_bridge.RemoteMessage",
+ "source_node": "roborio",
+ "logger": "NOT_LOGGED",
+ "frequency": 300,
+ "num_senders": 2,
+ "max_size": 200
+ },
+ {
"name": "/roborio/aos",
"type": "aos.RobotState",
"source_node": "roborio",