Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0ee5652
the dwa is cooked
Jawad12256 Nov 19, 2025
bc68db2
Merge branch 'main' into dwa-new
wowthecoder Nov 19, 2025
322a8ed
Fast Path PLanning
Nov 24, 2025
417e097
fixing
Nov 24, 2025
6931968
cleaning up
Nov 24, 2025
0f4327b
Merge branch 'main' into dwa-new
wowthecoder Nov 24, 2025
ad2bd95
test: write simple straight line test
wowthecoder Nov 25, 2025
855fb39
test: uncomment straight line test with obstacles
wowthecoder Nov 25, 2025
7be09fa
test: moving robots obstacle course, also added separate motion contr…
wowthecoder Nov 25, 2025
c2a697a
test: implement unit test of 12 robots dashing straight at each other
wowthecoder Nov 26, 2025
c75f02a
test: implement test with 6 robots moving randomly in a half court
wowthecoder Nov 26, 2025
52c55a5
fixing a bug
Nov 26, 2025
7637f5c
increased safety
valentinbruehl Nov 26, 2025
37eeb8e
revert dwa config and planner
wowthecoder Nov 26, 2025
d0b3a6d
rename test file
wowthecoder Nov 26, 2025
7a47ec2
Merge branch 'main' into motion-planning-tests
wowthecoder Nov 26, 2025
388c433
fixing slowing down at sugoals + linting
valentinbruehl Nov 26, 2025
b0a337f
recursion error fix
valentinbruehl Nov 26, 2025
459052e
fix: index out of order bug
valentinbruehl Nov 26, 2025
d1b45b9
updates to fastpathplanning
Dec 1, 2025
495c617
updates
Dec 1, 2025
c9efe28
updating
Dec 2, 2025
71e0ceb
to add new tests
Jan 10, 2026
77baab1
To test new cases
Jan 10, 2026
ee0784b
adding test
Jan 10, 2026
0432c4d
random movement test succeeds with fpp (5 robots)
valentinbruehl Jan 10, 2026
e1138f2
diagonal robot movement test pass with fpp
valentinbruehl Jan 10, 2026
a38b2c7
make number of robots in random_movement_test adjustable
valentinbruehl Jan 10, 2026
9ae1421
use fpp in all tests
valentinbruehl Jan 10, 2026
3912c34
reduce to 3 robots in random_movement_test
valentinbruehl Jan 10, 2026
02329eb
compute distance between two line segments
valentinbruehl Jan 14, 2026
bea2948
g
Jan 14, 2026
826285f
Lined Based obstacles and Config Files Updates
Jan 14, 2026
1311537
Merge branch 'main' into feature/fastpathplanning
valentinbruehl Jan 22, 2026
101110e
fixed issues concerning new functions in math_utils
valentinbruehl Jan 22, 2026
e4c7eaf
updating
Jan 24, 2026
286544e
Removing extra libraries
Jan 24, 2026
8d8041e
Update utama_core/motion_planning/src/fastpathplanning/planner.py
Vortex4627 Jan 24, 2026
ec19004
Adding snake eye convention and removing unwanted code
Jan 24, 2026
ef8bad9
Removed target from collide function
Jan 24, 2026
95785ff
Updating checksegment function
Jan 25, 2026
fb7d793
Including Joel's suggestions
Jan 25, 2026
9121ef7
snake_case and parameter error fix
valentinbruehl Feb 22, 2026
ba83a96
Update utama_core/motion_planning/src/controllers/fastpathplanning.py
Vortex4627 Feb 22, 2026
1da5084
Fix/goalkeeping (#92)
maimaicircle Jan 24, 2026
4ce37fa
chore(release): bump version to v1.5.20
utama-release-manager[bot] Jan 24, 2026
95a3176
Real/extend kick transmission (#93)
energy-in-joles Jan 25, 2026
bde48e7
chore(release): bump version to v1.5.21
utama-release-manager[bot] Jan 25, 2026
e2551f4
Add DeepWiki badge to README
fred-huang122 Feb 3, 2026
ca24507
Feature/rsim noise (#89)
szeyoong-low Feb 10, 2026
cf8cd3b
chore(release): bump version to v1.6.0
utama-release-manager[bot] Feb 10, 2026
7c9a91b
Fix/defence parameter (#103)
NingchuanIC Feb 18, 2026
0431a0c
Feature/kalman (#101)
szeyoong-low Feb 21, 2026
7a84ae8
chore(release): bump version to v1.7.0
utama-release-manager[bot] Feb 21, 2026
d789692
FIR filters (#81)
szeyoong-low Feb 21, 2026
1225fee
chore(release): bump version to v1.8.0
utama-release-manager[bot] Feb 21, 2026
0f6bbf8
reverting mistake
valentinbruehl Feb 22, 2026
df5fc67
planner.py naming convention fixes
valentinbruehl Feb 22, 2026
7aa1fd9
addressed pull request comments
valentinbruehl Feb 22, 2026
25680e3
Updating
Feb 27, 2026
8a158d9
Updating
Feb 27, 2026
baf96f4
Updating
Feb 27, 2026
4685fe9
Updating
Feb 27, 2026
851d748
Updating
Feb 27, 2026
9bd40b9
Added Field Bounds to FastPathPlannig
Mar 5, 2026
5dbf0b7
Added field bounds as an obstacle
Mar 6, 2026
286cfac
Added boundaries are as obstacle, updated how the point of closesnt o…
Mar 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ target/

# Jupyter Notebook
.ipynb_checkpoints
.jupyter/

# IPython
profile_default/
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/First-Order-RoboCup-SSL/Utama-Core)
# Utama Core
First Order Robotics core software stack for [RoboCup SSL](https://ssl.robocup.org/), an international league where teams build autonomous robotic teams to play football competitively.

Expand Down Expand Up @@ -39,16 +40,17 @@ First Order Robotics core software stack for [RoboCup SSL](https://ssl.robocup.o
### Folder Structure

1. `strategy`: higher level control from above roles to plays and tactics in decision-tree like abstraction
2. `skills`: lowest level of control for individual robots
3. `motion_planning`: control algorithms for movement and path planning
4. `team_controller`: interfacing with vision (including processing) and robots
5. `run`: The logic for main running loop, including refiners and predictors
6. `global_utils`: store utility functions that can be shared across all folders
7. `entities`: store classes for building field, robot, data entities etc.
8. `rsoccer_simulator`: Lightweight rSoccer simulator for testing
9. `replay`: replay system for storing played games in a .pkl file that can be reconstructed in rsoccer sim
10. `tests`: include all unit tests here
11. `config`: configs for the robots (defaults, settings, roles/tactics enums, etc.)
1. `skills`: lowest level of control for individual robots
1. `motion_planning`: control algorithms for movement and path planning
1. `team_controller`: interfacing with vision (including processing) and robots
1. `run`: The logic for main running loop
1. `data_processing`: processors of vision, robot_info and referee raw data
1. `global_utils`: store utility functions that can be shared across all folders
1. `entities`: store classes for building field, robot, data entities etc.
1. `rsoccer_simulator`: Lightweight rSoccer simulator for testing
1. `replay`: replay system for storing played games in a .pkl file that can be reconstructed in rsoccer sim
1. `tests`: include all unit tests here
1. `config`: configs for the robots (defaults, settings, roles/tactics enums, etc.)

### Code Writing

Expand Down
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from utama_core.entities.game.field import FieldBounds
from utama_core.replay import ReplayWriterConfig
from utama_core.rsoccer_simulator.src.Utils.gaussian_noise import RsimGaussianNoise
from utama_core.run import StrategyRunner
from utama_core.strategy.examples import (
DefenceStrategy,
GoToBallExampleStrategy,
PointCycleStrategy,
RobotPlacementStrategy,
StartupStrategy,
TwoRobotPlacementStrategy,
Expand All @@ -16,7 +18,7 @@ def main():
custom_bounds = FieldBounds(top_left=(2.25, 1.5), bottom_right=(4.5, -1.5))

runner = StrategyRunner(
strategy=TwoRobotPlacementStrategy(first_robot_id=0, second_robot_id=1, field_bounds=custom_bounds),
strategy=PointCycleStrategy(n_robots=2, field_bounds=custom_bounds, endpoint_tolerance=0.1, seed=42),
my_team_is_yellow=True,
my_team_is_right=True,
mode="rsim",
Expand Down
2,528 changes: 1,328 additions & 1,200 deletions pixi.lock

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "Utama-Core"
channels = ["https://prefix.dev/conda-forge"]
platforms = ["linux-64", "osx-arm64"]
preview = ["pixi-build"]
version = "1.5.19"
version = "1.8.0"

[pypi-dependencies]
lenses = ">=1.2.0"
Expand All @@ -22,11 +22,13 @@ pre-commit = ">=3.8.0"
hatch = ">=1.14.1,<2"
graphviz = ">=13.1.2,<14"
snakeviz = ">=2.2.2,<3"
scipy = ">=1.16.3,<2"
pandas = "==3.0.1"
rich = ">=14.2.0,<15"

[package]
name = "Utama-Core"
version = "1.5.19"
version = "1.8.0"

[package.build]
backend = { name = "pixi-build-python", version = "*" }
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ authors = [
{ name = "Hamish Starling", email = "hamish.starling22@imperial.ac.uk" },
{ name = "Luke Moran", email = "luke.moran22@imperial.ac.uk" },
{ name = "Sadig Sadikhzada", email = "sadig.sadikhzada23@imperial.ac.uk" },
{ name = "Li Han", email = "han.li124@imperial.ac.uk" }
{ name = "Li Han", email = "han.li124@imperial.ac.uk" },
{ name = "Valentin Bruhl", email = "valentin.bruhl24@imperial.ac.uk" },
{ name = "Sarthak Agarwal", email = "sarthak.agarwal25@imperial.ac.uk" },
{ name = "Low Sze Yoong", email = "szeyoong.low25@imperial.ac.uk" },
{ name = "Jiaming Liu", email = "jiaming.liu25@imperial.ac.uk" },
]
requires-python = ">=3.11"

Expand Down
Binary file added utama_core/.DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion utama_core/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.5.19"
__version__ = "1.8.0"
3 changes: 3 additions & 0 deletions utama_core/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
BAUD_RATE = 115200
PORT = "/dev/ttyUSB0"
TIMEOUT = 0.1
KICKER_COOLDOWN_TIME = 10 # in seconds to prevent kicker from being actuated too frequently
KICKER_COOLDOWN_TIMESTEPS = int(KICKER_COOLDOWN_TIME * CONTROL_FREQUENCY) # in timesteps
KICKER_PERSIST_TIMESTEPS = 10 # in timesteps to persist the kick command

MAX_GAME_HISTORY = 20 # number of previous game states to keep in Game

Expand Down
1 change: 1 addition & 0 deletions utama_core/data_processing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions utama_core/data_processing/receivers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from utama_core.data_processing.receivers.referee_receiver import RefereeMessageReceiver
from utama_core.data_processing.receivers.vision_receiver import VisionReceiver
4 changes: 4 additions & 0 deletions utama_core/data_processing/refiners/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from utama_core.data_processing.refiners.position import PositionRefiner
from utama_core.data_processing.refiners.referee import RefereeRefiner
from utama_core.data_processing.refiners.robot_info import RobotInfoRefiner
from utama_core.data_processing.refiners.velocity import VelocityRefiner
119 changes: 119 additions & 0 deletions utama_core/data_processing/refiners/filters/fir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from collections import deque
from typing import Union

import numpy as np
from scipy.signal import firwin

from utama_core.entities.data.vision import VisionRobotData


class FIR_filter:
"""
Finite Impulse Response (FIR) filter for 2D position and orientation.
This is essentially a weighted average of the past n data points.

More about the methodology:
https://youtube.com/playlist?list=PLbqhA-NKGP6Afr_KbPUuy_yIBpPR4jzWo&si=7l2BVnsN_jSKq2JL.

Args:
fs (float): Sampling rate (Hz). Defaults to 60.0.
taps (array-like or None): FIR taps. If None, a series of taps of length `window_len` is created.
window_len (int): Number of taps to be created if `taps` is None. Default 20.
cutoff (float): Cutoff frequency of the filter.
"""

def __init__(
self,
fs: float = 60.0,
taps: Union[np.array, None] = None,
window_len: int = 5,
cutoff: Union[float, None] = None,
):
self._fs = float(fs)
self._N = window_len
self._nyquist = 0.4 * self._fs

if cutoff and cutoff < self._nyquist:
self._cutoff = cutoff
else:
# Sets cutoff frequency according to the maximum acceleration and
# velocity of robots, below the limits dictated by Nyquist's theorem.

a_max = 50
v_max = 5
fc = a_max / (2 * np.pi * v_max)

self._cutoff = min(self._nyquist, fc)

if taps is None:
assert window_len >= 1, "window_len must be >= 1"
self._taps = firwin(window_len, self._cutoff, fs=fs)

else:
t = np.asarray(taps, dtype=float).ravel()
assert t.size >= 1, "taps must have at least 1 element"
# Normalize taps to sum to 1 for unity DC gain
self._taps = t / np.sum(t)
self._N = self._taps.size

self._buf_x = deque(maxlen=self._N)
self._buf_y = deque(maxlen=self._N)
self._buf_th = deque(maxlen=self._N)

def step(self, data: tuple[float]):
"""
A single iteration of the filter

Args:
data (tuple[float]): The new vision data received (x and y coordinates
in metres, orientation in radians).

Returns:
tuple[float]: The filtered data
"""

x, y, th = map(float, data)
# theta = normalise_heading(theta)

self._buf_x.append(x)
self._buf_y.append(y)
self._buf_th.append(th)

# Use only the available samples during warm-up
k = len(self._buf_x) # same as len(buf_y) and len(buf_th)
taps_eff = self._taps[-k:]
taps_eff = taps_eff / np.sum(taps_eff) # renormalize

# Position FIR
x_arr = np.asarray(self._buf_x, dtype=float)
y_arr = np.asarray(self._buf_y, dtype=float)
x_f = float(np.dot(taps_eff, x_arr))
y_f = float(np.dot(taps_eff, y_arr))

# Orientation FIR via circular averaging - currently disabled
# th_arr = np.asarray(self._buf_th, dtype=float)
# s = np.dot(taps_eff, np.sin(th_arr))
# c = np.dot(taps_eff, np.cos(th_arr))
# th_f = float(np.arctan2(s, c)) # already wrapped to (-pi, pi]

return x_f, y_f, th

@staticmethod
def filter_robot(filter, data: VisionRobotData) -> VisionRobotData:
"""
Externally callable function for Position Refiner to pass data
to be filtered.

Args:
filter (FIR_filter): The filter associated with a robot
data (VisionRobotData): The new vision data received (x and y
coordinates in metres, orientation in radians).

Returns:
VisionRobotData: The filtered vision data.
"""

# class VisionRobotData: id: int; x: float; y: float; orientation: float
(x_f, y_f, th_f) = filter.step((data.x, data.y, data.orientation))

return VisionRobotData(id=data.id, x=x_f, y=y_f, orientation=th_f)
Loading
Loading