Merge "Add boilerplate localizer code for LED control"
diff --git a/WORKSPACE b/WORKSPACE
index 58fb14f..f574965 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -12,6 +12,10 @@
apache2_debs = "files",
)
load(
+ "//debian:postgresql_amd64.bzl",
+ postgresql_amd64_debs = "files",
+)
+load(
"//debian:patch.bzl",
patch_debs = "files",
)
@@ -93,6 +97,8 @@
generate_repositories_for_debs(apache2_debs)
+generate_repositories_for_debs(postgresql_amd64_debs)
+
generate_repositories_for_debs(patch_debs)
generate_repositories_for_debs(pandoc_debs)
@@ -525,6 +531,13 @@
)
http_archive(
+ name = "postgresql_amd64",
+ build_file = "@//debian:postgresql_amd64.BUILD",
+ sha256 = "2b8bb77deaf58f798c296ce31ee7a32781395d55e05dcddc8a7da7e827f38d7f",
+ url = "https://www.frc971.org/Build-Dependencies/postgresql_amd64.tar.gz",
+)
+
+http_archive(
name = "patch",
build_file = "@//debian:patch.BUILD",
sha256 = "b5ce139648a2e04f5585948ddad2fdae24dd4ee7976ac5a22d6ae7bd5674631e",
diff --git a/debian/BUILD b/debian/BUILD
index 2aeabf2..3f13244 100644
--- a/debian/BUILD
+++ b/debian/BUILD
@@ -7,6 +7,10 @@
apache2_debs = "files",
)
load(
+ ":postgresql_amd64.bzl",
+ postgresql_amd64_debs = "files",
+)
+load(
":patch.bzl",
patch_debs = "files",
)
@@ -173,6 +177,28 @@
)
download_packages(
+ name = "download_postgresql_deps",
+ excludes = [
+ "adduser",
+ "debconf",
+ "debconf-2.0",
+ "libsystemd0",
+ "lsb-base",
+ "libstdc++6",
+ "libc-bin",
+ "libc-l10n",
+ "netbase",
+ "ucf",
+ "locales",
+ "locales-all",
+ ],
+ packages = [
+ "postgresql",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
+download_packages(
name = "download_patch_deps",
packages = [
"patch",
@@ -287,6 +313,12 @@
)
generate_deb_tarball(
+ name = "postgresql_amd64",
+ files = postgresql_amd64_debs,
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
+generate_deb_tarball(
name = "patch",
files = patch_debs,
target_compatible_with = ["@platforms//os:linux"],
diff --git a/debian/postgresql_amd64.BUILD b/debian/postgresql_amd64.BUILD
new file mode 100644
index 0000000..c2f8331
--- /dev/null
+++ b/debian/postgresql_amd64.BUILD
@@ -0,0 +1,56 @@
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+
+TEMPLATE = """\
+#!/bin/bash
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+add_ld_library_path_for() {
+ local file="$1"
+ local dir
+ local resolved_file
+ if ! resolved_file="$(rlocation "postgresql_amd64/$file")"; then
+ echo "Couldn't find file postgresql_amd64/${file}" >&2
+ exit 1
+ fi
+ dir="$(dirname "${resolved_file}")"
+ export LD_LIBRARY_PATH="${dir}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+}
+
+add_ld_library_path_for usr/lib/x86_64-linux-gnu/libbsd.so.0.11.3
+add_ld_library_path_for lib/x86_64-linux-gnu/libreadline.so.8.1
+
+exec $(rlocation postgresql_amd64/usr/lib/postgresql/13/bin/%s) "$@"
+"""
+
+[(
+ write_file(
+ name = "generate_%s_wrapper" % binary,
+ out = "%s.sh" % binary,
+ content = [TEMPLATE % binary],
+ ),
+ sh_binary(
+ name = binary,
+ srcs = ["%s.sh" % binary],
+ data = glob([
+ "usr/lib/**/*",
+ "lib/**/*",
+ ]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+ ),
+) for binary in (
+ "postgres",
+ "initdb",
+)]
diff --git a/debian/postgresql_amd64.bzl b/debian/postgresql_amd64.bzl
new file mode 100644
index 0000000..6fc9ba5
--- /dev/null
+++ b/debian/postgresql_amd64.bzl
@@ -0,0 +1,39 @@
+files = {
+ "libbsd0_0.11.3-1_amd64.deb": "284a7b8dcfcad74770f57360721365317448b38ab773db542bf630e94e60c13e",
+ "libedit2_3.1-20191231-2+b1_amd64.deb": "ac545f6ad10ba791aca24b09255ad1d6d943e6bc7c5511d5998e104aee51c943",
+ "libffi7_3.3-6_amd64.deb": "30ca89bfddae5fa6e0a2a044f22b6e50cd17c4bc6bc850c579819aeab7101f0f",
+ "libgdbm-compat4_1.19-2_amd64.deb": "e62caed68b0ffaa03b5fa539d6fdc08c4151f66236d5878949bead0b71b7bb09",
+ "libgdbm6_1.19-2_amd64.deb": "e54cfe4d8b8f209bb7df31a404ce040f7c2f9b1045114a927a7e1061cdf90727",
+ "libgnutls30_3.7.1-5_amd64.deb": "20b0189b72ad4c791cf5b280c111d41ce071a04dab0e9a9d7daa9504a7a7b543",
+ "libhogweed6_3.7.3-1_amd64.deb": "6aab2e892cdb2dfba45707601bc6c3b19aa228f70ae5841017f14c3b0ca3d22f",
+ "libicu67_67.1-7_amd64.deb": "2bf5c46254f527865bfd6368e1120908755fa57d83634bd7d316c9b3cfd57303",
+ "libidn2-0_2.3.0-5_amd64.deb": "cb80cd769171537bafbb4a16c12ec427065795946b3415781bc9792e92d60b59",
+ "libldap-2.4-2_2.4.57+dfsg-3_amd64.deb": "4186d0d3f086202d391da49d1bb5ced6dde5eafba1dbcffef9a8e1238a7ef7c3",
+ "libllvm11_11.0.1-2_amd64.deb": "eaff3c8dd6039af90b8b6bdbf33433e35d8c808a7aa195d0e3800ef5e61affff",
+ "libmd0_1.0.3-3_amd64.deb": "9e425b3c128b69126d95e61998e1b5ef74e862dd1fc953d91eebcc315aea62ea",
+ "libnettle8_3.7.3-1_amd64.deb": "e4f8ec31ed14518b241eb7b423ad5ed3f4a4e8ac50aae72c9fd475c569582764",
+ "libp11-kit0_0.23.22-1_amd64.deb": "bfef5f31ee1c730e56e16bb62cc5ff8372185106c75bf1ed1756c96703019457",
+ "libperl5.32_5.32.1-4+deb11u2_amd64.deb": "224cafe65968deb83168113b74dff2d2f13b115a41d99eb209ed3b8f981df0b3",
+ "libpq5_13.5-0+deb11u1_amd64.deb": "0bfa1dc24e1275963961efdcc6d2ff4d2eec390d7acd5a6aee3162569ae1886c",
+ "libreadline8_8.1-1_amd64.deb": "162ba9fdcde81b5502953ed4d84b24e8ad4e380bbd02990ab1a0e3edffca3c22",
+ "libsasl2-2_2.1.27+dfsg-2.1+deb11u1_amd64.deb": "2e86ab7a3329aad4b7350a9b067fe8f80b680302f2f82d94f73f9bf075404460",
+ "libsasl2-modules-db_2.1.27+dfsg-2.1+deb11u1_amd64.deb": "122bf3de4ca0ec873bc35bdde1f21ec9d91ace4f5245c3b1240e077f866e1ae9",
+ "libtasn1-6_4.16.0-2_amd64.deb": "fd7a200100298c2556e67bdc1a5faf5cf21c3136fa47f381d7e9769233ee88a1",
+ "libtinfo6_6.2+20201114-2_amd64.deb": "aeaf942c71ecc0ed081efdead1a1de304dcd513a9fc06791f26992e76986597b",
+ "libunistring2_0.9.10-4_amd64.deb": "654433ad02d3a8b05c1683c6c29a224500bf343039c34dcec4e5e9515345e3d4",
+ "libuuid1_2.36.1-8+deb11u1_amd64.deb": "31250af4dd3b7d1519326a9a6764d1466a93d8f498cf6545058761ebc38b2823",
+ "libxml2_2.9.10+dfsg-6.7_amd64.deb": "023296a15e1a28607609cb15c7ca0dd8a25160f3e89a0da58368319c7e17d4e0",
+ "libxslt1.1_1.1.34-4_amd64.deb": "17eb62d8973867b61e7f8b21b5c16ed33e151799656e49caf670081707853fb8",
+ "libz3-4_4.8.10-1_amd64.deb": "7a38c2dd985eb9315857588ee06ff297e2b16de159dec85bd2777a43ebe9f458",
+ "openssl_1.1.1k-1+deb11u1_amd64.deb": "ed998755dabb96ffe107c2d41ce685ecbb4fa200f7825ff82c1092f8334bf3cb",
+ "perl-modules-5.32_5.32.1-4+deb11u2_all.deb": "6fa15be322c3c89ec4a07d704ad58d4a2d1aabf866135a859f6d8d58c59e9df4",
+ "perl_5.32.1-4+deb11u2_amd64.deb": "1cebc4516ed7c240b812c7bdd7e6ea0810f513152717ca17ce139ee0dfbc7b0d",
+ "postgresql-13_13.5-0+deb11u1_amd64.deb": "e475540f43756dc1c64de0a8a3b33f2c0e45b39610f091afbfe3b6ef72573c7b",
+ "postgresql-client-13_13.5-0+deb11u1_amd64.deb": "cd1779abafdee712d9ea4ebae62d873b61540fd76beab1cc86e604c12813d005",
+ "postgresql-client-common_225_all.deb": "a867f301751692f9ad127c1dd921c3bce7f3969bdf58c6bf38c57303d1b51d2c",
+ "postgresql-common_225_all.deb": "90216c317fd9f247d8fb1597fb4677cbdf2bbb83811213ce4344a44820449e66",
+ "postgresql_13+225_all.deb": "c8791bd0fd7cce76341cbd2c6ba98991a206441fe948534394239e95d102b4b8",
+ "readline-common_8.1-1_all.deb": "3f947176ef949f93e4ad5d76c067d33fa97cf90b62ee0748acb4f5f64790edc8",
+ "ssl-cert_1.1.0+nmu1_all.deb": "6f3b0c20b0a37b2b196d832910a754cf784f96854daa02a16f4ac46d366cdcb8",
+ "tzdata_2021a-1+deb11u2_all.deb": "4a34cbe17d391e6351386f3530b7ffd096c2cc8582e970f745addc636fa7c397",
+}
diff --git a/scouting/BUILD b/scouting/BUILD
index a769426..0c2c641 100644
--- a/scouting/BUILD
+++ b/scouting/BUILD
@@ -33,7 +33,7 @@
"//scouting/www:index.html",
"//scouting/www:zonejs_copy",
],
- visibility = ["//scouting/deploy:__pkg__"],
+ visibility = ["//visibility:public"],
)
protractor_ts_test(
@@ -42,5 +42,5 @@
":scouting_test.ts",
],
on_prepare = ":scouting_test.protractor.on-prepare.js",
- server = ":scouting",
+ server = "//scouting/testing:scouting_test_servers",
)
diff --git a/scouting/db/db.go b/scouting/db/db.go
index d666f79..94e9056 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -27,6 +27,11 @@
Climbing int32
}
+type NotesData struct {
+ TeamNumber int32
+ Notes []string
+}
+
// Opens a database at the specified path. If the path refers to a non-existent
// file, the database will be created and initialized with empty tables.
func NewDatabase(path string) (*Database, error) {
@@ -86,6 +91,20 @@
return nil, errors.New(fmt.Sprint("Failed to create team_match_stats table: ", err))
}
+ statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_notes (" +
+ "id INTEGER PRIMARY KEY, " +
+ "TeamNumber INTEGER, " +
+ "Notes TEXT)")
+ if err != nil {
+ return nil, errors.New(fmt.Sprint("Failed to prepare notes table creation: ", err))
+ }
+ defer statement.Close()
+
+ _, err = statement.Exec()
+ if err != nil {
+ return nil, errors.New(fmt.Sprint("Failed to create notes table: ", err))
+ }
+
return database, nil
}
@@ -107,6 +126,15 @@
if err != nil {
return errors.New(fmt.Sprint("Failed to drop stats table: ", err))
}
+
+ statement, err = database.Prepare("DROP TABLE IF EXISTS team_notes")
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to prepare dropping notes table: ", err))
+ }
+ _, err = statement.Exec()
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to drop notes table: ", err))
+ }
return nil
}
@@ -285,3 +313,42 @@
}
return teams, nil
}
+
+func (database *Database) QueryNotes(TeamNumber int32) (NotesData, error) {
+ rows, err := database.Query("SELECT * FROM team_notes WHERE TeamNumber = ?", TeamNumber)
+ if err != nil {
+ return NotesData{}, errors.New(fmt.Sprint("Failed to select from notes: ", err))
+ }
+ defer rows.Close()
+
+ var notes []string
+ for rows.Next() {
+ var id int32
+ var data string
+ err = rows.Scan(&id, &TeamNumber, &data)
+ if err != nil {
+ return NotesData{}, errors.New(fmt.Sprint("Failed to scan from notes: ", err))
+ }
+ notes = append(notes, data)
+ }
+ return NotesData{TeamNumber, notes}, nil
+}
+
+func (database *Database) AddNotes(data NotesData) error {
+ if len(data.Notes) > 1 {
+ return errors.New("Can only insert one row of notes at a time")
+ }
+ statement, err := database.Prepare("INSERT INTO " +
+ "team_notes(TeamNumber, Notes)" +
+ "VALUES (?, ?)")
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to prepare insertion into notes table: ", err))
+ }
+ defer statement.Close()
+
+ _, err = statement.Exec(data.TeamNumber, data.Notes[0])
+ if err != nil {
+ return errors.New(fmt.Sprint("Failed to insert into Notes database: ", err))
+ }
+ return nil
+}
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 474ea41..13a43f8 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -324,3 +324,27 @@
t.Errorf("Got %#v,\nbut expected %#v.", got, correct)
}
}
+
+func TestNotes(t *testing.T) {
+ db := createDatabase(t)
+ defer db.Delete()
+
+ expected := NotesData{
+ TeamNumber: 1234,
+ Notes: []string{"Note 1", "Note 3"},
+ }
+
+ err := db.AddNotes(NotesData{1234, []string{"Note 1"}})
+ check(t, err, "Failed to add Note")
+ err = db.AddNotes(NotesData{1235, []string{"Note 2"}})
+ check(t, err, "Failed to add Note")
+ err = db.AddNotes(NotesData{1234, []string{"Note 3"}})
+ check(t, err, "Failed to add Note")
+
+ actual, err := db.QueryNotes(1234)
+ check(t, err, "Failed to get Notes")
+
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("Got %#v,\nbut expected %#v.", actual, expected)
+ }
+}
diff --git a/scouting/testing/BUILD b/scouting/testing/BUILD
new file mode 100644
index 0000000..43bbe06
--- /dev/null
+++ b/scouting/testing/BUILD
@@ -0,0 +1,12 @@
+py_binary(
+ name = "scouting_test_servers",
+ testonly = True,
+ srcs = [
+ "scouting_test_servers.py",
+ ],
+ data = [
+ "//scouting",
+ "//scouting/scraping:test_data",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/scouting/testing/scouting_test_servers.py b/scouting/testing/scouting_test_servers.py
new file mode 100644
index 0000000..3447815
--- /dev/null
+++ b/scouting/testing/scouting_test_servers.py
@@ -0,0 +1,108 @@
+"""This library is here to run the various servers involved in the scouting app.
+
+The servers are:
+ - The fake TBA server
+ - The actual web server
+"""
+
+import argparse
+import json
+import os
+from pathlib import Path
+import shutil
+import signal
+import socket
+import subprocess
+import sys
+import time
+from typing import List
+
+def wait_for_server(port: int):
+ """Waits for the server at the specified port to respond to TCP connections."""
+ while True:
+ try:
+ connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ connection.connect(("localhost", port))
+ connection.close()
+ break
+ except ConnectionRefusedError:
+ connection.close()
+ time.sleep(0.01)
+
+def create_tba_config(tmpdir: Path) -> Path:
+ # Configure the scouting webserver to scrape data from our fake TBA
+ # server.
+ config = tmpdir / "scouting_config.json"
+ config.write_text(json.dumps({
+ "api_key": "dummy_key_that_is_not_actually_used_in_this_test",
+ "base_url": "http://localhost:7000",
+ }))
+ return config
+
+def set_up_tba_api_dir(tmpdir: Path, year: int, event_code: str):
+ tba_api_dir = tmpdir / "api" / "v3" / "event" / f"{year}{event_code}"
+ tba_api_dir.mkdir(parents=True, exist_ok=True)
+ (tba_api_dir / "matches").write_text(
+ Path(f"scouting/scraping/test_data/{year}_{event_code}.json").read_text()
+ )
+
+class Runner:
+ """Helps manage the services we need for testing the scouting app."""
+
+ def start(self, port: int):
+ """Starts the services needed for testing the scouting app."""
+ self.tmpdir = Path(os.environ["TEST_TMPDIR"]) / "servers"
+ self.tmpdir.mkdir(exist_ok=True)
+
+ db_path = self.tmpdir / "scouting.db"
+ tba_config = create_tba_config(self.tmpdir)
+
+ self.webserver = subprocess.Popen([
+ "scouting/scouting",
+ f"--port={port}",
+ f"--database={db_path}",
+ f"--tba_config={tba_config}",
+ ])
+
+ # Create a fake TBA server to serve the static match list.
+ set_up_tba_api_dir(self.tmpdir, year=2016, event_code="nytr")
+ set_up_tba_api_dir(self.tmpdir, year=2020, event_code="fake")
+ self.fake_tba_api = subprocess.Popen(
+ ["python3", "-m", "http.server", "7000"],
+ cwd=self.tmpdir,
+ )
+
+ # Wait for the TBA server and the scouting webserver to start up.
+ wait_for_server(7000)
+ wait_for_server(port)
+
+ def stop(self):
+ """Stops the services needed for testing the scouting app."""
+ servers = (self.webserver, self.fake_tba_api)
+ for server in servers:
+ server.terminate()
+ for server in servers:
+ server.wait()
+
+ try:
+ shutil.rmtree(self.tmpdir)
+ except FileNotFoundError:
+ pass
+
+
+def main(argv: List[str]):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--port", type=int, help="The port for the actual web server.")
+ args = parser.parse_args(argv[1:])
+
+ runner = Runner()
+ runner.start(args.port)
+
+ # Wait until we're asked to shut down via CTRL-C or SIGTERM.
+ signal.pause()
+
+ runner.stop()
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/scouting/webserver/requests/debug/cli/BUILD b/scouting/webserver/requests/debug/cli/BUILD
index 903f8c8..371f66e 100644
--- a/scouting/webserver/requests/debug/cli/BUILD
+++ b/scouting/webserver/requests/debug/cli/BUILD
@@ -30,7 +30,8 @@
],
data = [
":cli",
- "//scouting/scraping:test_data",
- "//scouting/webserver",
+ ],
+ deps = [
+ "//scouting/testing:scouting_test_servers",
],
)
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index 64d79a6..f4b82b4 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -7,11 +7,15 @@
import shutil
import socket
import subprocess
+import sys
import textwrap
import time
from typing import Any, Dict, List
import unittest
+import scouting.testing.scouting_test_servers
+
+
def write_json_request(content: Dict[str, Any]):
"""Writes a JSON file with the specified dict content."""
json_path = Path(os.environ["TEST_TMPDIR"]) / "test.json"
@@ -31,72 +35,15 @@
run_result.stderr.decode("utf-8"),
)
-def wait_for_server(port: int):
- """Waits for the server at the specified port to respond to TCP connections."""
- while True:
- try:
- connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- connection.connect(("localhost", port))
- connection.close()
- break
- except ConnectionRefusedError:
- connection.close()
- time.sleep(0.01)
-
class TestDebugCli(unittest.TestCase):
def setUp(self):
- tmpdir = Path(os.environ["TEST_TMPDIR"]) / "temp"
- try:
- shutil.rmtree(tmpdir)
- except FileNotFoundError:
- pass
- os.mkdir(tmpdir)
-
- # Copy the test data into place so that the final API call can be
- # emulated.
- self.set_up_tba_api_dir(tmpdir, year=2016, event_code="nytr")
- self.set_up_tba_api_dir(tmpdir, year=2020, event_code="fake")
-
- # Create a fake TBA server to serve the static match list.
- self.fake_tba_api = subprocess.Popen(
- ["python3", "-m", "http.server", "7000"],
- cwd=tmpdir,
- )
-
- # Configure the scouting webserver to scrape data from our fake TBA
- # server.
- scouting_config = tmpdir / "scouting_config.json"
- scouting_config.write_text(json.dumps({
- "api_key": "dummy_key_that_is_not_actually_used_in_this_test",
- "base_url": "http://localhost:7000",
- }))
-
- # Run the scouting webserver.
- self.webserver = subprocess.Popen([
- "scouting/webserver/webserver_/webserver",
- "-port=8080",
- "-database=%s/database.db" % tmpdir,
- "-tba_config=%s/scouting_config.json" % tmpdir,
- ])
-
- # Wait for the servers to be reachable.
- wait_for_server(7000)
- wait_for_server(8080)
+ self.servers = scouting.testing.scouting_test_servers.Runner()
+ self.servers.start(8080)
def tearDown(self):
- self.fake_tba_api.terminate()
- self.webserver.terminate()
- self.fake_tba_api.wait()
- self.webserver.wait()
-
- def set_up_tba_api_dir(self, tmpdir, year, event_code):
- tba_api_dir = tmpdir / "api" / "v3" / "event" / f"{year}{event_code}"
- os.makedirs(tba_api_dir)
- (tba_api_dir / "matches").write_text(
- Path(f"scouting/scraping/test_data/{year}_{event_code}.json").read_text()
- )
+ self.servers.stop()
def refresh_match_list(self, year=2016, event_code="nytr"):
"""Triggers the webserver to fetch the match list."""
diff --git a/y2022/BUILD b/y2022/BUILD
index 4420894..f2bdb8b 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -203,6 +203,7 @@
"//frc971:constants",
"//frc971/control_loops:pose",
"//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
+ "//frc971/shooter_interpolation:interpolation",
"//y2022/control_loops/drivetrain:polydrivetrain_plants",
"//y2022/control_loops/superstructure/catapult:catapult_plants",
"//y2022/control_loops/superstructure/climber:climber_plants",
diff --git a/y2022/constants.cc b/y2022/constants.cc
index 980735f..2e7c5a9 100644
--- a/y2022/constants.cc
+++ b/y2022/constants.cc
@@ -130,9 +130,20 @@
catapult_params->zeroing_constants.moving_buffer_size = 20;
catapult_params->zeroing_constants.allowable_encoder_error = 0.9;
+ // Interpolation table for comp and practice robots
+ r.shot_interpolation_table = InterpolationTable<Values::ShotParams>({
+ {2, {0.08, 8.0}},
+ {5, {0.6, 10.0}},
+ });
+
switch (team) {
// A set of constants for tests.
case 1:
+ r.shot_interpolation_table = InterpolationTable<Values::ShotParams>({
+ {2, {0.08, 8.0}},
+ {5, {0.6, 10.0}},
+ });
+
climber->potentiometer_offset = 0.0;
intake_front->potentiometer_offset = 0.0;
intake_front->subsystem_params.zeroing_constants
@@ -193,6 +204,11 @@
break;
case kCodingRobotTeamNumber:
+ r.shot_interpolation_table = InterpolationTable<Values::ShotParams>({
+ {2, {0.08, 8.0}},
+ {5, {0.6, 10.0}},
+ });
+
climber->potentiometer_offset = 0.0;
intake_front->potentiometer_offset = 0.0;
intake_front->subsystem_params.zeroing_constants
diff --git a/y2022/constants.h b/y2022/constants.h
index ffe1283..21ea7a4 100644
--- a/y2022/constants.h
+++ b/y2022/constants.h
@@ -8,12 +8,15 @@
#include "frc971/constants.h"
#include "frc971/control_loops/pose.h"
#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "frc971/shooter_interpolation/interpolation.h"
#include "y2022/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
#include "y2022/control_loops/superstructure/catapult/catapult_plant.h"
#include "y2022/control_loops/superstructure/climber/climber_plant.h"
#include "y2022/control_loops/superstructure/intake/intake_plant.h"
#include "y2022/control_loops/superstructure/turret/turret_plant.h"
+using ::frc971::shooter_interpolation::InterpolationTable;
+
namespace y2022 {
namespace constants {
@@ -205,6 +208,23 @@
// TODO(milind): set this
static constexpr double kImuHeight() { return 0.0; }
+
+ struct ShotParams {
+ // Measured in radians
+ double shot_angle;
+ // Muzzle velocity (m/s) of the ball as it is released from the catapult.
+ double shot_velocity;
+
+ static ShotParams BlendY(double coefficient, ShotParams a1, ShotParams a2) {
+ using ::frc971::shooter_interpolation::Blend;
+ return ShotParams{
+ Blend(coefficient, a1.shot_angle, a2.shot_angle),
+ Blend(coefficient, a1.shot_velocity, a2.shot_velocity),
+ };
+ }
+ };
+
+ InterpolationTable<ShotParams> shot_interpolation_table;
};
// Creates and returns a Values instance for the constants.
diff --git a/y2022/control_loops/superstructure/BUILD b/y2022/control_loops/superstructure/BUILD
index e7b6559..4739cd1 100644
--- a/y2022/control_loops/superstructure/BUILD
+++ b/y2022/control_loops/superstructure/BUILD
@@ -82,6 +82,7 @@
":superstructure_output_fbs",
":superstructure_position_fbs",
":superstructure_status_fbs",
+ "//aos:flatbuffer_merge",
"//aos/events:event_loop",
"//frc971/control_loops:control_loop",
"//frc971/control_loops/drivetrain:drivetrain_status_fbs",
diff --git a/y2022/control_loops/superstructure/catapult/catapult.cc b/y2022/control_loops/superstructure/catapult/catapult.cc
index 4a644e2..8b5c7eb 100644
--- a/y2022/control_loops/superstructure/catapult/catapult.cc
+++ b/y2022/control_loops/superstructure/catapult/catapult.cc
@@ -312,25 +312,28 @@
const flatbuffers::Offset<
frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus>
-Catapult::Iterate(const Goal *unsafe_goal, const Position *position,
+Catapult::Iterate(const CatapultGoal *catapult_goal, const Position *position,
double battery_voltage, double *catapult_voltage, bool fire,
flatbuffers::FlatBufferBuilder *fbb) {
const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
- *catapult_goal = unsafe_goal != nullptr && unsafe_goal->has_catapult()
- ? (unsafe_goal->catapult()->return_position())
- : nullptr;
+ *return_goal =
+ catapult_goal != nullptr && catapult_goal->has_return_position()
+ ? catapult_goal->return_position()
+ : nullptr;
const bool catapult_disabled = catapult_.Correct(
- catapult_goal, position->catapult(), catapult_voltage == nullptr);
+ return_goal, position->catapult(), catapult_voltage == nullptr);
if (catapult_disabled) {
catapult_state_ = CatapultState::PROFILE;
- } else if (catapult_.running() && unsafe_goal &&
- unsafe_goal->has_catapult() && fire && !last_firing_) {
+ } else if (catapult_.running() && catapult_goal != nullptr && fire &&
+ !last_firing_) {
catapult_state_ = CatapultState::FIRING;
+ latched_shot_position = catapult_goal->shot_position();
+ latched_shot_velocity = catapult_goal->shot_velocity();
}
- if (catapult_.running() && unsafe_goal && unsafe_goal->has_catapult()) {
+ if (catapult_.running()) {
last_firing_ = fire;
}
@@ -354,8 +357,7 @@
catapult_mpc_.SetState(
next_X.block<2, 1>(0, 0),
- Eigen::Vector2d(unsafe_goal->catapult()->shot_position(),
- unsafe_goal->catapult()->shot_velocity()));
+ Eigen::Vector2d(latched_shot_position, latched_shot_velocity));
const bool solved = catapult_mpc_.Solve();
@@ -379,7 +381,7 @@
use_profile_ = false;
}
} else {
- if (unsafe_goal && unsafe_goal->has_catapult() && !fire) {
+ if (!fire) {
// Eh, didn't manage to solve before it was time to fire. Give up.
catapult_state_ = CatapultState::PROFILE;
}
diff --git a/y2022/control_loops/superstructure/catapult/catapult.h b/y2022/control_loops/superstructure/catapult/catapult.h
index 0606c0d..6a2c834 100644
--- a/y2022/control_loops/superstructure/catapult/catapult.h
+++ b/y2022/control_loops/superstructure/catapult/catapult.h
@@ -210,14 +210,11 @@
// shooting or not. Returns the status.
const flatbuffers::Offset<
frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus>
- Iterate(const Goal *unsafe_goal, const Position *position,
+ Iterate(const CatapultGoal *unsafe_goal, const Position *position,
double battery_voltage, double *catapult_voltage, bool fire,
flatbuffers::FlatBufferBuilder *fbb);
private:
- // TODO(austin): Prototype is just an encoder. Catapult has both an encoder
- // and pot. Switch back once we have a catapult.
- // PotAndAbsoluteEncoderSubsystem catapult_;
PotAndAbsoluteEncoderSubsystem catapult_;
catapult::CatapultController catapult_mpc_;
@@ -226,6 +223,9 @@
CatapultState catapult_state_ = CatapultState::PROFILE;
+ double latched_shot_position = 0.0;
+ double latched_shot_velocity = 0.0;
+
bool last_firing_ = false;
bool use_profile_ = true;
diff --git a/y2022/control_loops/superstructure/superstructure.cc b/y2022/control_loops/superstructure/superstructure.cc
index 0b693ef..56e58cc 100644
--- a/y2022/control_loops/superstructure/superstructure.cc
+++ b/y2022/control_loops/superstructure/superstructure.cc
@@ -1,6 +1,7 @@
#include "y2022/control_loops/superstructure/superstructure.h"
#include "aos/events/event_loop.h"
+#include "aos/flatbuffer_merge.h"
#include "y2022/control_loops/superstructure/collision_avoidance.h"
namespace y2022 {
@@ -48,6 +49,7 @@
aos::FlatbufferFixedAllocatorArray<
frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal, 64>
turret_goal_buffer;
+ aos::FlatbufferFixedAllocatorArray<CatapultGoal, 64> catapult_goal_buffer;
const aos::monotonic_clock::time_point timestamp =
event_loop()->context().monotonic_event_time;
@@ -64,6 +66,7 @@
const frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal
*turret_goal = nullptr;
+ const CatapultGoal *catapult_goal = nullptr;
double roller_speed_compensated_front = 0.0;
double roller_speed_compensated_back = 0.0;
double transfer_roller_speed_front = 0.0;
@@ -84,6 +87,31 @@
turret_goal =
unsafe_goal->auto_aim() ? auto_aim_goal : unsafe_goal->turret();
+
+ catapult_goal = unsafe_goal->catapult();
+
+ constants::Values::ShotParams shot_params;
+ const double distance_to_goal = aimer_.DistanceToGoal();
+ if (unsafe_goal->auto_aim() && values_->shot_interpolation_table.GetInRange(
+ distance_to_goal, &shot_params)) {
+ std::optional<flatbuffers::Offset<
+ frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystemGoal>>
+ return_position_offset;
+ if (unsafe_goal != nullptr && unsafe_goal->has_catapult() &&
+ unsafe_goal->catapult()->has_return_position()) {
+ return_position_offset = {
+ aos::CopyFlatBuffer(unsafe_goal->catapult()->return_position(),
+ catapult_goal_buffer.fbb())};
+ }
+ CatapultGoal::Builder builder(*catapult_goal_buffer.fbb());
+ builder.add_shot_position(shot_params.shot_angle);
+ builder.add_shot_velocity(shot_params.shot_velocity);
+ if (return_position_offset.has_value()) {
+ builder.add_return_position(return_position_offset.value());
+ }
+ catapult_goal_buffer.Finish(builder.Finish());
+ catapult_goal = &catapult_goal_buffer.message();
+ }
}
// Superstructure state machine:
@@ -395,7 +423,7 @@
// flippers
const flatbuffers::Offset<PotAndAbsoluteEncoderProfiledJointStatus>
catapult_status_offset = catapult_.Iterate(
- unsafe_goal, position, robot_state().voltage_battery(),
+ catapult_goal, position, robot_state().voltage_battery(),
output != nullptr && !catapult_.estopped()
? &(output_struct.catapult_voltage)
: nullptr,
@@ -459,6 +487,10 @@
status_builder.add_solve_time(catapult_.solve_time());
status_builder.add_shot_count(catapult_.shot_count());
status_builder.add_mpc_active(catapult_.mpc_active());
+ if (catapult_goal != nullptr) {
+ status_builder.add_shot_position(catapult_goal->shot_position());
+ status_builder.add_shot_velocity(catapult_goal->shot_velocity());
+ }
status_builder.add_flippers_open(flippers_open_);
status_builder.add_reseating_in_catapult(reseating_in_catapult_);
diff --git a/y2022/control_loops/superstructure/superstructure_lib_test.cc b/y2022/control_loops/superstructure/superstructure_lib_test.cc
index 73ceee7..e6ff227 100644
--- a/y2022/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2022/control_loops/superstructure/superstructure_lib_test.cc
@@ -1254,6 +1254,42 @@
superstructure_status_fetcher_->aimer()->turret_velocity());
}
+TEST_F(SuperstructureTest, InterpolationTableTest) {
+ SetEnabled(true);
+ WaitUntilZeroed();
+
+ constexpr double kDistance = 3.0;
+
+ SendDrivetrainStatus(0.0, {0.0, kDistance}, 0.0);
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+
+ goal_builder.add_auto_aim(true);
+
+ ASSERT_EQ(builder.Send(goal_builder.Finish()), aos::RawSender::Error::kOk);
+ }
+
+ // Give it time to stabilize.
+ RunFor(chrono::seconds(2));
+
+ ASSERT_TRUE(superstructure_status_fetcher_.Fetch());
+
+ EXPECT_NEAR(superstructure_status_fetcher_->aimer()->target_distance(),
+ kDistance, 0.01);
+
+ constants::Values::ShotParams shot_params;
+ EXPECT_TRUE(
+ values_->shot_interpolation_table.GetInRange(kDistance, &shot_params));
+
+ EXPECT_EQ(superstructure_status_fetcher_->shot_velocity(),
+ shot_params.shot_velocity);
+ EXPECT_EQ(superstructure_status_fetcher_->shot_position(),
+ shot_params.shot_angle);
+}
+
} // namespace testing
} // namespace superstructure
} // namespace control_loops
diff --git a/y2022/control_loops/superstructure/superstructure_status.fbs b/y2022/control_loops/superstructure/superstructure_status.fbs
index 4b215ac..d802f08 100644
--- a/y2022/control_loops/superstructure/superstructure_status.fbs
+++ b/y2022/control_loops/superstructure/superstructure_status.fbs
@@ -70,6 +70,8 @@
solve_time:double (id: 7);
mpc_active:bool (id: 8);
+ shot_position:double (id: 16);
+ shot_velocity:double (id: 17);
// The number of shots we have taken.
shot_count:int32 (id: 9);
diff --git a/y2022/vision/blob_detector.cc b/y2022/vision/blob_detector.cc
index aaa22f6..873cf43 100644
--- a/y2022/vision/blob_detector.cc
+++ b/y2022/vision/blob_detector.cc
@@ -13,7 +13,7 @@
DEFINE_uint64(red_delta, 100,
"Required difference between green pixels vs. red");
-DEFINE_uint64(blue_delta, 30,
+DEFINE_uint64(blue_delta, 1,
"Required difference between green pixels vs. blue");
DEFINE_bool(use_outdoors, false,
@@ -52,6 +52,14 @@
}
}
+ // Fill in the contours on the binarized image so that we don't detect
+ // multiple blobs in one
+ const auto blobs = FindBlobs(binarized_image);
+ for (auto it = blobs.begin(); it < blobs.end(); it++) {
+ cv::drawContours(binarized_image, blobs, it - blobs.begin(),
+ cv::Scalar(255), cv::FILLED);
+ }
+
return binarized_image;
}
diff --git a/y2022/vision/viewer.cc b/y2022/vision/viewer.cc
index 24c8fc6..f847dbd 100644
--- a/y2022/vision/viewer.cc
+++ b/y2022/vision/viewer.cc
@@ -212,7 +212,7 @@
TargetEstimator estimator(intrinsics, extrinsics);
- for (auto it = file_list.begin() + FLAGS_skip; it != file_list.end(); it++) {
+ for (auto it = file_list.begin() + FLAGS_skip; it < file_list.end(); it++) {
LOG(INFO) << "Reading file " << *it;
cv::Mat image_mat = cv::imread(it->c_str());
BlobDetector::BlobResult blob_result;