diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cf7fedf --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,84 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +# A second push to the same PR makes the first run's result irrelevant. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build (nix) + runs-on: ubuntu-latest + steps: + # The protocol definitions are a submodule; without them CMake fails at + # configure time with the "run git submodule update" message. + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + + # The dev shell pins eigen_5. Ubuntu ships 3.4.0 through at least 26.04 + # LTS, which lacks Matrix::canonicalEulerAngles() -- so this cannot be an + # apt-based job without patching the source. + - name: Build all targets + run: | + nix develop --command bash -c ' + cmake -B build . + make -C build -j"$(nproc)" vision_processor geometry_benchmark blob_benchmark + ' + + python: + name: Python (wrapper_backend) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: DeterminateSystems/nix-installer-action@main + - uses: DeterminateSystems/magic-nix-cache-action@main + + # Importing wrapper_backend runs protoc against the submodule and + # generates wrapper_backend/proto/, which mypy needs on its path even + # though it excludes the generated files themselves. + - name: Lint and type check + run: | + nix develop --command bash -c ' + uv sync --locked + uv run python -c "import wrapper_backend" + uv run ruff check wrapper_backend/ + uv run ruff format --check wrapper_backend/ + uv run mypy + ' + + frontend: + name: Frontend (wrapper-frontend) + runs-on: ubuntu-latest + defaults: + run: + working-directory: wrapper-frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: wrapper-frontend/package-lock.json + + - run: npm ci + + - run: npm run lint + + - run: npm run format:check + + - run: npm run check + + - run: npm run build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b077dc..9f9c1f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,8 @@ image: ubuntu:latest +variables: + GIT_SUBMODULE_STRATEGY: recursive + stages: - build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..95300df --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "proto"] + path = proto + url = https://github.com/RoboCup-SSL/ssl-protocol-defs.git + branch = main diff --git a/CMakeLists.txt b/CMakeLists.txt index 449403a..46c95fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,30 @@ link_libraries("m" "stdc++" ${YAML_CPP_LIBRARIES} ${OpenCV_LIBS} PkgConfig::LIBA include_directories(SYSTEM ${YAML_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${SPINNAKER_INCLUDE_DIRS} Eigen3::Eigen ${mvIMPACT_INCLUDE_DIRS}) include_directories(src) -file(GLOB PROTO_FILES proto/*.proto) + +set(PROTOC_OUT_DIR "${CMAKE_SOURCE_DIR}/src/proto") +include_directories("${PROTOC_OUT_DIR}") + +# league proto repository provides a generator function that fixes OUT_DIR limitations with protoc provided function +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/proto/cmake") +include(SSLProtocolDefs) +set(PROTO_FILES + vision/ssl_vision_detection.proto + vision/ssl_vision_geometry.proto + vision/ssl_vision_wrapper.proto + vision/ssl_vp_config.proto + gamecontroller/ssl_gc_common.proto + gamecontroller/ssl_gc_game_event.proto + gamecontroller/ssl_gc_geometry.proto + gamecontroller/ssl_gc_referee_message.proto +) +ssl_protocol_defs_generate_cpp( + OUT_DIR "${PROTOC_OUT_DIR}" + PROTOS ${PROTO_FILES} + SOURCES_VAR PROTO_SRCS + HEADERS_VAR PROTO_HDRS +) + file(GLOB CL_KERNELS RELATIVE "${CMAKE_SOURCE_DIR}" kernel/*.cl) file(GLOB_RECURSE SRC src/*.cpp src/*.c) list(REMOVE_ITEM SRC "${CMAKE_SOURCE_DIR}/src/main.cpp" "${CMAKE_SOURCE_DIR}/src/geometry_benchmark.cpp" "${CMAKE_SOURCE_DIR}/src/blob_benchmark.cpp") @@ -95,10 +118,8 @@ if(NOT "${CL_KERNEL_ASM_OLD}" STREQUAL "${CL_KERNEL_ASM}") message(STATUS "(Re-)Generated cl_kernels.S") endif() -add_custom_target(AUTOGENERATE DEPENDS "src/cl_kernels.S") +add_custom_target(AUTOGENERATE DEPENDS "src/cl_kernels.S" ${PROTO_SRCS} ${PROTO_HDRS}) set_property(SOURCE "src/cl_kernels.S" APPEND PROPERTY OBJECT_DEPENDS ${CL_KERNEL_PATHS}) -protobuf_generate(TARGET AUTOGENERATE LANGUAGE cpp PROTOC_OUT_DIR "${CMAKE_SOURCE_DIR}/src" PROTOS ${PROTO_FILES}) -get_property(PROTO_SRCS TARGET AUTOGENERATE PROPERTY SOURCES) list(APPEND SRC ${PROTO_SRCS} "src/cl_kernels.S") add_executable(${PROJECT_NAME} ${SRC} "src/main.cpp") diff --git a/README.md b/README.md index d29ff07..730d4c7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ A modular replacement for `geom_publisher.py` plus a browser UI: - `wrapper_backend/` — async Python (uv-managed). Owns the field geometry, absorbs incoming calibrations, exposes the bus over WebSocket. Run with `./start_wrapper.sh` (defaults to `geometry-divB.yml`). See [`wrapper_backend/README.md`](wrapper_backend/README.md). - `wrapper-frontend/` — Svelte 5 + TypeScript + Vite. Connects to the backend's WebSocket and renders the operator UI. Run with `cd wrapper-frontend && npm install && npm run dev`. See [`wrapper-frontend/README.md`](wrapper-frontend/README.md). - ## Dependency installation and compilation ### Only geom_publisher.py cam_viewer.py (e.g. vision expert laptop) @@ -58,6 +57,7 @@ Installation with PIP: `pip install protobuf pyyaml` 3. Compile vision_processor: + git submodule update --init --recursive cmake -B build . make -C build vision_processor diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..66621c9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1788179007, + "narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..878abcf --- /dev/null +++ b/flake.nix @@ -0,0 +1,91 @@ +{ + description = "SSL vision_processor build environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs }: + let + inherit (nixpkgs) lib; + systems = [ "x86_64-linux" "aarch64-linux" ]; + forAllSystems = lib.genAttrs systems; + in + { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + # Only what python/*.py needs at the system level. wrapper_backend/ is + # uv-managed (see pyproject.toml) and brings its own venv. + python = pkgs.python3.withPackages (ps: [ + ps.protobuf + ps.pyyaml + ps.opencv4 # python/cam_viewer.py + ]); + + # pocl is a CPU OpenCL runtime. It makes the build environment + # self-contained and testable without a GPU driver; a real GPU ICD on + # the host is picked up instead when OCL_ICD_VENDORS points at it. + openclRuntime = pkgs.pocl; + in + { + default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + cmake + pkg-config + protobuf # provides protoc for both C++ and Python codegen + ]; + + buildInputs = with pkgs; [ + # Deliberately the nixpkgs default (3.4.x), matching what every + # distro ships: Debian, Ubuntu through 26.04 LTS, and Fedora are + # all still on 3.4. Pinning eigen_5 here would let code compile + # that does not build for anyone packaging against a distro Eigen. + eigen + + opencv # core imgproc imgcodecs videoio + yaml-cpp + # Must match the ffmpeg nixpkgs built opencv against, currently + # 8.1.2. The default `ffmpeg` is 9.x, which links fine but loads a + # second set of libav* sonames alongside the ones opencv's videoio + # pulls in -- two copies of ffmpeg's global state in one process. + ffmpeg_8 # libavformat libavcodec libavutil + protobuf + + opencl-headers # CL/cl.h + opencl-clhpp # CL/opencl.hpp (CL_HPP_TARGET_OPENCL_VERSION=300) + ocl-icd # libOpenCL.so, the ICD loader + openclRuntime + + python + uv # wrapper_backend/ + ]; + + # The ICD loader finds runtimes through this. Without it the loader + # reports zero platforms and vision_processor exits at startup. + OCL_ICD_VENDORS = "${openclRuntime}/etc/OpenCL/vendors"; + + # uv must not download its own interpreter inside the shell. + UV_PYTHON = python.interpreter; + UV_PYTHON_DOWNLOADS = "never"; + + shellHook = '' + echo "vision_processor dev shell" + echo " eigen ${pkgs.eigen.version}" + echo " opencv ${pkgs.opencv.version}" + echo " ffmpeg ${pkgs.ffmpeg_8.version}" + echo " protobuf ${pkgs.protobuf.version}" + echo + echo " cmake -B build . && make -C build -j vision_processor" + echo + echo "Camera SDKs (Spinnaker, mvIMPACT) are proprietary and not" + echo "packaged here; the OpenCV backend is available." + ''; + }; + } + ); + }; +} diff --git a/proto b/proto new file mode 160000 index 0000000..02cbc8e --- /dev/null +++ b/proto @@ -0,0 +1 @@ +Subproject commit 02cbc8e6b6ec8e5057d5a2d49d05404efbd53c22 diff --git a/proto/ssl_gc_common.proto b/proto/ssl_gc_common.proto deleted file mode 100644 index 796e2d3..0000000 --- a/proto/ssl_gc_common.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto2"; - -option go_package = "github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"; - -// Team is either blue or yellow -enum Team { - // team not set - UNKNOWN = 0; - // yellow team - YELLOW = 1; - // blue team - BLUE = 2; -} - -// RobotId is the combination of a team and a robot id -message RobotId { - // the robot number - optional uint32 id = 1; - // the team that the robot belongs to - optional Team team = 2; -} - -// Division denotes the current division, which influences some rules -enum Division { - DIV_UNKNOWN = 0; - DIV_A = 1; - DIV_B = 2; -} diff --git a/proto/ssl_gc_game_event.proto b/proto/ssl_gc_game_event.proto deleted file mode 100644 index 315c23a..0000000 --- a/proto/ssl_gc_game_event.proto +++ /dev/null @@ -1,546 +0,0 @@ -syntax = "proto2"; - -option go_package = "github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"; - -import "proto/ssl_gc_common.proto"; -import "proto/ssl_gc_geometry.proto"; - -// GameEvent contains exactly one game event -// Each game event has optional and required fields. The required fields are mandatory to process the event. -// Some optional fields are only used for visualization, others are required to determine the ball placement position. -// If fields are missing that are required for the ball placement position, no ball placement command will be issued. -// Fields are marked optional to make testing and extending of the protocol easier. -// An autoRef should ideally set all fields, except if there are good reasons to not do so. -message GameEvent { - - optional Type type = 40; - - // The origins of this game event. - // Empty, if it originates from game controller. - // Contains autoRef name(s), if it originates from one or more autoRefs. - // Ignored if sent by autoRef to game controller. - repeated string origin = 41; - - // Unix timestamp in microseconds when the event was created. - optional uint64 created_timestamp = 49; - - // the event that occurred - oneof event { - - // Ball out of field events (stopping) - - BallLeftField ball_left_field_touch_line = 6; - BallLeftField ball_left_field_goal_line = 7; - AimlessKick aimless_kick = 11; - - // Stopping Fouls - - AttackerTooCloseToDefenseArea attacker_too_close_to_defense_area = 19; - DefenderInDefenseArea defender_in_defense_area = 31; - BoundaryCrossing boundary_crossing = 43; - KeeperHeldBall keeper_held_ball = 13; - BotDribbledBallTooFar bot_dribbled_ball_too_far = 17; - - BotPushedBot bot_pushed_bot = 24; - BotHeldBallDeliberately bot_held_ball_deliberately = 26; - BotTippedOver bot_tipped_over = 27; - - // Non-Stopping Fouls - - AttackerTouchedBallInDefenseArea attacker_touched_ball_in_defense_area = 15; - BotKickedBallTooFast bot_kicked_ball_too_fast = 18; - BotCrashUnique bot_crash_unique = 22; - BotCrashDrawn bot_crash_drawn = 21; - - // Fouls while ball out of play - - DefenderTooCloseToKickPoint defender_too_close_to_kick_point = 29; - BotTooFastInStop bot_too_fast_in_stop = 28; - BotInterferedPlacement bot_interfered_placement = 20; - - // Scoring goals - - Goal possible_goal = 39; - Goal goal = 8; - Goal invalid_goal = 44; - - // Other events - - AttackerDoubleTouchedBall attacker_double_touched_ball = 14; - PlacementSucceeded placement_succeeded = 5; - PenaltyKickFailed penalty_kick_failed = 45; - - NoProgressInGame no_progress_in_game = 2; - PlacementFailed placement_failed = 3; - MultipleCards multiple_cards = 32; - MultipleFouls multiple_fouls = 34; - BotSubstitution bot_substitution = 37; - TooManyRobots too_many_robots = 38; - ChallengeFlag challenge_flag = 46; - ChallengeFlagHandled challenge_flag_handled = 48; - EmergencyStop emergency_stop = 47; - - UnsportingBehaviorMinor unsporting_behavior_minor = 35; - UnsportingBehaviorMajor unsporting_behavior_major = 36; - } - - // the ball left the field normally - message BallLeftField { - // the team that last touched the ball - required Team by_team = 1; - // the bot that last touched the ball - optional uint32 by_bot = 2; - // the location where the ball left the field [m] - optional Vector2 location = 3; - } - // the ball left the field via goal line and a team committed an aimless kick - message AimlessKick { - // the team that last touched the ball - required Team by_team = 1; - // the bot that last touched the ball - optional uint32 by_bot = 2; - // the location where the ball left the field [m] - optional Vector2 location = 3; - // the location where the ball was last touched [m] - optional Vector2 kick_location = 4; - } - // a team shot a goal - message Goal { - // the team that scored the goal - required Team by_team = 1; - // the team that shot the goal (different from by_team for own goals) - optional Team kicking_team = 6; - // the bot that shot the goal - optional uint32 kicking_bot = 2; - // the location where the ball entered the goal [m] - optional Vector2 location = 3; - // the location where the ball was kicked (for deciding if this was a valid goal) [m] - optional Vector2 kick_location = 4; - // the maximum height the ball reached during the goal kick (for deciding if this was a valid goal) [m] - optional float max_ball_height = 5; - // number of robots of scoring team when the ball entered the goal (for deciding if this was a valid goal) - optional uint32 num_robots_by_team = 7; - // The UNIX timestamp [μs] when the scoring team last touched the ball - optional uint64 last_touch_by_team = 8; - // An additional message with e.g. a reason for invalid goals - optional string message = 9; - } - // the ball entered the goal directly during an indirect free kick - message IndirectGoal { - // the team that tried to shoot the goal - required Team by_team = 1; - // the bot that kicked the ball - at least the team must be set - optional uint32 by_bot = 2; - // the location where the ball entered the goal [m] - optional Vector2 location = 3; - // the location where the ball was kicked [m] - optional Vector2 kick_location = 4; - } - // the ball entered the goal, but was initially chipped - message ChippedGoal { - // the team that tried to shoot the goal - required Team by_team = 1; - // the bot that kicked the ball - optional uint32 by_bot = 2; - // the location where the ball entered the goal [m] - optional Vector2 location = 3; - // the location where the ball was kicked [m] - optional Vector2 kick_location = 4; - // the maximum height [m] of the ball, before it entered the goal and since the last kick [m] - optional float max_ball_height = 5; - } - // a bot moved too fast while the game was stopped - message BotTooFastInStop { - // the team that found guilty - required Team by_team = 1; - // the bot that was too fast - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the bot speed [m/s] - optional float speed = 4; - } - // a bot of the defending team got too close to the kick point during a free kick - message DefenderTooCloseToKickPoint { - // the team that was found guilty - required Team by_team = 1; - // the bot that violates the distance to the kick point - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the distance [m] from bot to the kick point (including the minimum radius) - optional float distance = 4; - } - // two robots crashed into each other with similar speeds - message BotCrashDrawn { - // the bot of the yellow team - optional uint32 bot_yellow = 1; - // the bot of the blue team - optional uint32 bot_blue = 2; - // the location of the crash (center between both bots) [m] - optional Vector2 location = 3; - // the calculated crash speed [m/s] of the two bots - optional float crash_speed = 4; - // the difference [m/s] of the velocity of the two bots - optional float speed_diff = 5; - // the angle [rad] in the range [0, π] of the bot velocity vectors - // an angle of 0 rad ( 0°) means, the bots barely touched each other - // an angle of π rad (180°) means, the bots crashed frontal into each other - optional float crash_angle = 6; - } - // two robots crashed into each other and one team was found guilty to due significant speed difference - message BotCrashUnique { - // the team that caused the crash - required Team by_team = 1; - // the bot that caused the crash - optional uint32 violator = 2; - // the bot of the opposite team that was involved in the crash - optional uint32 victim = 3; - // the location of the crash (center between both bots) [m] - optional Vector2 location = 4; - // the calculated crash speed vector [m/s] of the two bots - optional float crash_speed = 5; - // the difference [m/s] of the velocity of the two bots - optional float speed_diff = 6; - // the angle [rad] in the range [0, π] of the bot velocity vectors - // an angle of 0 rad ( 0°) means, the bots barely touched each other - // an angle of π rad (180°) means, the bots crashed frontal into each other - optional float crash_angle = 7; - } - // a bot pushed another bot over a significant distance - message BotPushedBot { - // the team that pushed the other team - required Team by_team = 1; - // the bot that pushed the other bot - optional uint32 violator = 2; - // the bot of the opposite team that was pushed - optional uint32 victim = 3; - // the location of the push (center between both bots) [m] - optional Vector2 location = 4; - // the pushed distance [m] - optional float pushed_distance = 5; - } - // a bot tipped over - message BotTippedOver { - // the team that found guilty - required Team by_team = 1; - // the bot that tipped over - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the location of the ball at the moment when this foul occurred [m] - optional Vector2 ball_location = 4; - } - // a defender other than the keeper was fully located inside its own defense and touched the ball - message DefenderInDefenseArea { - // the team that found guilty - required Team by_team = 1; - // the bot that is inside the penalty area - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the distance [m] from bot case to the nearest point outside the defense area - optional float distance = 4; - } - // a defender other than the keeper was partially located inside its own defense area and touched the ball - message DefenderInDefenseAreaPartially { - // the team that found guilty - required Team by_team = 1; - // the bot that is partially inside the penalty area - optional uint32 by_bot = 2; - // the location of the bot - optional Vector2 location = 3; - // the distance [m] that the bot is inside the penalty area - optional float distance = 4; - // the location of the ball at the moment when this foul occurred [m] - optional Vector2 ball_location = 5; - } - // an attacker touched the ball inside the opponent defense area - message AttackerTouchedBallInDefenseArea { - // the team that found guilty - required Team by_team = 1; - // the bot that is inside the penalty area - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the distance [m] that the bot is inside the penalty area - optional float distance = 4; - } - // a bot kicked the ball too fast - message BotKickedBallTooFast { - // the team that found guilty - required Team by_team = 1; - // the bot that kicked too fast - optional uint32 by_bot = 2; - // the location of the ball at the time of the highest speed [m] - optional Vector2 location = 3; - // the absolute initial ball speed (kick speed) [m/s] - optional float initial_ball_speed = 4; - // was the ball chipped? - optional bool chipped = 5; - } - // a bot dribbled to ball too far - message BotDribbledBallTooFar { - // the team that found guilty - required Team by_team = 1; - // the bot that dribbled too far - optional uint32 by_bot = 2; - // the location where the dribbling started [m] - optional Vector2 start = 3; - // the location where the maximum dribbling distance was reached [m] - optional Vector2 end = 4; - } - // an attacker touched the opponent robot inside defense area - message AttackerTouchedOpponentInDefenseArea { - // the team that found guilty - required Team by_team = 1; - // the bot that touched the opponent robot - optional uint32 by_bot = 2; - // the bot of the opposite team that was touched - optional uint32 victim = 4; - // the location of the contact point between both bots [m] - optional Vector2 location = 3; - } - // an attacker touched the ball multiple times when it was not allowed to - message AttackerDoubleTouchedBall { - // the team that found guilty - required Team by_team = 1; - // the bot that touched the ball twice - optional uint32 by_bot = 2; - // the location of the ball when it was first touched [m] - optional Vector2 location = 3; - } - // an attacker was located too near to the opponent defense area during stop or free kick - message AttackerTooCloseToDefenseArea { - // the team that found guilty - required Team by_team = 1; - // the bot that is too close to the defense area - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - // the distance [m] of the bot to the penalty area - optional float distance = 4; - // the location of the ball at the moment when this foul occurred [m] - optional Vector2 ball_location = 5; - } - // a bot held the ball for too long - message BotHeldBallDeliberately { - // the team that found guilty - required Team by_team = 1; - // the bot that holds the ball - optional uint32 by_bot = 2; - // the location of the ball [m] - optional Vector2 location = 3; - // the duration [s] that the bot hold the ball - optional float duration = 4; - } - // a bot interfered the ball placement of the other team - message BotInterferedPlacement { - // the team that found guilty - required Team by_team = 1; - // the bot that interfered the placement - optional uint32 by_bot = 2; - // the location of the bot [m] - optional Vector2 location = 3; - } - // a team collected multiple cards (yellow and red), which results in a penalty kick - message MultipleCards { - // the team that received multiple yellow cards - required Team by_team = 1; - } - // a team collected multiple fouls, which results in a yellow card - message MultipleFouls { - // the team that collected multiple fouls - required Team by_team = 1; - // the list of game events that caused the multiple fouls - repeated GameEvent caused_game_events = 2; - } - // a team failed to place the ball multiple times in a row - message MultiplePlacementFailures { - // the team that failed multiple times - required Team by_team = 1; - } - // timeout waiting for the attacking team to perform the free kick - message KickTimeout { - // the team that that should have kicked - required Team by_team = 1; - // the location of the ball [m] - optional Vector2 location = 2; - // the time [s] that was waited - optional float time = 3; - } - // game was stuck - message NoProgressInGame { - // the location of the ball - optional Vector2 location = 1; - // the time [s] that was waited - optional float time = 2; - } - // ball placement failed - message PlacementFailed { - // the team that failed - required Team by_team = 1; - // the remaining distance [m] from ball to placement position - optional float remaining_distance = 2; - } - // a team was found guilty for minor unsporting behavior - message UnsportingBehaviorMinor { - // the team that found guilty - required Team by_team = 1; - // an explanation of the situation and decision - required string reason = 2; - } - // a team was found guilty for major unsporting behavior - message UnsportingBehaviorMajor { - // the team that found guilty - required Team by_team = 1; - // an explanation of the situation and decision - required string reason = 2; - } - // a keeper held the ball in its defense area for too long - message KeeperHeldBall { - // the team that found guilty - required Team by_team = 1; - // the location of the ball [m] - optional Vector2 location = 2; - // the duration [s] that the keeper hold the ball - optional float duration = 3; - } - // a team successfully placed the ball - message PlacementSucceeded { - // the team that did the placement - required Team by_team = 1; - // the time [s] taken for placing the ball - optional float time_taken = 2; - // the distance [m] between placement location and actual ball position - optional float precision = 3; - // the distance [m] between the initial ball location and the placement position - optional float distance = 4; - } - // both teams are prepared - all conditions are met to continue (with kickoff or penalty kick) - message Prepared { - // the time [s] taken for preparing - optional float time_taken = 1; - } - // bots are being substituted by a team - message BotSubstitution { - // the team that substitutes robots - required Team by_team = 1; - } - // A challenge flag, requested by a team previously, is flagged - message ChallengeFlag { - // the team that requested the challenge flag - required Team by_team = 1; - } - // A challenge, flagged recently, has been handled by the referee - message ChallengeFlagHandled { - // the team that requested the challenge flag - required Team by_team = 1; - // the challenge was accepted by the referee - required bool accepted = 2; - } - // An emergency stop, requested by team previously, occurred - message EmergencyStop { - // the team that substitutes robots - required Team by_team = 1; - } - // a team has too many robots on the field - message TooManyRobots { - // the team that has too many robots - required Team by_team = 1; - // number of robots allowed at the moment - optional int32 num_robots_allowed = 2; - // number of robots currently on the field - optional int32 num_robots_on_field = 3; - // the location of the ball at the moment when this foul occurred [m] - optional Vector2 ball_location = 4; - } - // a robot chipped the ball over the field boundary out of the playing surface - message BoundaryCrossing { - // the team that has too many robots - required Team by_team = 1; - // the location of the ball [m] - optional Vector2 location = 2; - } - // the penalty kick failed (by time or by keeper) - message PenaltyKickFailed { - // the team that last touched the ball - required Team by_team = 1; - // the location of the ball at the moment of this event [m] - optional Vector2 location = 2; - // an explanation of the failure - optional string reason = 3; - } - - enum Type { - UNKNOWN_GAME_EVENT_TYPE = 0; - - // Ball out of field events (stopping) - - BALL_LEFT_FIELD_TOUCH_LINE = 6; // triggered by autoRef - BALL_LEFT_FIELD_GOAL_LINE = 7; // triggered by autoRef - AIMLESS_KICK = 11; // triggered by autoRef - - // Stopping Fouls - - ATTACKER_TOO_CLOSE_TO_DEFENSE_AREA = 19; // triggered by autoRef - DEFENDER_IN_DEFENSE_AREA = 31; // triggered by autoRef - BOUNDARY_CROSSING = 41; // triggered by autoRef - KEEPER_HELD_BALL = 13; // triggered by GC - BOT_DRIBBLED_BALL_TOO_FAR = 17; // triggered by autoRef - - BOT_PUSHED_BOT = 24; // triggered by human ref - BOT_HELD_BALL_DELIBERATELY = 26; // triggered by human ref - BOT_TIPPED_OVER = 27; // triggered by human ref - - // Non-Stopping Fouls - - ATTACKER_TOUCHED_BALL_IN_DEFENSE_AREA = 15; // triggered by autoRef - BOT_KICKED_BALL_TOO_FAST = 18; // triggered by autoRef - BOT_CRASH_UNIQUE = 22; // triggered by autoRef - BOT_CRASH_DRAWN = 21; // triggered by autoRef - - // Fouls while ball out of play - - DEFENDER_TOO_CLOSE_TO_KICK_POINT = 29; // triggered by autoRef - BOT_TOO_FAST_IN_STOP = 28; // triggered by autoRef - BOT_INTERFERED_PLACEMENT = 20; // triggered by autoRef - - // Scoring goals - - POSSIBLE_GOAL = 39; // triggered by autoRef - GOAL = 8; // triggered by GC - INVALID_GOAL = 42; // triggered by GC - - // Other events - - ATTACKER_DOUBLE_TOUCHED_BALL = 14; // triggered by autoRef - PLACEMENT_SUCCEEDED = 5; // triggered by autoRef - PENALTY_KICK_FAILED = 43; // triggered by GC and autoRef - - NO_PROGRESS_IN_GAME = 2; // triggered by GC - PLACEMENT_FAILED = 3; // triggered by GC - MULTIPLE_CARDS = 32; // triggered by GC - MULTIPLE_FOULS = 34; // triggered by GC - BOT_SUBSTITUTION = 37; // triggered by GC - TOO_MANY_ROBOTS = 38; // triggered by GC - CHALLENGE_FLAG = 44; // triggered by GC - CHALLENGE_FLAG_HANDLED = 46; // triggered by GC - EMERGENCY_STOP = 45; // triggered by GC - - UNSPORTING_BEHAVIOR_MINOR = 35; // triggered by human ref - UNSPORTING_BEHAVIOR_MAJOR = 36; // triggered by human ref - - // Deprecated events - reserved 1; - reserved 9; - reserved 10; - reserved 12; - reserved 16; - reserved 40; - reserved 23; - reserved 25; - reserved 30; - reserved 33; - } -} diff --git a/proto/ssl_gc_geometry.proto b/proto/ssl_gc_geometry.proto deleted file mode 100644 index 2faa403..0000000 --- a/proto/ssl_gc_geometry.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto2"; - -// A vector with two dimensions -message Vector2 { - required float x = 1; - required float y = 2; -} - -// A vector with three dimensions -message Vector3 { - required float x = 1; - required float y = 2; - required float z = 3; -} diff --git a/proto/ssl_gc_referee_message.proto b/proto/ssl_gc_referee_message.proto deleted file mode 100644 index 0ea57a4..0000000 --- a/proto/ssl_gc_referee_message.proto +++ /dev/null @@ -1,216 +0,0 @@ -syntax = "proto2"; - -option go_package = "github.com/RoboCup-SSL/ssl-game-controller/internal/app/state"; - -import "proto/ssl_gc_game_event.proto"; - -// Each UDP packet contains one of these messages. -message Referee { - // A random UUID of the source that is kept constant at the source while running - // If multiple sources are broadcasting to the same network, this id can be used to identify individual sources - optional string source_identifier = 18; - - // The match type is a meta information about the current match that helps to process the logs after a competition - optional MatchType match_type = 19 [default = UNKNOWN_MATCH]; - - // The UNIX timestamp when the packet was sent, in microseconds. - // Divide by 1,000,000 to get a time_t. - required uint64 packet_timestamp = 1; - - // These are the "coarse" stages of the game. - enum Stage { - // The first half is about to start. - // A kickoff is called within this stage. - // This stage ends with the NORMAL_START. - NORMAL_FIRST_HALF_PRE = 0; - // The first half of the normal game, before half time. - NORMAL_FIRST_HALF = 1; - // Half time between first and second halves. - NORMAL_HALF_TIME = 2; - // The second half is about to start. - // A kickoff is called within this stage. - // This stage ends with the NORMAL_START. - NORMAL_SECOND_HALF_PRE = 3; - // The second half of the normal game, after half time. - NORMAL_SECOND_HALF = 4; - // The break before extra time. - EXTRA_TIME_BREAK = 5; - // The first half of extra time is about to start. - // A kickoff is called within this stage. - // This stage ends with the NORMAL_START. - EXTRA_FIRST_HALF_PRE = 6; - // The first half of extra time. - EXTRA_FIRST_HALF = 7; - // Half time between first and second extra halves. - EXTRA_HALF_TIME = 8; - // The second half of extra time is about to start. - // A kickoff is called within this stage. - // This stage ends with the NORMAL_START. - EXTRA_SECOND_HALF_PRE = 9; - // The second half of extra time. - EXTRA_SECOND_HALF = 10; - // The break before penalty shootout. - PENALTY_SHOOTOUT_BREAK = 11; - // The penalty shootout. - PENALTY_SHOOTOUT = 12; - // The game is over. - POST_GAME = 13; - } - required Stage stage = 2; - - // The number of microseconds left in the stage. - // The following stages have this value; the rest do not: - // NORMAL_FIRST_HALF - // NORMAL_HALF_TIME - // NORMAL_SECOND_HALF - // EXTRA_TIME_BREAK - // EXTRA_FIRST_HALF - // EXTRA_HALF_TIME - // EXTRA_SECOND_HALF - // PENALTY_SHOOTOUT_BREAK - // - // If the stage runs over its specified time, this value - // becomes negative. - optional sint32 stage_time_left = 3; - - // These are the "fine" states of play on the field. - enum Command { - // All robots should completely stop moving. - HALT = 0; - // Robots must keep 50 cm from the ball. - STOP = 1; - // A prepared kickoff or penalty may now be taken. - NORMAL_START = 2; - // The ball is dropped and free for either team. - FORCE_START = 3; - // The yellow team may move into kickoff position. - PREPARE_KICKOFF_YELLOW = 4; - // The blue team may move into kickoff position. - PREPARE_KICKOFF_BLUE = 5; - // The yellow team may move into penalty position. - PREPARE_PENALTY_YELLOW = 6; - // The blue team may move into penalty position. - PREPARE_PENALTY_BLUE = 7; - // The yellow team may take a direct free kick. - DIRECT_FREE_YELLOW = 8; - // The blue team may take a direct free kick. - DIRECT_FREE_BLUE = 9; - // The yellow team is currently in a timeout. - TIMEOUT_YELLOW = 12; - // The blue team is currently in a timeout. - TIMEOUT_BLUE = 13; - // Equivalent to STOP, but the yellow team must pick up the ball and - // drop it in the Designated Position. - BALL_PLACEMENT_YELLOW = 16; - // Equivalent to STOP, but the blue team must pick up the ball and drop - // it in the Designated Position. - BALL_PLACEMENT_BLUE = 17; - } - required Command command = 4; - - // The number of commands issued since startup (mod 2^32). - required uint32 command_counter = 5; - - // The UNIX timestamp when the command was issued, in microseconds. - // This value changes only when a new command is issued, not on each packet. - required uint64 command_timestamp = 6; - - // Information about a single team. - message TeamInfo { - // The team's name (empty string if operator has not typed anything). - required string name = 1; - // The number of goals scored by the team during normal play and overtime. - required uint32 score = 2; - // The number of red cards issued to the team since the beginning of the game. - required uint32 red_cards = 3; - // The amount of time (in microseconds) left on each yellow card issued to the team. - // If no yellow cards are issued, this array has no elements. - // Otherwise, times are ordered from smallest to largest. - repeated uint32 yellow_card_times = 4 [packed = true]; - // The total number of yellow cards ever issued to the team. - required uint32 yellow_cards = 5; - // The number of timeouts this team can still call. - // If in a timeout right now, that timeout is excluded. - required uint32 timeouts = 6; - // The number of microseconds of timeout this team can use. - required uint32 timeout_time = 7; - // The pattern number of this team's goalkeeper. - required uint32 goalkeeper = 8; - // The total number of countable fouls that act towards yellow cards - optional uint32 foul_counter = 9; - // The number of consecutive ball placement failures of this team - optional uint32 ball_placement_failures = 10; - // Indicate if the team is able and allowed to place the ball - optional bool can_place_ball = 12; - // The maximum number of bots allowed on the field based on division and cards - optional uint32 max_allowed_bots = 13; - // The team has submitted an intent to substitute one or more robots at the next chance - optional bool bot_substitution_intent = 14; - // Indicate if the team reached the maximum allowed ball placement failures and is thus not allowed to place the ball anymore - optional bool ball_placement_failures_reached = 15; - // The team is allowed to substitute one or more robots currently - optional bool bot_substitution_allowed = 16; - } - - // Information about the two teams. - required TeamInfo yellow = 7; - required TeamInfo blue = 8; - - // The coordinates of the Designated Position. These are measured in - // millimetres and correspond to SSL-Vision coordinates. These fields are - // always either both present (in the case of a ball placement command) or - // both absent (in the case of any other command). - message Point { - required float x = 1; - required float y = 2; - } - optional Point designated_position = 9; - - // Information about the direction of play. - // True, if the blue team will have it's goal on the positive x-axis of the ssl-vision coordinate system. - // Obviously, the yellow team will play on the opposite half. - optional bool blue_team_on_positive_half = 10; - - reserved 11; - - // The command that will be issued after the current stoppage and ball placement to continue the game. - optional Command next_command = 12; - - // All game events that were detected since the last RUNNING state. - // Will be cleared as soon as the game is continued. - reserved 13; - repeated GameEvent game_events = 16; - - // All non-finished proposed game events that may be processed next. - reserved 14; - repeated GameEventProposalGroup game_event_proposals = 17; - - // The time in microseconds that is remaining until the current action times out - // The time will not be reset. It can get negative. - // An autoRef would raise an appropriate event, if the time gets negative. - // Possible actions where this time is relevant: - // * free kicks - // * kickoff, penalty kick, force start - // * ball placement - optional int32 current_action_time_remaining = 15; -} - -// List of matching proposals -message GameEventProposalGroup { - // The proposed game event. - repeated GameEvent game_event = 1; - // Whether the proposal group was accepted - optional bool accepted = 2; -} - -// MatchType is a meta information about the current match for easier log processing -enum MatchType { - // not set - UNKNOWN_MATCH = 0; - // match is part of the group phase - GROUP_PHASE = 1; - // match is part of the elimination phase - ELIMINATION_PHASE = 2; - // a friendly match, not part of a tournament - FRIENDLY = 3; -} diff --git a/proto/ssl_vision_detection.proto b/proto/ssl_vision_detection.proto deleted file mode 100644 index 442af74..0000000 --- a/proto/ssl_vision_detection.proto +++ /dev/null @@ -1,59 +0,0 @@ -syntax = "proto2"; - -message SSL_DetectionBall { - // Confidence in [0-1] of the detection - required float confidence = 1; - optional uint32 area = 2; - // X-coordinate in [mm] in global ssl-vision coordinate system - required float x = 3; - // Y-coordinate in [mm] in global ssl-vision coordinate system - required float y = 4; - // Z-coordinate in [mm] in global ssl-vision coordinate system - // Not supported by ssl-vision, but might be set by simulators - optional float z = 5; - // X-coordinate in [pixel] in the image - required float pixel_x = 6; - // Y-coordinate in [pixel] in the image - required float pixel_y = 7; -} - -message SSL_DetectionRobot { - // Confidence in [0-1] of the detection - required float confidence = 1; - // Id of the robot - optional uint32 robot_id = 2; - // X-coordinate in [mm] in global ssl-vision coordinate system - required float x = 3; - // Y-coordinate in [mm] in global ssl-vision coordinate system - required float y = 4; - // Orientation in [rad] - optional float orientation = 5; - // X-coordinate in [pixel] in the image - required float pixel_x = 6; - // Y-coordinate in [pixel] in the image - required float pixel_y = 7; - // Height, as configured in ssl-vision for the respective team - optional float height = 8; -} - -message SSL_DetectionFrame { - // monotonously increasing frame number - required uint32 frame_number = 1; - // Unix timestamp in [seconds] at which the image has been received by ssl-vision - required double t_capture = 2; - // Unix timestamp in [seconds] at which this message has been sent to the network - required double t_sent = 3; - // Camera timestamp in [seconds] as reported by the camera, if supported - // This is not necessarily a unix timestamp - optional double t_capture_camera = 8; - // Internal VisionProcessor field for clock synchronization. - repeated float t_offsets = 9; - // Identifier of the camera - required uint32 camera_id = 4; - // Detected balls - repeated SSL_DetectionBall balls = 5; - // Detected yellow robots - repeated SSL_DetectionRobot robots_yellow = 6; - // Detected blue robots - repeated SSL_DetectionRobot robots_blue = 7; -} diff --git a/proto/ssl_vision_detection_tracked.proto b/proto/ssl_vision_detection_tracked.proto deleted file mode 100644 index f843a50..0000000 --- a/proto/ssl_vision_detection_tracked.proto +++ /dev/null @@ -1,88 +0,0 @@ -syntax = "proto2"; - -// Default network address: 224.5.23.2:10010 - -import "proto/ssl_gc_common.proto"; -import "proto/ssl_gc_geometry.proto"; - -// Capabilities that a source implementation can have -enum Capability { - CAPABILITY_UNKNOWN = 0; - CAPABILITY_DETECT_FLYING_BALLS = 1; - CAPABILITY_DETECT_MULTIPLE_BALLS = 2; - CAPABILITY_DETECT_KICKED_BALLS = 3; -} - -// A single tracked ball -message TrackedBall { - // The position (x, y, height) [m] in the ssl-vision coordinate system - required Vector3 pos = 1; - - // The velocity [m/s] in the ssl-vision coordinate system - optional Vector3 vel = 2; - - // The visibility of the ball - // A value between 0 (not visible) and 1 (visible) - // The exact implementation depends on the source software - optional float visibility = 3; -} - -// A ball kicked by a robot, including predictions when the ball will come to a stop -message KickedBall { - // The initial position [m] from which the ball was kicked - required Vector2 pos = 1; - // The initial velocity [m/s] with which the ball was kicked - required Vector3 vel = 2; - // The unix timestamp [s] when the kick was performed - required double start_timestamp = 3; - - // The predicted unix timestamp [s] when the ball comes to a stop - optional double stop_timestamp = 4; - // The predicted position [m] at which the ball will come to a stop - optional Vector2 stop_pos = 5; - - // The robot that kicked the ball - optional RobotId robot_id = 6; -} - -// A single tracked robot -message TrackedRobot { - required RobotId robot_id = 1; - - // The position [m] in the ssl-vision coordinate system - required Vector2 pos = 2; - // The orientation [rad] in the ssl-vision coordinate system - required float orientation = 3; - - // The velocity [m/s] in the ssl-vision coordinate system - optional Vector2 vel = 4; - // The angular velocity [rad/s] in the ssl-vision coordinate system - optional float vel_angular = 5; - - // The visibility of the robot - // A value between 0 (not visible) and 1 (visible) - // The exact implementation depends on the source software - optional float visibility = 6; -} - -// A frame that contains all currently tracked objects on the field on all cameras -message TrackedFrame { - // A monotonous increasing frame counter - required uint32 frame_number = 1; - // The unix timestamp in [s] of the data - required double timestamp = 2; - - // The list of detected balls - // The first ball is the primary one - // Sources may add additional balls based on their capabilities - repeated TrackedBall balls = 3; - // The list of detected robots of both teams - repeated TrackedRobot robots = 4; - - // Information about a kicked ball, if the ball was kicked by a robot and is still moving - // Note: This field is optional. Some source implementations might not set this at any time - optional KickedBall kicked_ball = 5; - - // List of capabilities of the source implementation - repeated Capability capabilities = 6; -} diff --git a/proto/ssl_vision_geometry.proto b/proto/ssl_vision_geometry.proto deleted file mode 100644 index 972562e..0000000 --- a/proto/ssl_vision_geometry.proto +++ /dev/null @@ -1,155 +0,0 @@ -syntax = "proto2"; -// A 2D float vector. -message Vector2f { - // X-coordinate in mm - required float x = 1; - // Y-coordinate in mm - required float y = 2; -} - -// Represents a field marking as a line segment represented by a start point p1, -// and end point p2, and a line thickness. The start and end points are along -// the center of the line, so the thickness of the line extends by thickness / 2 -// on either side of the line. -message SSL_FieldLineSegment { - // Name of this field marking. - required string name = 1; - // Start point of the line segment. - required Vector2f p1 = 2; - // End point of the line segment. - required Vector2f p2 = 3; - // Thickness of the line segment. - required float thickness = 4; - // The type of this shape - optional SSL_FieldShapeType type = 5; -} - -// Represents a field marking as a circular arc segment represented by center point, a -// start angle, an end angle, and an arc thickness. -message SSL_FieldCircularArc { - // Name of this field marking. - required string name = 1; - // Center point of the circular arc. - required Vector2f center = 2; - // Radius of the arc. - required float radius = 3; - // Start angle in counter-clockwise order. - required float a1 = 4; - // End angle in counter-clockwise order. - required float a2 = 5; - // Thickness of the arc. - required float thickness = 6; - // The type of this shape - optional SSL_FieldShapeType type = 7; -} - -message SSL_GeometryFieldSize { - // Field length (distance between goal lines) in mm - required int32 field_length = 1; - // Field width (distance between touch lines) in mm - required int32 field_width = 2; - // Goal width (distance between inner edges of goal posts) in mm - required int32 goal_width = 3; - // Goal depth (distance from outer goal line edge to inner goal back) in mm - required int32 goal_depth = 4; - // Boundary width (distance from touch line centers to boundary walls) in mm - required int32 boundary_width = 5; - // Boundary width at the goal lines (distance from goal line centers to boundary walls) in mm - optional int32 boundary_width_goal_line = 16; - // Generated line segments based on the other parameters - repeated SSL_FieldLineSegment field_lines = 6; - // Generated circular arcs based on the other parameters - repeated SSL_FieldCircularArc field_arcs = 7; - // Depth of the penalty/defense area (measured between line centers) in mm - optional int32 penalty_area_depth = 8; - // Width of the penalty/defense area (measured between line centers) in mm - optional int32 penalty_area_width = 9; - // Radius of the center circle (measured between line centers) in mm - optional int32 center_circle_radius = 10; - // Thickness/width of the lines on the field in mm - optional int32 line_thickness = 11; - // Distance between the goal center and the center of the penalty mark in mm - optional int32 goal_center_to_penalty_mark = 12; - // Goal height in mm - optional int32 goal_height = 13; - // Ball radius in mm (note that this is a float type to represent sub-mm precision) - optional float ball_radius = 14; - // Max allowed robot radius in mm (note that this is a float type to represent sub-mm precision) - optional float max_robot_radius = 15; - // Width of the goal substitution area (distance from goal line center plus boundary width to boundary walls) in mm - optional int32 goal_substitution_area_width = 17; -} - -message SSL_GeometryCameraCalibration { - required uint32 camera_id = 1; - required float focal_length = 2; - required float principal_point_x = 3; - required float principal_point_y = 4; - required float distortion = 5; - required float q0 = 6; - required float q1 = 7; - required float q2 = 8; - required float q3 = 9; - required float tx = 10; - required float ty = 11; - required float tz = 12; - optional float derived_camera_world_tx = 13; - optional float derived_camera_world_ty = 14; - optional float derived_camera_world_tz = 15; - optional uint32 pixel_image_width = 16; - optional uint32 pixel_image_height = 17; -} - -// Two-Phase model for straight-kicked balls. -// There are two phases with different accelerations during the ball kicks: -// 1. Sliding -// 2. Rolling -// The full model is described in the TDP of ER-Force from 2016, which can be found here: -// https://ssl.robocup.org/wp-content/uploads/2019/01/2016_ETDP_ER-Force.pdf -message SSL_BallModelStraightTwoPhase { - // Ball sliding acceleration [m/s^2] (should be negative) - required double acc_slide = 1; - // Ball rolling acceleration [m/s^2] (should be negative) - required double acc_roll = 2; - // Fraction of the initial velocity where the ball starts to roll - required double k_switch = 3; -} - -// Fixed-Loss model for chipped balls. -// Uses fixed damping factors for xy and z direction per hop. -message SSL_BallModelChipFixedLoss { - // Chip kick velocity damping factor in XY direction for the first hop - required double damping_xy_first_hop = 1; - // Chip kick velocity damping factor in XY direction for all following hops - required double damping_xy_other_hops = 2; - // Chip kick velocity damping factor in Z direction for all hops - required double damping_z = 3; -} - -message SSL_GeometryModels { - optional SSL_BallModelStraightTwoPhase straight_two_phase = 1; - optional SSL_BallModelChipFixedLoss chip_fixed_loss = 2; -} - -message SSL_GeometryData { - required SSL_GeometryFieldSize field = 1; - repeated SSL_GeometryCameraCalibration calib = 2; - optional SSL_GeometryModels models = 3; -} - -enum SSL_FieldShapeType { - Undefined = 0; - CenterCircle = 1; - TopTouchLine = 2; - BottomTouchLine = 3; - LeftGoalLine = 4; - RightGoalLine = 5; - HalfwayLine = 6; - CenterLine = 7; - LeftPenaltyStretch = 8; - RightPenaltyStretch = 9; - LeftFieldLeftPenaltyStretch = 10; - LeftFieldRightPenaltyStretch = 11; - RightFieldLeftPenaltyStretch = 12; - RightFieldRightPenaltyStretch = 13; -} diff --git a/proto/ssl_vision_wrapper.proto b/proto/ssl_vision_wrapper.proto deleted file mode 100644 index a89cdd5..0000000 --- a/proto/ssl_vision_wrapper.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto2"; -import "proto/ssl_vision_detection.proto"; -import "proto/ssl_vision_geometry.proto"; -import "proto/ssl_vp_config.proto"; - -enum SSL_Source { - SSL_SOURCE_UNKNOWN = 0; - SSL_SOURCE_OTHER = 1; - SSL_SOURCE_SSL_VISION = 2; - SSL_SOURCE_VISION_PROCESSOR = 3; - SSL_SOURCE_GRSIM = 4; - SSL_SOURCE_ERFORCE_SIM = 5; -} - -message SSL_WrapperPacket { - optional SSL_DetectionFrame detection = 1; - optional SSL_GeometryData geometry = 2; - optional SSL_Source source = 3; - optional SSL_VPConfig config = 4; -} diff --git a/proto/ssl_vision_wrapper_tracked.proto b/proto/ssl_vision_wrapper_tracked.proto deleted file mode 100644 index 8344777..0000000 --- a/proto/ssl_vision_wrapper_tracked.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto2"; -import "proto/ssl_vision_detection_tracked.proto"; - -// A wrapper packet containing meta data of the source -// Also serves for the possibility to extend the protocol later -message TrackerWrapperPacket { - // A random UUID of the source that is kept constant at the source while running - // If multiple sources are broadcasting to the same network, this id can be used to identify individual sources - required string uuid = 1; - // The name of the source software that is producing this messages. - optional string source_name = 2; - // The tracked frame - optional TrackedFrame tracked_frame = 3; -} diff --git a/proto/ssl_vp_config.proto b/proto/ssl_vp_config.proto deleted file mode 100644 index 291076a..0000000 --- a/proto/ssl_vp_config.proto +++ /dev/null @@ -1,168 +0,0 @@ -syntax = "proto2"; -import "proto/ssl_gc_geometry.proto"; - -enum SSL_VPConfigCameraDriver { - // FLIR cameras - SPINNAKER = 0; - // Video and image files, video4linux2 (e.g. webcams) cameras - OPENCV = 1; - // Bluefox3 cameras - MVIMPACT = 2; -} - -enum SSL_VpConfigCameraWBType { - OUTDOOR = 0; - INDOOR = 1; - MANUAL = 2; -} - -message SSL_VPConfigCamera { - // Camera driver type. Availability depending on libraries installed. - optional SSL_VPConfigCameraDriver driver = 1; - - // Spinnaker, mvIMPACT or OpenCV(v4l2) camera id - optional uint32 id = 2 [default = 0]; - // OpenCV camera device, image or video file path (if unset id is used instead) - optional string path = 3 [default = ""]; - - // Camera resolution as reported by the camera. VisionProcessor might use half (true color) resolution internally with SPINNAKER and MVIMPACT. - // 0 = Highest resolution supported by camera - optional uint32 width = 4 [default = 0]; - optional uint32 height = 5 [default = 0]; - - // Camera exposure time in ms to adjust brightness. Higher values lead to more motion blur. Lower values lead to a darker image. - // Camera frame rate will drop if set equal to or higher than the frame time (1 / fps_cam). - // 0.0 = automatic exposure - optional float exposure = 6 [default = 0.0]; - // Camera gain to adjust brightness. Higher values lead to more noise. Lower values lead to a darker image. - // Frequently changing bot identities or team colors indicate an image that is too bright (all colors becoming nearly white). - // 0.0 = automatic gain - optional float gain = 7 [default = 0.0]; - // Camera gamma as nonlinear brightness adjustment. Only supported on SPINNAKER and OPENCV. - // Low values reduce differences between bright and dark illuminated areas. High values increase the color contrast. - // 1.0 = no gamma - optional float gamma = 8 [default = 1.0]; - - // Camera white balance. OUTDOOR or INDOOR for auto white balance with green or gray carpet respectively. - // (distinction between OUTDOOR and INDOOR currently only implemented for SPINNAKER backend) - optional SSL_VpConfigCameraWBType wb_type = 9 [default = OUTDOOR]; - optional float wb_red = 10 [default = 1.0]; - optional float wb_blue = 11 [default = 1.0]; -} - -message SSL_VPConfigGeometry { - // Total camera amount over the field - optional uint32 camera_amount = 1 [default = 1]; - // Camera height above field in mm - // 0.0 = automated camera height calibration, this does not work if the camera looks perpendicular to the field. - optional float camera_height = 2 [default = 0.0]; - - // Field line crossings at the edges of the camera extent in pixels from the top left image corner. - // The first coordinate must be the edge with the smallest x and y field coordinate. - repeated Vector2 line_corners = 3; - - // Enable field line pixel refinement. - // Disable in cluttered environments when the geometry calibration has failed multiple times in a row. - optional bool refinement = 4 [default=true]; -} - -message SSL_VPConfigThresholds { - // Minimum mean cosine similarity (not normalized) of the blob border gradient (0 - 195075.0) - // High value lead to undetected blobs. Low values lead to an increase in false positive blob detections. - optional float circularity = 1 [default=15.0]; - // Minimum circularity/(3*stddev) ball blob score - optional float score = 2 [default=5.0]; - - // Minimum confidence (0.0 - 1.0) - optional float min_confidence = 3 [default=0.2]; - - // Min ball camera edge distance in mm - optional float min_cam_edge_distance = 4 [default=170.0]; - - // Maximum allowed clipping of two objects in mm - optional float clipping_tolerance = 5 [default=10.0]; - - // Added tolerance to geometry related operations in mm. - // Affects field border cutoff area and field line blob classification. - optional float geometry_tolerance = 6 [default=10.0]; -} - -message Color { - optional uint32 red = 1 [default=128]; - optional uint32 green = 2 [default=128]; - optional uint32 blue = 3 [default=128]; -} - -message VPColor { - optional Color reference = 1; - optional Color live = 2; -} - -message SSL_VPConfigColor { - // Strength of the reference color during color updates (0.0 - 0.5-history_force/2) - optional float reference_force = 1 [default=0.1]; - // Strength of the previous color during color updates (0.0 - 1.0-reference_force) - optional float history_force = 2 [default=0.7]; - - // Colors used for ball determination - optional VPColor orange = 3; - optional VPColor field = 4; - // Colors used for center blob determination - optional VPColor yellow = 5; - optional VPColor blue = 6; - // Colors used for side blob determination - optional VPColor green = 7; - optional VPColor pink = 8; -} - -message SSL_VPConfigNetwork { - // Game controller ip address - optional string gc_ip = 1 [default="224.5.23.1"]; - // Game controller UDP port - optional uint32 gc_port = 2 [default=10003]; - - // Vision IP address (port impossible to adjust over network) - optional string vision_ip = 3 [default="224.5.23.2"]; -} - -enum SSL_VPConfigStreamType { - RAW = 0; - REPROJ_FALSE_COLOR = 1; - REPROJ_GRADIENT_DOT = 2; - REPROJ_BLOB_SCORE = 3; -} - -message SSL_VPConfigStream { - // If false no network live stream will be encoded and sent. - optional bool active = 1 [default=true]; - // Type of image to transmit. - optional SSL_VPConfigStreamType type = 2 [default=RAW]; - // Camera RTP stream destination IP - optional string stream_ip = 3 [default="127.0.0.1"]; - optional uint32 stream_port = 4 [default=10100]; -} - -message SSL_VPConfig { - // Persistent human-readable network unique identifier (e.g. hostname or hostname:suffix if multiple VP instances on the same host). - required string instance = 1; - // Corresponding to the camera_id used in SSL_DetectionFrame. Unique per field and implies position on the field: - // Single camera field: 0 = global field - // Double camera field: 0 = -x side, 1 = +x side - // Quadruple cam field: 0 = -x and -y, 1 = -x and +y, 2 = +x and -y, 3 = +x and +y - // Configuration for more cams depending on field shape. - optional uint32 camera_id = 2 [default=0]; - - // Database of SSL game controller team names and robot heights in mm. - // If the team broadcasted by the game controller has no entry, the average robot height is used. - map robot_heights = 3; - - optional SSL_VPConfigCamera camera = 4; - optional SSL_VPConfigGeometry geometry = 5; - optional SSL_VPConfigThresholds thresholds = 6; - optional SSL_VPConfigColor color = 7; - optional SSL_VPConfigNetwork network = 8; - optional SSL_VPConfigStream stream = 9; - - // Wait for geometry prior to processing frames - optional bool wait_for_geometry = 10 [default=false]; -} diff --git a/pyproject.toml b/pyproject.toml index 4a99690..f52c3e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,5 +22,9 @@ files = ["wrapper_backend"] exclude = ["wrapper_backend/proto/"] mypy_path = "wrapper_backend" +[[tool.mypy.overrides]] +module = "python_bindings" +ignore_missing_imports = true + [tool.ruff] src = ["wrapper_backend"] diff --git a/python/binary.py b/python/binary.py index 0020bef..5d05994 100644 --- a/python/binary.py +++ b/python/binary.py @@ -21,7 +21,7 @@ from dataset import Dataset from visionsocket import VisionRecorder # Importing visionsocket generates protobuf files -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket def parser_binary(parser: argparse.ArgumentParser, default='bin/vision') -> argparse.ArgumentParser: diff --git a/python/dataset.py b/python/dataset.py index eb9d6db..67ce46d 100644 --- a/python/dataset.py +++ b/python/dataset.py @@ -24,7 +24,7 @@ import yaml from geom_publisher import load_geometry, yaml_load -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket def parser_test_data(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: diff --git a/python/geom_publisher.py b/python/geom_publisher.py index 0936d4f..6d518ef 100755 --- a/python/geom_publisher.py +++ b/python/geom_publisher.py @@ -22,8 +22,8 @@ from google.protobuf.json_format import ParseDict from visionsocket import parser_vision_network, VisionSocket # Importing visionsocket generates protobuf files -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket, SSL_SOURCE_VISION_PROCESSOR -from proto.ssl_vision_geometry_pb2 import SSL_FieldShapeType, SSL_GeometryData +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket, SSL_SOURCE_VISION_PROCESSOR +from proto.vision.ssl_vision_geometry_pb2 import SSL_FieldShapeType, SSL_GeometryData def yaml_load(path: Path, default = None): diff --git a/python/overlap_benchmark.py b/python/overlap_benchmark.py index 5309147..06b7a00 100755 --- a/python/overlap_benchmark.py +++ b/python/overlap_benchmark.py @@ -25,7 +25,7 @@ from binary import parser_binary, run_binary from dataset import parser_test_data, iterate_field, Dataset from geom_publisher import load_geometry -from proto.ssl_vision_detection_pb2 import SSL_DetectionRobot, SSL_DetectionBall +from proto.vision.ssl_vision_detection_pb2 import SSL_DetectionRobot, SSL_DetectionBall from blob_benchmark import AvgValue from visionsocket import parser_vision_network, VisionRecorder diff --git a/python/replay.py b/python/replay.py index fe706bf..4dd9801 100755 --- a/python/replay.py +++ b/python/replay.py @@ -20,8 +20,8 @@ from pathlib import Path from google.protobuf.json_format import ParseDict -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket -from proto.ssl_vision_detection_pb2 import SSL_DetectionFrame +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket +from proto.vision.ssl_vision_detection_pb2 import SSL_DetectionFrame from visionsocket import parser_vision_network, VisionSocket from geom_publisher import load_geometry diff --git a/python/visionsocket.py b/python/visionsocket.py index 35da19a..5c462a1 100644 --- a/python/visionsocket.py +++ b/python/visionsocket.py @@ -17,30 +17,28 @@ import pathlib import socket import struct +import sys import threading from google.protobuf.json_format import MessageToDict -if not os.path.exists('python/proto/ssl_vision_wrapper_pb2.py'): +_REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent +_PROTO_DEFS = _REPO_ROOT / 'proto' +_PROTO_OUT = _REPO_ROOT / 'python' / 'proto' / 'vision' + +sys.path.insert(0, str(_PROTO_DEFS / 'python')) +from python_bindings import generate_python_bindings + +if not os.path.exists(_PROTO_OUT / 'ssl_vision_wrapper_pb2.py'): print("Compiling Protobuf files...") - import subprocess - try: - subprocess.run([ - 'protoc', - '--python_out=python', '--pyi_out=python', - *[str(path) for path in pathlib.Path().rglob('proto/*.proto')] - ], check=True) - except subprocess.CalledProcessError: - # Ubuntu 22.04 protoc can't do pyi - subprocess.run([ - 'protoc', - '--python_out=python', - *[str(path) for path in pathlib.Path().rglob('proto/*.proto')] - ], check=True) - - - -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket + # Nests the bindings under `proto` and rewrites the generated cross-imports + # to match; see proto/python/python_bindings.py. + generate_python_bindings( + out_dir=_REPO_ROOT / 'python', package='proto', includes=['vision', 'gamecontroller'] + ) + + +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket def parser_vision_network(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: diff --git a/setup.sh b/setup.sh index a0375dd..74c941c 100755 --- a/setup.sh +++ b/setup.sh @@ -84,6 +84,8 @@ fi # Compile vision_processor SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) cd "$SCRIPT_DIR" +echo -e "\e[92m » Updating protocol definitions submodule\e[39m" +git submodule update --init --recursive cmake -B build . make -j -C build vision_processor diff --git a/src/CameraModel.cpp b/src/CameraModel.cpp index 3502394..c4061cc 100644 --- a/src/CameraModel.cpp +++ b/src/CameraModel.cpp @@ -177,7 +177,7 @@ void CameraModel::updateEuler(const Eigen::Vector3f &euler) { } Eigen::Vector3f CameraModel::getEuler() { - return f2iOrientation.toRotationMatrix().canonicalEulerAngles(0, 1, 2); + return f2iOrientation.toRotationMatrix().eulerAngles(0, 1, 2); } diff --git a/src/CameraModel.h b/src/CameraModel.h index 8ebaff5..57e0c05 100644 --- a/src/CameraModel.h +++ b/src/CameraModel.h @@ -16,7 +16,7 @@ #pragma once #include -#include "proto/ssl_vision_geometry.pb.h" +#include "proto/vision/ssl_vision_geometry.pb.h" float goalBoundaryWidth(const SSL_GeometryFieldSize& field); diff --git a/src/GroundTruth.h b/src/GroundTruth.h index 5a8618e..27385e8 100644 --- a/src/GroundTruth.h +++ b/src/GroundTruth.h @@ -18,7 +18,7 @@ #include #include -#include "proto/ssl_vision_detection.pb.h" +#include "proto/vision/ssl_vision_detection.pb.h" std::vector parseGroundTruth(const std::string& source); diff --git a/src/Perspective.cpp b/src/Perspective.cpp index ec98f0d..88fc33f 100644 --- a/src/Perspective.cpp +++ b/src/Perspective.cpp @@ -15,7 +15,7 @@ */ #include "Perspective.h" #include "pattern.h" -#include "proto/ssl_vision_wrapper.pb.h" +#include "proto/vision/ssl_vision_wrapper.pb.h" #include "log.h" #include diff --git a/src/Perspective.h b/src/Perspective.h index 574e4c7..5cab4a3 100644 --- a/src/Perspective.h +++ b/src/Perspective.h @@ -15,7 +15,7 @@ */ #pragma once -#include "proto/ssl_vision_geometry.pb.h" +#include "proto/vision/ssl_vision_geometry.pb.h" #include "udpsocket.h" #include "CameraModel.h" diff --git a/src/calib/GeomModel.cpp b/src/calib/GeomModel.cpp index ba00bba..f6410b2 100644 --- a/src/calib/GeomModel.cpp +++ b/src/calib/GeomModel.cpp @@ -18,7 +18,7 @@ #include "CalibDiagnostic.h" #include "Distortion.h" #include "LineDetection.h" -#include "proto/ssl_vision_wrapper.pb.h" +#include "proto/vision/ssl_vision_wrapper.pb.h" #include "log.h" #include diff --git a/src/geometry_benchmark.cpp b/src/geometry_benchmark.cpp index 0f121b0..e2e3e55 100644 --- a/src/geometry_benchmark.cpp +++ b/src/geometry_benchmark.cpp @@ -17,7 +17,7 @@ #include #include "Resources.h" #include "GroundTruth.h" -#include "proto/ssl_vision_wrapper.pb.h" +#include "proto/vision/ssl_vision_wrapper.pb.h" #include "calib/LineDetection.h" #include "calib/GeomModel.h" diff --git a/src/main.cpp b/src/main.cpp index 8770ee0..7a0baa5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,8 +19,8 @@ #include #include "CameraModel.h" -#include "proto/ssl_vision_geometry.pb.h" -#include "proto/ssl_vision_wrapper.pb.h" +#include "proto/vision/ssl_vision_geometry.pb.h" +#include "proto/vision/ssl_vision_wrapper.pb.h" #include "Resources.h" #include "calib/GeomModel.h" #include "pattern.h" diff --git a/src/udpsocket.cpp b/src/udpsocket.cpp index b239248..990e5d6 100644 --- a/src/udpsocket.cpp +++ b/src/udpsocket.cpp @@ -14,8 +14,8 @@ limitations under the License. */ #include "udpsocket.h" -#include "proto/ssl_vision_wrapper.pb.h" -#include "proto/ssl_gc_referee_message.pb.h" +#include "proto/vision/ssl_vision_wrapper.pb.h" +#include "proto/gamecontroller/ssl_gc_referee_message.pb.h" #include "driver/cameradriver.h" #include diff --git a/src/udpsocket.h b/src/udpsocket.h index b375407..ee8b056 100644 --- a/src/udpsocket.h +++ b/src/udpsocket.h @@ -19,8 +19,8 @@ #include #include #include -#include "proto/ssl_vision_geometry.pb.h" -#include "proto/ssl_vision_detection.pb.h" +#include "proto/vision/ssl_vision_geometry.pb.h" +#include "proto/vision/ssl_vision_detection.pb.h" #ifdef _WIN32 #include // before Windows.h, else Winsock 1 conflict diff --git a/wrapper_backend/CLAUDE.md b/wrapper_backend/CLAUDE.md index c31e88b..cc36d53 100644 --- a/wrapper_backend/CLAUDE.md +++ b/wrapper_backend/CLAUDE.md @@ -29,7 +29,7 @@ Topics: `geometry.in`, `detection.in` (inbound demuxed), `wrapper_packet.out` (o - `ParseDict` runs strict (no `ignore_unknown_fields`). A typo in `geometry.yml` raises at startup. Don't add forgiveness. - `optional_field_lines:` controls the SSL markings that may be absent on lab/exhibition carpets: `goal2goal` (CenterLine), `halfway` (HalfwayLine), `centercircle` (CenterCircle arc), `penalty` (the six penalty-area stretches). Touchlines and goal lines are always emitted. The block and all four keys are required — `load_geometry` pops the block before `ParseDict` so strict parse still rejects typos elsewhere, and missing keys raise `KeyError` rather than silently defaulting. - Two `# type: ignore[assignment]` on `SSL_FieldShapeType.Value(...)` calls are unavoidable: `types-protobuf` types `Value()` as `int` while proto enum fields are typed as the enum. -- Generated proto bindings are NOT committed. `wrapper_backend/__init__.py` runs `protoc` on first import if `wrapper_backend/proto/ssl_vision_wrapper_pb2.py` is missing, then prepends `wrapper_backend/` to `sys.path` so `from proto.* import ...` resolves to `wrapper_backend/proto/`. mypy uses `mypy_path = "wrapper_backend"` and excludes `wrapper_backend/proto/` to mirror this. +- Protocol definitions live in the `proto/` git submodule (`RoboCup-SSL/ssl-protocol-defs`, `main`); the `.proto` sources are under `proto/proto/vision/`. Generated proto bindings are NOT committed. `wrapper_backend/__init__.py` calls the submodule's shared generator `proto/python/python_bindings.py` on first import if `wrapper_backend/proto/vision/ssl_vision_wrapper_pb2.py` is missing; that helper nests the bindings under `proto` and rewrites protoc's generated cross-imports (`from vision import ...` -> `from proto.vision import ...`), which is what makes them importable. It then prepends `wrapper_backend/` to `sys.path` so `from proto.vision.* import ...` resolves to `wrapper_backend/proto/vision/`. mypy uses `mypy_path = "wrapper_backend"` and excludes `wrapper_backend/proto/` to mirror this. - `python/` scripts (`geom_publisher.py`, `cam_viewer.py`, benchmarks) are NOT covered by the wrapper's tooling. They keep running on system Python; never modify them as part of wrapper work unless explicitly asked. - Pre-commit hooks are scoped to `^wrapper_backend/` for ruff. Don't widen the scope without reason — would reformat all the legacy `python/` files. diff --git a/wrapper_backend/__init__.py b/wrapper_backend/__init__.py index f3b230b..b669cde 100644 --- a/wrapper_backend/__init__.py +++ b/wrapper_backend/__init__.py @@ -1,41 +1,39 @@ from __future__ import annotations import pathlib -import subprocess import sys _BACKEND_DIR = pathlib.Path(__file__).resolve().parent _REPO_ROOT = _BACKEND_DIR.parent -_PROTO_SRC = _REPO_ROOT / "proto" -_PROTO_OUT = _BACKEND_DIR / "proto" +_PROTO_DEFS = _REPO_ROOT / "proto" +_PROTO_SRC = _PROTO_DEFS / "proto" / "vision" +_PROTO_OUT = _BACKEND_DIR / "proto" / "vision" - -def _generate_proto_bindings() -> None: - sources = sorted(_PROTO_SRC.glob("*.proto")) - if not sources: - raise RuntimeError(f"no .proto files found in {_PROTO_SRC}") - print("Compiling Protobuf files...", file=sys.stderr) - cmd_base = ["protoc", f"--proto_path={_REPO_ROOT}", f"--python_out={_BACKEND_DIR}"] - args = [str(p.relative_to(_REPO_ROOT)) for p in sources] - try: - subprocess.run([*cmd_base, f"--pyi_out={_BACKEND_DIR}", *args], check=True) - except subprocess.CalledProcessError: - # Older protoc (e.g. Ubuntu 22.04) doesn't support --pyi_out. - subprocess.run([*cmd_base, *args], check=True) +sys.path.insert(0, str(_PROTO_DEFS / "python")) +from python_bindings import generate_python_bindings # noqa: E402 def _bindings_are_stale() -> bool: outputs = list(_PROTO_OUT.glob("*_pb2.py")) if not outputs: return True + sources = list(_PROTO_SRC.glob("*.proto")) + if not sources: + return True oldest_out = min(o.stat().st_mtime for o in outputs) - newest_src = max(s.stat().st_mtime for s in _PROTO_SRC.glob("*.proto")) + newest_src = max(s.stat().st_mtime for s in sources) return newest_src > oldest_out if _bindings_are_stale(): - _generate_proto_bindings() - -# Generated _pb2.py files import siblings as `from proto import X_pb2`, -# so the directory holding them must be on sys.path as `proto`. + print("Compiling Protobuf files...", file=sys.stderr) + # The generator nests everything under `proto` and rewrites the generated + # cross-imports to match; see proto/python/python_bindings.py for why bare + # `--python_out` is not enough. + generate_python_bindings( + out_dir=_BACKEND_DIR, package="proto", includes=["vision", "gamecontroller"] + ) + +# The generated package is `wrapper_backend/proto/`, so `wrapper_backend/` must +# be on sys.path for `from proto.vision.* import ...` to resolve. sys.path.insert(0, str(_BACKEND_DIR)) diff --git a/wrapper_backend/geometry.py b/wrapper_backend/geometry.py index 359ef7f..ec2e023 100644 --- a/wrapper_backend/geometry.py +++ b/wrapper_backend/geometry.py @@ -16,11 +16,11 @@ import yaml from google.protobuf.json_format import ParseDict -from proto.ssl_vision_geometry_pb2 import ( +from proto.vision.ssl_vision_geometry_pb2 import ( SSL_FieldShapeType, SSL_GeometryData, ) -from proto.ssl_vision_wrapper_pb2 import ( +from proto.vision.ssl_vision_wrapper_pb2 import ( SSL_SOURCE_VISION_PROCESSOR, SSL_WrapperPacket, ) diff --git a/wrapper_backend/multicast.py b/wrapper_backend/multicast.py index 09e13bd..0c306d1 100644 --- a/wrapper_backend/multicast.py +++ b/wrapper_backend/multicast.py @@ -13,7 +13,7 @@ from google.protobuf.message import DecodeError -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket from wrapper_backend.bus import Bus log = logging.getLogger("wrapper_backend.multicast") diff --git a/wrapper_backend/websocket.py b/wrapper_backend/websocket.py index 02bea09..64e04da 100644 --- a/wrapper_backend/websocket.py +++ b/wrapper_backend/websocket.py @@ -25,7 +25,7 @@ from aiohttp import WSMsgType, web from google.protobuf.json_format import MessageToDict -from proto.ssl_vision_wrapper_pb2 import SSL_WrapperPacket +from proto.vision.ssl_vision_wrapper_pb2 import SSL_WrapperPacket from wrapper_backend.bus import Bus log = logging.getLogger("wrapper_backend.websocket")