Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gamecontroller can Bind to a Different Port Based on Command Line Argument #3207

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bacfacf
chnage port depending on argument
Mr-Anyone May 20, 2024
d620120
added docs
Mr-Anyone May 20, 2024
1557448
remove stuff
Mr-Anyone May 20, 2024
5478f15
updated entry points
Mr-Anyone May 20, 2024
8d394cd
fixed stuff
Mr-Anyone May 21, 2024
0b5d2a6
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] May 21, 2024
5d5e130
copied code from nima
Mr-Anyone May 22, 2024
53b3120
added some new port to fix
Mr-Anyone May 22, 2024
c052a97
change bind address and stuff like that
Mr-Anyone May 23, 2024
ace67d6
added new stuff to robot communication
Mr-Anyone May 23, 2024
730753a
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] May 23, 2024
9b8d59d
added new stuff for robot communication
Mr-Anyone May 23, 2024
9453411
Merge branch 'gamecontroller_port' of github.com:Mr-Anyone/Thunderbot…
Mr-Anyone May 23, 2024
ebcc8d6
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] May 23, 2024
6ad2b47
merged arune's changes
Mr-Anyone May 31, 2024
e730860
added new feature that arune requested
Mr-Anyone Jun 11, 2024
60dad81
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 11, 2024
b44cdcf
added requested feature
Mr-Anyone Jun 26, 2024
0c984eb
added comments and remove necessary print statements
Mr-Anyone Jun 26, 2024
749fb3f
remove code that are non necessary
Mr-Anyone Jun 26, 2024
f3a42e7
removed one line of legacy code
Mr-Anyone Jun 26, 2024
bed3109
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 26, 2024
51f42e2
Merge branch 'gamecontroller_port' of github.com:Mr-Anyone/Thunderbot…
Mr-Anyone Jun 26, 2024
1e8fcba
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 26, 2024
3218a32
refactored function
Mr-Anyone Jun 28, 2024
b147096
Merge branch 'gamecontroller_port' of github.com:Mr-Anyone/Thunderbot…
Mr-Anyone Jun 28, 2024
cf668bf
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 28, 2024
036da38
added comments to make things make more sense
Mr-Anyone Jul 2, 2024
4f1fbe5
Merge branch 'gamecontroller_port' of github.com:Mr-Anyone/Thunderbot…
Mr-Anyone Jul 2, 2024
9a3ef5f
address some comments
Mr-Anyone Jul 3, 2024
82e6f87
resolved some comments
Mr-Anyone Jul 5, 2024
c51c412
made new changes
Mr-Anyone Jul 5, 2024
e616430
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jul 5, 2024
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
142 changes: 72 additions & 70 deletions src/software/field_tests/field_test_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(
:param test_name: The name of the test to run
:param blue_full_system_proto_unix_io: The blue full system proto unix io to use
:param yellow_full_system_proto_unix_io: The yellow full system proto unix io to use
:param gamecontroller: The gamecontroller context managed instance
:param gamecontroller: The gamecontroller context managed instance
:param publish_validation_protos: whether to publish validation protos
:param: is_yellow_friendly: if yellow is the friendly team
"""
Expand Down Expand Up @@ -373,84 +373,86 @@ def field_test_runner():
debug_full_system=debug_full_sys,
friendly_colour_yellow=args.run_yellow,
should_restart_on_crash=False,
) as friendly_fs, RobotCommunication(
) as friendly_fs, Gamecontroller(
# we would be using conventional port if and only if we are playing in robocup.
supress_logs=(not args.show_gamecontroller_logs),
use_conventional_port=False,
) as gamecontroller, RobotCommunication(
current_proto_unix_io=friendly_proto_unix_io,
multicast_channel=getRobotMulticastChannel(args.channel),
interface=args.interface,
estop_mode=estop_mode,
estop_path=estop_path,
enable_radio=args.enable_radio,
referee_port=Gamecontroller.get_referee_port_static(gamecontroller),
) as rc_friendly:
with Gamecontroller(
supress_logs=(not args.show_gamecontroller_logs)
) as gamecontroller:
friendly_fs.setup_proto_unix_io(friendly_proto_unix_io)
rc_friendly.setup_for_fullsystem()

gamecontroller.setup_proto_unix_io(
blue_full_system_proto_unix_io, yellow_full_system_proto_unix_io,
)
# Inject the proto unix ios into thunderscope and start the test
tscope = Thunderscope(
configure_field_test_view(
simulator_proto_unix_io=simulator_proto_unix_io,
blue_full_system_proto_unix_io=blue_full_system_proto_unix_io,
yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io,
yellow_is_friendly=args.run_yellow,
),
layout_path=None,
)

# connect the keyboard estop toggle to the key event if needed
if estop_mode == EstopMode.KEYBOARD_ESTOP:
tscope.keyboard_estop_shortcut.activated.connect(
rc_friendly.toggle_keyboard_estop
)
# we call this method to enable estop automatically when a field test starts
rc_friendly.toggle_keyboard_estop()
logger.warning(
"\x1b[31;20m"
+ "Keyboard Estop Enabled, robots will start moving automatically when test starts!"
+ "\x1b[0m"
)
friendly_fs.setup_proto_unix_io(friendly_proto_unix_io)
rc_friendly.setup_for_fullsystem()

time.sleep(LAUNCH_DELAY_S)
runner = FieldTestRunner(
test_name=current_test,
gamecontroller.setup_proto_unix_io(
blue_full_system_proto_unix_io, yellow_full_system_proto_unix_io,
)
# Inject the proto unix ios into thunderscope and start the test
tscope = Thunderscope(
configure_field_test_view(
simulator_proto_unix_io=simulator_proto_unix_io,
blue_full_system_proto_unix_io=blue_full_system_proto_unix_io,
yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io,
gamecontroller=gamecontroller,
thunderscope=tscope,
is_yellow_friendly=args.run_yellow,
yellow_is_friendly=args.run_yellow,
),
layout_path=None,
)

# connect the keyboard estop toggle to the key event if needed
if estop_mode == EstopMode.KEYBOARD_ESTOP:
tscope.keyboard_estop_shortcut.activated.connect(
rc_friendly.toggle_keyboard_estop
)
# we call this method to enable estop automatically when a field test starts
rc_friendly.toggle_keyboard_estop()
logger.warning(
"\x1b[31;20m"
+ "Keyboard Estop Enabled, robots will start moving automatically when test starts!"
+ "\x1b[0m"
)

friendly_proto_unix_io.register_observer(World, runner.world_buffer)

# Setup proto loggers.
#
# NOTE: Its important we use the test runners time provider because
# test will run as fast as possible with a varying tick rate. The
# SimulatorTestRunner time provider is tied to the simulators
# t_capture coming out of the wrapper packet (rather than time.time).
with ProtoLogger(
f"{args.blue_full_system_runtime_dir}/logs/{current_test}",
time_provider=runner.time_provider,
) as blue_logger, ProtoLogger(
f"{args.yellow_full_system_runtime_dir}/logs/{current_test}",
time_provider=runner.time_provider,
) as yellow_logger:
blue_full_system_proto_unix_io.register_to_observe_everything(
blue_logger.buffer
)
yellow_full_system_proto_unix_io.register_to_observe_everything(
yellow_logger.buffer
)
yield runner
print(
f"\n\nTo replay this test for the blue team, go to the `src` folder and run \n./tbots.py run thunderscope --blue_log {blue_logger.log_folder}",
flush=True,
)
print(
f"\n\nTo replay this test for the yellow team, go to the `src` folder and run \n./tbots.py run thunderscope --yellow_log {yellow_logger.log_folder}",
flush=True,
)
time.sleep(LAUNCH_DELAY_S)
runner = FieldTestRunner(
test_name=current_test,
blue_full_system_proto_unix_io=blue_full_system_proto_unix_io,
yellow_full_system_proto_unix_io=yellow_full_system_proto_unix_io,
gamecontroller=gamecontroller,
thunderscope=tscope,
is_yellow_friendly=args.run_yellow,
)

friendly_proto_unix_io.register_observer(World, runner.world_buffer)

# Setup proto loggers.
#
# NOTE: Its important we use the test runners time provider because
# test will run as fast as possible with a varying tick rate. The
# SimulatorTestRunner time provider is tied to the simulators
# t_capture coming out of the wrapper packet (rather than time.time).
with ProtoLogger(
f"{args.blue_full_system_runtime_dir}/logs/{current_test}",
time_provider=runner.time_provider,
) as blue_logger, ProtoLogger(
f"{args.yellow_full_system_runtime_dir}/logs/{current_test}",
time_provider=runner.time_provider,
) as yellow_logger:
blue_full_system_proto_unix_io.register_to_observe_everything(
blue_logger.buffer
)
yellow_full_system_proto_unix_io.register_to_observe_everything(
yellow_logger.buffer
)
yield runner
print(
f"\n\nTo replay this test for the blue team, go to the `src` folder and run \n./tbots.py run thunderscope --blue_log {blue_logger.log_folder}",
flush=True,
)
print(
f"\n\nTo replay this test for the yellow team, go to the `src` folder and run \n./tbots.py run thunderscope --yellow_log {yellow_logger.log_folder}",
flush=True,
)
11 changes: 8 additions & 3 deletions src/software/networking/ssl_proto_communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ def __init__(self, port: int) -> None:

:param port the port to bind to
"""
# bind to all local interfaces, TCP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("", port))
try:
# bind to all local interfaces, TCP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("", port))
except ConnectionRefusedError:
raise ConnectionRefusedError(
f"SSL Socket connection refused on port {port}. Is binary already running in a separate process?"
)

def send(self, proto: protobuf_message.Message) -> None:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import random
import logging
import os
import socket
Expand All @@ -21,29 +22,60 @@


class Gamecontroller(object):
""" Gamecontroller Context Manager """
"""Gamecontroller Context Manager"""

CI_MODE_LAUNCH_DELAY_S = 0.3
REFEREE_IP = "224.5.23.1"
CI_MODE_OUTPUT_RECEIVE_BUFFER_SIZE = 9000

def __init__(self, supress_logs: bool = False) -> None:
def __init__(self, supress_logs: bool = False, use_conventional_port=False) -> None:
"""Run Gamecontroller

:param supress_logs: Whether to suppress the logs
:param use_conventional_port: whether or not to use the conventional port!
"""

self.supress_logs = supress_logs

# We need to find 2 free ports to use for the gamecontroller
# so that we can run multiple gamecontroller instances in parallel
self.referee_port = self.next_free_port()
self.ci_port = self.next_free_port()
# We default to using a non-conventional port to avoid emitting
# on the same port as what other teams may be listening on.
if use_conventional_port:
if not self.is_valid_port(SSL_REFEREE_PORT):
raise OSError(f"Cannot use port {SSL_REFEREE_PORT} for Gamecontroller")

self.referee_port = SSL_REFEREE_PORT
else:
self.referee_port = self.next_free_port(random.randint(1024, 65535))

self.ci_port = self.next_free_port()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicated call (same as line 40)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, locally I tried updating this line so we also use a random port for this as well, but I'm not sure why I'm still unable to to launch two Thunderscopes at once: ./tbots.py run thunderscope --disable_communication --run_diagnostics --run_blue --interface eno1 --launch_gc

This was mainly out of curiosity and isn't something we need to be able to achieve.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

# this allows gamecontroller to listen to override commands
self.command_override_buffer = ThreadSafeBuffer(
buffer_size=2, protobuf_type=ManualGCCommand
)

@staticmethod
def get_referee_port_static(gamecontroller: Gamecontroller):
"""
return the default port if gamecontroller is None, otherwise the port that the gamecontroller is using.

:param gamecontroller: the gamecontroller we are using
:return: the default port if gamecontroller is None, otherwise the port that the gamecontroller is using.
"""
if gamecontroller is not None:
return gamecontroller.get_referee_port()

return SSL_REFEREE_PORT

def get_referee_port(self) -> int:
"""
Sometimes, the port that we are using changes depending on context.
We want a getter function that returns the port we are using.

:return: the port that the game controller is currently using!
"""

return self.referee_port

def __enter__(self) -> "self":
"""Enter the gamecontroller context manager.

Expand Down Expand Up @@ -102,24 +134,35 @@ def refresh(self):
)
manual_command = self.command_override_buffer.get(return_cached=False)

def next_free_port(self, port: int = 40000, max_port: int = 65535) -> None:
def is_valid_port(self, port):
Mr-Anyone marked this conversation as resolved.
Show resolved Hide resolved
"""
determine whether or not a given port is valid

:param port: the port we are checking
:return: True if a port is valid False otherwise
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
sock.bind(("", port))
sock.close()
return True
except OSError:
return False

def next_free_port(self, start_port: int = 40000, max_port: int = 65535) -> int:
"""Find the next free port. We need to find 2 free ports to use for the gamecontroller
so that we can run multiple gamecontroller instances in parallel.

:param port: The port to start looking from
:param start_port: The port to start looking from
:param max_port: The maximum port to look up to
:return: The next free port

"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

while port <= max_port:
try:
sock.bind(("", port))
sock.close()
return port
except OSError:
port += 1
while start_port <= max_port:
if self.is_valid_port(start_port):
return start_port
start_port += 1

raise IOError("no free ports")

Expand Down
6 changes: 5 additions & 1 deletion src/software/thunderscope/robot_communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(
estop_path: os.PathLike = None,
estop_baudrate: int = 115200,
enable_radio: bool = False,
referee_port: int = SSL_REFEREE_PORT,
):
"""Initialize the communication with the robots

Expand All @@ -37,8 +38,11 @@ def __init__(
:param estop_path: The path to the estop
:param estop_baudrate: The baudrate of the estop
:param enable_radio: Whether to use radio to send primitives to robots
:param referee_port: the referee port that we are using. If this is None, the default port is used

"""

self.referee_port = referee_port
self.receive_ssl_referee_proto = None
self.receive_ssl_wrapper = None
self.sequence_number = 0
Expand Down Expand Up @@ -113,7 +117,7 @@ def setup_for_fullsystem(self) -> None:

self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener(
SSL_REFEREE_ADDRESS,
SSL_REFEREE_PORT,
self.referee_port,
lambda data: self.current_proto_unix_io.send_proto(Referee, data),
True,
)
nimazareian marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
19 changes: 18 additions & 1 deletion src/software/thunderscope/thunderscope_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from software.thunderscope.binary_context_managers.full_system import FullSystem
from software.thunderscope.binary_context_managers.simulator import Simulator
from software.thunderscope.binary_context_managers.game_controller import Gamecontroller

from software.thunderscope.binary_context_managers.tigers_autoref import TigersAutoref


Expand Down Expand Up @@ -218,8 +219,19 @@
help="Whether to populate with default robot positions (False) or start with an empty field (True) for AI vs AI",
)

parser.add_argument(
"--launch_gc",
action="store_true",
default=False,
help="whether or not to launch the gamecontroller when --run_blue or --run_yellow is ran",
)
nimazareian marked this conversation as resolved.
Show resolved Hide resolved

args = parser.parse_args()

# we only have --launch_gc parameter but not args.run_yellow and args.run_blue
if not args.run_blue and not args.run_yellow and args.launch_gc:
parser.error("--launch_gc has to be ran with --run_blue argument")

# Sanity check that an interface was provided
if args.run_blue or args.run_yellow:
if args.interface is None:
Expand Down Expand Up @@ -310,13 +322,18 @@
args.keyboard_estop, args.disable_communication
)

with RobotCommunication(
with (
Gamecontroller(supress_logs=(not args.verbose), use_conventional_port=False)
if args.launch_gc
else contextlib.nullcontext()
) as gamecontroller, RobotCommunication(
current_proto_unix_io=current_proto_unix_io,
multicast_channel=getRobotMulticastChannel(args.channel),
interface=args.interface,
estop_mode=estop_mode,
estop_path=estop_path,
enable_radio=args.enable_radio,
referee_port=Gamecontroller.get_referee_port_static(gamecontroller),
) as robot_communication:
nimazareian marked this conversation as resolved.
Show resolved Hide resolved

if estop_mode == EstopMode.KEYBOARD_ESTOP:
Expand Down
Loading