Merge changes I8e9a3a44,I0e992ccf,I53881cf2
* changes:
Recal pot after weird pot slip event
Add ability to wait for a distance along a spline in auto
Fix arm freakout
diff --git a/frc971/control_loops/python/graph.py b/frc971/control_loops/python/graph.py
index 1cc6f57..16c792bc 100644
--- a/frc971/control_loops/python/graph.py
+++ b/frc971/control_loops/python/graph.py
@@ -23,8 +23,10 @@
self.canvas = FigureCanvas(fig) # a Gtk.DrawingArea
self.canvas.set_vexpand(True)
self.canvas.set_size_request(800, 250)
- self.callback_id = self.canvas.mpl_connect('motion_notify_event',
- self.on_mouse_move)
+ self.mouse_move_callback = self.canvas.mpl_connect(
+ 'motion_notify_event', self.on_mouse_move)
+ self.click_callback = self.canvas.mpl_connect('button_press_event',
+ self.on_click)
self.add(self.canvas)
# The current graph data
@@ -99,6 +101,24 @@
if self.cursor_watcher is not None:
self.cursor_watcher.queue_draw()
+ def on_click(self, event):
+ """Same as on_mouse_move but also selects multisplines"""
+
+ if self.data is None:
+ return
+ total_steps_taken = self.data.shape[1]
+ total_time = self.dt * total_steps_taken
+ if event.xdata is not None:
+ # clip the position if still on the canvas, but off the graph
+ self.cursor = np.clip(event.xdata, 0, total_time)
+
+ self.redraw_cursor()
+
+ # tell the field to update too
+ if self.cursor_watcher is not None:
+ self.cursor_watcher.queue_draw()
+ self.cursor_watcher.on_graph_clicked()
+
def redraw_cursor(self):
"""Redraws the cursor line"""
# TODO: This redraws the entire graph and isn't very snappy
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index b5e8c1b..1659e55 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -287,7 +287,11 @@
if i == 0:
self.draw_robot_at_point(cr, spline, 0)
- self.draw_robot_at_point(cr, spline, 1)
+
+ is_last_spline = spline is multispline.getLibsplines()[-1]
+
+ if multispline == self.active_multispline or is_last_spline:
+ self.draw_robot_at_point(cr, spline, 1)
def export_json(self, file_name):
export_folder = Path(
@@ -422,6 +426,15 @@
prev_multispline.getSplines()[-1])
self.queue_draw()
+ def on_graph_clicked(self):
+ if self.graph.cursor is not None:
+ cursor = self.graph.find_cursor()
+ if cursor is None:
+ return
+ multispline_index, x = cursor
+
+ self.active_multispline_index = multispline_index
+
def do_button_release_event(self, event):
self.drag_start = None
@@ -489,7 +502,7 @@
multispline, result = Multispline.nearest_distance(
self.multisplines, cur_p)
- if result and result.fun < 0.1:
+ if self.control_point_index == None and result and result.fun < 0.1:
self.active_multispline_index = self.multisplines.index(
multispline)
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 718711c..ca1af9e 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -48,14 +48,14 @@
}
type Action struct {
- TeamNumber string `gorm:"primaryKey"`
- MatchNumber int32 `gorm:"primaryKey"`
- SetNumber int32 `gorm:"primaryKey"`
- CompLevel string `gorm:"primaryKey"`
- CompletedAction []byte
+ TeamNumber string `gorm:"primaryKey"`
+ MatchNumber int32 `gorm:"primaryKey"`
+ SetNumber int32 `gorm:"primaryKey"`
+ CompLevel string `gorm:"primaryKey"`
// This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
- TimeStamp int32 `gorm:"primaryKey"`
- CollectedBy string
+ CompletedAction []byte
+ Timestamp int64 `gorm:"primaryKey"`
+ CollectedBy string
}
type NotesData struct {
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index ea83fa3..b0d34d8 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -761,27 +761,27 @@
correct := []Action{
Action{
TeamNumber: "1235", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0000, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0000, CollectedBy: "",
},
Action{
TeamNumber: "1236", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0321, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0321, CollectedBy: "",
},
Action{
TeamNumber: "1237", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0222, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0222, CollectedBy: "",
},
Action{
TeamNumber: "1238", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0110, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0110, CollectedBy: "",
},
Action{
TeamNumber: "1239", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0004, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0004, CollectedBy: "",
},
Action{
TeamNumber: "1233", MatchNumber: 94, SetNumber: 1, CompLevel: "quals",
- CompletedAction: []byte(""), TimeStamp: 0004, CollectedBy: "",
+ CompletedAction: []byte(""), Timestamp: 0005, CollectedBy: "",
},
}
diff --git a/scouting/scouting_test.cy.js b/scouting/scouting_test.cy.js
index 0237c4b..8ac1880 100644
--- a/scouting/scouting_test.cy.js
+++ b/scouting/scouting_test.cy.js
@@ -105,7 +105,7 @@
});
//TODO(FILIP): Verify last action when the last action header gets added.
- it('should: be able to get to submit screen in data scouting.', () => {
+ it('should: be able to submit data scouting.', () => {
switchToTab('Data Entry');
headerShouldBe('Team Selection');
clickButton('Next');
@@ -131,11 +131,11 @@
clickButton('Endgame');
cy.get('[type="checkbox"]').check();
- // Should be on submit screen.
- // TODO(FILIP): Verify that submitting works once we add it.
-
clickButton('End Match');
headerShouldBe('Review and Submit');
+
+ clickButton('Submit');
+ headerShouldBe('Success');
});
it('should: be able to return to correct screen with undo for pick and place.', () => {
diff --git a/scouting/webserver/requests/debug/BUILD b/scouting/webserver/requests/debug/BUILD
index f3f4a72..d9bb030 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -14,6 +14,7 @@
"//scouting/webserver/requests/messages:request_all_notes_response_go_fbs",
"//scouting/webserver/requests/messages:request_notes_for_team_response_go_fbs",
"//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
+ "//scouting/webserver/requests/messages:submit_actions_response_go_fbs",
"//scouting/webserver/requests/messages:submit_driver_ranking_response_go_fbs",
"//scouting/webserver/requests/messages:submit_notes_response_go_fbs",
"//scouting/webserver/requests/messages:submit_shift_schedule_response_go_fbs",
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index eb3a1ca..acb9dd4 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -16,6 +16,7 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
@@ -157,3 +158,9 @@
server+"/requests/submit/submit_driver_ranking", requestBytes,
submit_driver_ranking_response.GetRootAsSubmitDriverRankingResponse)
}
+
+func SubmitActions(server string, requestBytes []byte) (*submit_actions_response.SubmitActionsResponseT, error) {
+ return sendMessage[submit_actions_response.SubmitActionsResponseT](
+ server+"/requests/submit/submit_actions", requestBytes,
+ submit_actions_response.GetRootAsSubmitActionsResponse)
+}
diff --git a/scouting/webserver/requests/messages/submit_actions.fbs b/scouting/webserver/requests/messages/submit_actions.fbs
index 8c79097..d8aa98d 100644
--- a/scouting/webserver/requests/messages/submit_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_actions.fbs
@@ -62,5 +62,6 @@
set_number:int (id: 2);
comp_level:string (id: 3);
actions_list:[Action] (id:4);
+ //TODO: delete this field
collected_by:string (id: 5);
}
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 9305274..8646a30 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -56,6 +56,7 @@
type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
type SubmitActions = submit_actions.SubmitActions
+type Action = submit_actions.Action
type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
// The interface we expect the database abstraction to conform to.
@@ -74,6 +75,7 @@
QueryNotes(int32) ([]string, error)
AddNotes(db.NotesData) error
AddDriverRanking(db.DriverRankingData) error
+ AddAction(db.Action) error
}
// Handles unknown requests. Just returns a 404.
@@ -785,6 +787,62 @@
w.Write(builder.FinishedBytes())
}
+type submitActionsHandler struct {
+ db Database
+}
+
+func (handler submitActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ // Get the username of the person submitting the data.
+ username := parseUsername(req)
+
+ requestBytes, err := io.ReadAll(req.Body)
+ if err != nil {
+ respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
+ return
+ }
+
+ request, success := parseRequest(w, requestBytes, "SubmitActions", submit_actions.GetRootAsSubmitActions)
+ if !success {
+ return
+ }
+
+ log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
+
+ for i := 0; i < request.ActionsListLength(); i++ {
+
+ var action Action
+ request.ActionsList(&action, i)
+
+ dbAction := db.Action{
+ TeamNumber: string(request.TeamNumber()),
+ MatchNumber: request.MatchNumber(),
+ SetNumber: request.SetNumber(),
+ CompLevel: string(request.CompLevel()),
+ //TODO: Serialize CompletedAction
+ CompletedAction: []byte{},
+ Timestamp: action.Timestamp(),
+ CollectedBy: username,
+ }
+
+ // Do some error checking.
+ if action.Timestamp() < 0 {
+ respondWithError(w, http.StatusBadRequest, fmt.Sprint(
+ "Invalid timestamp field value of ", action.Timestamp()))
+ return
+ }
+
+ err = handler.db.AddAction(dbAction)
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
+ return
+ }
+ }
+
+ builder := flatbuffers.NewBuilder(50 * 1024)
+ builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
+ w.Write(builder.FinishedBytes())
+}
+
func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
scoutingServer.HandleFunc("/requests", unknown)
scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
@@ -796,4 +854,5 @@
scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
+ scoutingServer.Handle("/requests/submit/submit_actions", submitActionsHandler{db})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index dab3174..ac644ea 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -752,6 +752,109 @@
}
}
+func packAction(action *submit_actions.ActionT) []byte {
+ builder := flatbuffers.NewBuilder(50 * 1024)
+ builder.Finish((action).Pack(builder))
+ return (builder.FinishedBytes())
+}
+
+func TestAddingActions(t *testing.T) {
+ database := MockDatabase{}
+ scoutingServer := server.NewScoutingServer()
+ HandleRequests(&database, scoutingServer)
+ scoutingServer.Start(8080)
+ defer scoutingServer.Stop()
+
+ builder := flatbuffers.NewBuilder(1024)
+ builder.Finish((&submit_actions.SubmitActionsT{
+ TeamNumber: "1234",
+ MatchNumber: 4,
+ SetNumber: 1,
+ CompLevel: "qual",
+ ActionsList: []*submit_actions.ActionT{
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePickupObjectAction,
+ Value: &submit_actions.PickupObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ Auto: true,
+ },
+ },
+ Timestamp: 2400,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePlaceObjectAction,
+ Value: &submit_actions.PlaceObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ ScoreLevel: submit_actions.ScoreLevelkLow,
+ Auto: false,
+ },
+ },
+ Timestamp: 1009,
+ },
+ },
+ }).Pack(builder))
+
+ _, err := debug.SubmitActions("http://localhost:8080", builder.FinishedBytes())
+ if err != nil {
+ t.Fatal("Failed to submit actions: ", err)
+ }
+
+ // Make sure that the data made it into the database.
+ // TODO: Add this back when we figure out how to add the serialized action into the database.
+
+ /* expectedActionsT := []*submit_actions.ActionT{
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePickupObjectAction,
+ Value: &submit_actions.PickupObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ Auto: true,
+ },
+ },
+ Timestamp: 2400,
+ },
+ {
+ ActionTaken: &submit_actions.ActionTypeT{
+ Type: submit_actions.ActionTypePlaceObjectAction,
+ Value: &submit_actions.PlaceObjectActionT{
+ ObjectType: submit_actions.ObjectTypekCube,
+ ScoreLevel: submit_actions.ScoreLevelkLow,
+ Auto: false,
+ },
+ },
+ Timestamp: 1009,
+ },
+ } */
+
+ expectedActions := []db.Action{
+ {
+ TeamNumber: "1234",
+ MatchNumber: 4,
+ SetNumber: 1,
+ CompLevel: "qual",
+ CollectedBy: "debug_cli",
+ CompletedAction: []byte{},
+ Timestamp: 2400,
+ },
+ {
+ TeamNumber: "1234",
+ MatchNumber: 4,
+ SetNumber: 1,
+ CompLevel: "qual",
+ CollectedBy: "debug_cli",
+ CompletedAction: []byte{},
+ Timestamp: 1009,
+ },
+ }
+
+ if !reflect.DeepEqual(expectedActions, database.actions) {
+ t.Fatal("Expected ", expectedActions, ", but got:", database.actions)
+ }
+
+}
+
// A mocked database we can use for testing. Add functionality to this as
// needed for your tests.
@@ -761,6 +864,7 @@
shiftSchedule []db.Shift
driver_ranking []db.DriverRankingData
stats2023 []db.Stats2023
+ actions []db.Action
}
func (database *MockDatabase) AddToMatch(match db.TeamMatch) error {
@@ -830,3 +934,12 @@
func (database *MockDatabase) ReturnAllDriverRankings() ([]db.DriverRankingData, error) {
return database.driver_ranking, nil
}
+
+func (database *MockDatabase) AddAction(action db.Action) error {
+ database.actions = append(database.actions, action)
+ return nil
+}
+
+func (database *MockDatabase) ReturnActions() ([]db.Action, error) {
+ return database.actions, nil
+}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index 9ab5b7a..71c8de2 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -301,7 +301,7 @@
builder.finish(SubmitActions.endSubmitActions(builder));
const buffer = builder.asUint8Array();
- const res = await fetch('/requests/submit/actions', {
+ const res = await fetch('/requests/submit/submit_actions', {
method: 'POST',
body: buffer,
});
diff --git a/y2023/control_loops/python/graph_edit.py b/y2023/control_loops/python/graph_edit.py
index bbc7a1b..99b09af 100644
--- a/y2023/control_loops/python/graph_edit.py
+++ b/y2023/control_loops/python/graph_edit.py
@@ -244,6 +244,7 @@
self.set_size_request(ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
self.center = (0, 0)
self.shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
+ self.window_shape = (ARM_AREA_WIDTH, ARM_AREA_HEIGHT)
self.theta_version = False
self.init_extents()
@@ -326,9 +327,6 @@
def on_draw(self, widget, event):
cr = self.get_window().cairo_create()
- self.window_shape = (self.get_window().get_geometry().width,
- self.get_window().get_geometry().height)
-
cr.save()
cr.set_font_size(20)
cr.translate(self.window_shape[0] / 2, self.window_shape[1] / 2)
diff --git a/y2023/www/field.html b/y2023/www/field.html
index cc89bb9..6bd2fc0 100644
--- a/y2023/www/field.html
+++ b/y2023/www/field.html
@@ -56,6 +56,10 @@
<td>Game Piece Held</td>
<td id="game_piece"> NA </td>
</tr>
+ <tr>
+ <td>Game Piece Position (+ = left, 0 = empty)</td>
+ <td id="game_piece_position"> NA </td>
+ </tr>
</table>
<table>
diff --git a/y2023/www/field_handler.ts b/y2023/www/field_handler.ts
index 2f62c7d..24a55fa 100644
--- a/y2023/www/field_handler.ts
+++ b/y2023/www/field_handler.ts
@@ -56,6 +56,8 @@
(document.getElementById('arm_state') as HTMLElement);
private gamePiece: HTMLElement =
(document.getElementById('game_piece') as HTMLElement);
+ private gamePiecePosition: HTMLElement =
+ (document.getElementById('game_piece_position') as HTMLElement);
private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
private circularIndex: HTMLElement =
@@ -387,6 +389,8 @@
this.armState.innerHTML =
ArmState[this.superstructureStatus.arm().state()];
this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
+ this.gamePiecePosition.innerHTML =
+ this.superstructureStatus.gamePiecePosition().toFixed(4);
this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
this.circularIndex.innerHTML =