Skip to content

Commit 26da6e0

Browse files
Merge pull request #4 from ICRS-RoboCup-SSL/use_queue
Use queue branch merge
2 parents 33c20d9 + ed3309d commit 26da6e0

File tree

11 files changed

+241
-213
lines changed

11 files changed

+241
-213
lines changed

entities/game/game.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
from typing import List
12
from entities.game.field import Field
2-
from entities.data.vision import FrameData
3-
3+
from entities.data.vision import FrameData, RobotData, BallData
44

55
class Game:
66
"""
@@ -38,3 +38,10 @@ def add_new_state(self, frame_data: FrameData) -> None:
3838
self._records.append(frame_data)
3939
else:
4040
raise ValueError("Invalid frame data.")
41+
42+
def get_robots_pos(self, is_yellow: bool) -> List[RobotData]:
43+
record = self._records[-1]
44+
return record.yellow_robots if is_yellow else record.blue_robots
45+
46+
def get_ball_pos(self) -> BallData:
47+
return self._records[-1].ball

main.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import threading
2+
import queue
23
from entities.game import Game
3-
from team_controller.src.data.referee_receiver import RefereeMessageReceiver
4+
5+
from team_controller.src.controllers.robot_startup_controller import StartUpController
46
from team_controller.src.data.vision_receiver import VisionDataReceiver
7+
from team_controller.src.data.message_enum import MessageType
8+
59

610

711
def data_update_listener(receiver: VisionDataReceiver):
@@ -11,9 +15,10 @@ def data_update_listener(receiver: VisionDataReceiver):
1115

1216
def main():
1317
game = Game()
14-
# Initialize the VisionDataReceiver
15-
receiver = VisionDataReceiver(debug=False)
16-
referee_receiver = RefereeMessageReceiver(debug=True)
18+
19+
message_queue = queue.SimpleQueue()
20+
receiver = VisionDataReceiver(message_queue, debug=False)
21+
decision_maker = StartUpController(game, debug=False)
1722

1823
# Start the data receiving in a separate thread
1924
data_thread = threading.Thread(target=data_update_listener, args=(receiver,))
@@ -26,26 +31,27 @@ def main():
2631

2732
try:
2833
while True:
29-
# Wait for the update event with a timeout (optional)
30-
if receiver.wait_for_update(timeout=0.1):
31-
# An update has occurred, so process the updated data
32-
frame_data = receiver.get_frame_data()
33-
game.add_new_state(frame_data)
34+
(message_type, message) = message_queue.get() # Infinite timeout for now
3435

36+
if message_type == MessageType.VISION:
37+
# message = FrameData(...)
38+
game.add_new_state(message)
3539
# access current state data
3640
# print(
3741
# game.current_state.yellow_robots[0].x,
3842
# game.current_state.yellow_robots[0].y,
3943
# )
4044

4145
# access game records from -x number of frames ago
42-
# print(game.records[-1].ts, game.records[-1].ball[0].x)
43-
else:
44-
print("No data update received within the timeout period.")
46+
print(game.records[-1].ts, game.records[-1].ball[0].x)
47+
48+
elif message_type == MessageType.REF:
49+
pass
50+
51+
decision_maker.make_decision()
4552

4653
except KeyboardInterrupt:
4754
print("Stopping main program.")
4855

49-
5056
if __name__ == "__main__":
5157
main()

rationale.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
- Running additional computations off main thread for some reason
2+
- Ability to prioritise / wait on only one queue vs waiting on all queues -> Claim is that you have to process both and load is constant so if you fall behind, you are falling behind consistently and we need to fix it separately because it won't work properly.
3+
4+
We claim can't wait on multiple successively i.e. ref then image then ref etc because packet drop or no ref message or lost connection etc - missing other data. Can't skip through.
5+
6+
7+
8+
Single thread - Doesn't support the "long running computation" - Would get less performance if we added it.
9+
Queues with no blocking - main spins in fast event loop
10+
11+
12+
1 big queue with blocking
13+
-> Advantage is that you get slightly better latency because the other threads are running and you can put that in another thread.
14+
-> Kind of depends if they want to do long running other computations.
15+
16+
17+
18+
19+
20+
21+
If the queue is empty then what does main need to do? Is it nothing?
22+
23+
24+
Problems with current approach:
25+
- Sequential Approach
26+
- Separation of concerns for the entities and last 5
27+
-> Enables receivers to have no concept of state which is good as they shouldn't be responsible for knowing what we want to track about the game
28+
- Threading complexity and potential race conditions
29+
- Another advantage is that the controller is only summoned when decision to be amde - no random sleep lengths
30+
31+
32+
-> "Entities Data Structure" -> Plan is just have main manage it after retrieving from the thread safe queue. Only 1 thread; no race conditions. Main give that data to whoever.
33+
34+
Priorities: If you wanted to you could but we think you don't need to because you need to process everything anyway
35+

team_controller/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ This folder contains all the files which perform the initial processing of data
5959
- `Ball`: A named tuple representing the ball's position with fields `x`, `y`, and `z`.
6060
- `Robot`: A named tuple representing a robot's position with fields `x`, `y`, and `orientation`.
6161
- **Methods**:
62-
- `get_robots_pos(is_yellow: bool) -> List[Robot]`: Retrieves the current position data for robots on the specified team. If `is_yellow` is `True`, it retrieves data for the yellow team; otherwise, it retrieves data for the blue team.
62+
- DOCTODO `get_robots_pos(is_yellow: bool) -> List[Robot]`: Retrieves the current position data for robots on the specified team. If `is_yellow` is `True`, it retrieves data for the yellow team; otherwise, it retrieves data for the blue team.
6363
- `get_ball_pos() -> Ball`: Retrieves the current position data for the ball.
6464
- `pull_game_data() -> None`: Continuously receives vision data packets and updates the internal data structures for the game state. This method runs indefinitely and should typically be started in a separate thread.
6565

team_controller/proto/ssl_gc_referee_message.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ message Referee {
210210
// * kickoff, penalty kick, force start
211211
// * ball placement
212212
optional int64 current_action_time_remaining = 15;
213-
213+
214214
// A message that can be displayed to the spectators, like a reason for a stoppage.
215215
optional string status_message = 20;
216216
}

team_controller/src/controllers/robot_startup_controller.py

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
from typing import Tuple, Optional, Dict, Union, List
55

6+
from entities.game.game import Game
67
from global_utils.math_utils import rotate_vector
78
from entities.data.command import RobotSimCommand
89
from entities.data.vision import RobotData, BallData
@@ -13,55 +14,46 @@
1314
PID_PARAMS,
1415
YELLOW_START,
1516
)
17+
from team_controller.src.generated_code.ssl_simulation_robot_control_pb2 import (
18+
RobotControl,
19+
)
1620

1721
# TODO: To be moved to a High-level Descision making repo
18-
1922
class StartUpController:
2023
def __init__(
2124
self,
22-
vision_receiver: VisionDataReceiver,
25+
game: Game,
2326
debug=False,
2427
):
25-
self.vision_receiver = vision_receiver
26-
self.sim_robot_controller = SimRobotController(is_team_yellow=True, debug=debug)
28+
self.game = game
29+
self.sim_robot_controller = SimRobotController(debug=debug)
2730

2831
# TODO: Tune PID parameters further when going from sim to real(it works for Grsim)
2932
# potentially have a set tunig parameters for each robot
3033
self.pid_oren = PID(0.0167, 8, -8, 5, 0, 0.03, num_robots=6)
3134
self.pid_trans = PID(0.0167, 1.5, -1.5, 5, 0, 0.02, num_robots=6)
3235

33-
self.lock = threading.Lock()
34-
3536
self.debug = debug
3637

37-
def startup(self):
38-
while True:
39-
start_time = time.time()
40-
41-
robots, balls = self._get_positions()
42-
43-
if robots and balls:
44-
for robot_id, robot_data in enumerate(robots):
45-
if robot_data is None:
46-
continue
47-
target_coords = YELLOW_START[robot_id]
48-
command = self._calculate_robot_velocities(
49-
robot_id, target_coords, robots, balls, face_ball=True
50-
)
51-
self.sim_robot_controller.add_robot_commands(command, robot_id)
52-
53-
self.sim_robot_controller.send_robot_commands()
54-
self.sim_robot_controller.robot_has_ball(robot_id=3)
55-
56-
time_to_sleep = max(0, 0.0167 - (time.time() - start_time))
57-
time.sleep(time_to_sleep)
58-
59-
def _get_positions(self) -> tuple:
60-
# Fetch the latest positions of robots and balls with thread locking.
61-
with self.lock:
62-
robots = self.vision_receiver.get_robots_pos(is_yellow=True)
63-
balls = self.vision_receiver.get_ball_pos()
64-
return robots, balls
38+
def make_decision(self):
39+
robots = self.game.get_robots_pos(is_yellow=True)
40+
balls = self.game.get_ball_pos()
41+
42+
if robots and balls:
43+
out_packet = RobotControl()
44+
for robot_id, robot_data in enumerate(robots):
45+
if robot_data is None:
46+
continue
47+
target_coords = YELLOW_START[robot_id]
48+
command = self._calculate_robot_velocities(
49+
robot_id, target_coords, robots, balls, face_ball=True
50+
)
51+
self.sim_robot_controller.add_robot_command(command)
52+
53+
if self.debug:
54+
print(out_packet)
55+
self.sim_robot_controller.send_robot_commands(team_is_yellow=True)
56+
self.sim_robot_controller.robot_has_ball(robot_id=3, team_is_yellow=True)
6557

6658
def _calculate_robot_velocities(
6759
self,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import queue
2+
import abc
3+
4+
5+
class BaseReceiver(abc.ABC):
6+
"""Interface for receivers. Every receiver should add messages to the passed-in message queue which
7+
will notify main of something to do."""
8+
9+
def __init__(self, message_queue:queue.SimpleQueue):
10+
self._message_queue = message_queue
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from enum import Enum
2+
3+
4+
class MessageType(Enum):
5+
"""Describes the type of message added to the global message queue by the receivers"""
6+
VISION = 1
7+
REF = 2

team_controller/src/data/referee_receiver.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ class RefereeMessageReceiver:
2222
port (int): The port for receiving referee data. Defaults to REFEREE_PORT.
2323
debug (bool): Whether to print debug information. Defaults to False.
2424
"""
25-
26-
def __init__(self, ip=MULTICAST_GROUP_REFEREE, port=REFEREE_PORT, debug=False):
25+
def __init__(self, ip=MULTICAST_GROUP_REFEREE, port=REFEREE_PORT, debug=False): # TODO: add message queue
2726
self.net = network_manager.NetworkManager(address=(ip, port), bind_socket=True)
2827
self.prev_command_counter = -1
2928
self.command_history = []

0 commit comments

Comments
 (0)