From 229d183d726e12601712ff6689ffd6e9781dbab1 Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 22 May 2024 22:30:51 -0700 Subject: [PATCH 01/50] init --- src/software/networking/udp/BUILD | 18 ++++++++ src/software/networking/udp/network_utils.cpp | 45 +++++++++++++++++++ src/software/networking/udp/network_utils.h | 11 +++++ .../networking/udp/proto_udp_listener.hpp | 29 +++++++++--- .../networking/udp/udp_network_factory.hpp | 13 ++++++ 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/software/networking/udp/network_utils.cpp create mode 100644 src/software/networking/udp/network_utils.h create mode 100644 src/software/networking/udp/udp_network_factory.hpp diff --git a/src/software/networking/udp/BUILD b/src/software/networking/udp/BUILD index e84149b344..de84270aa2 100644 --- a/src/software/networking/udp/BUILD +++ b/src/software/networking/udp/BUILD @@ -1,5 +1,15 @@ package(default_visibility = ["//visibility:public"]) +cc_library( + name = "network_utils", + hdrs = [ + "network_utils.h", + ], + srcs = [ + "network_utils.cpp", + ], +) + cc_library( name = "proto_udp_listener", hdrs = [ @@ -7,6 +17,7 @@ cc_library( ], visibility = ["//visibility:private"], deps = [ + ":network_utils", "//software/logger", "//software/util/typename", ], @@ -59,3 +70,10 @@ cc_library( ":udp_sender", ], ) + +cc_library( + name = "udp_network_factory", + hdrs = [ + "udp_network_factory.hpp", + ], +) diff --git a/src/software/networking/udp/network_utils.cpp b/src/software/networking/udp/network_utils.cpp new file mode 100644 index 0000000000..4433449a52 --- /dev/null +++ b/src/software/networking/udp/network_utils.cpp @@ -0,0 +1,45 @@ +#include "software/networking/udp/network_utils.h" + +#include +#include + +bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4) +{ + struct ifaddrs* ifAddrStruct = nullptr; + struct ifaddrs* ifa = nullptr; + + getifaddrs(&ifAddrStruct); + + for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_name == interface) + { + if (ipv4 && ifa->ifa_addr->sa_family == AF_INET) + { + char addressBuffer[INET_ADDRSTRLEN]; + struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; + inet_ntop(AF_INET, &sa->sin_addr, addressBuffer, INET_ADDRSTRLEN); + freeifaddrs(ifAddrStruct); + ip_address = addressBuffer; + return true; + } + else if (!ipv4 && ifa->ifa_addr->sa_family == AF_INET6) + { + char addressBuffer[INET6_ADDRSTRLEN]; + struct sockaddr_in6* sa = (struct sockaddr_in6*) ifa->ifa_addr; + inet_ntop(AF_INET6, &sa->sin6_addr, addressBuffer, INET6_ADDRSTRLEN); + freeifaddrs(ifAddrStruct); + ip_address = addressBuffer; + return true; + } + } + } + + return false; +} + +bool isIpv6(const std::string& ip_address) +{ + struct sockaddr_in6 sa; + return inet_pton(AF_INET6, ip_address.c_str(), &(sa.sin6_addr)) != 0; +} diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h new file mode 100644 index 0000000000..8b3e1a4d94 --- /dev/null +++ b/src/software/networking/udp/network_utils.h @@ -0,0 +1,11 @@ +#pragma once + +std::string getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); + +/** + * @brief Check if the given string is a valid IPv6 address + * + * @param ip_address The string to check + * @return true if the string is a valid IPv6 address, false otherwise + */ +bool isIpv6(const std::string& ip_address); diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 7c3b9b11c5..e6556014ce 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -8,6 +8,7 @@ #include #include "software/logger/logger.h" +#include "software/networking/udp/network_utils.h" #include "software/networking/udp/proto_udp_listener.hpp" #include "software/util/typename/typename.h" @@ -31,7 +32,7 @@ class ProtoUdpListener * @param multicast If true, joins the multicast group of given ip_address */ ProtoUdpListener(boost::asio::io_service& io_service, const std::string& ip_address, - unsigned short port, + unsigned short port, const std::string& listen_interface, std::function receive_callback, bool multicast); @@ -66,6 +67,8 @@ class ProtoUdpListener void handleDataReception(const boost::system::error_code& error, size_t num_bytes_received); + void setupMulticast(); + /** * Start listening for data */ @@ -89,12 +92,16 @@ class ProtoUdpListener template ProtoUdpListener::ProtoUdpListener( boost::asio::io_service& io_service, const std::string& ip_address, - const unsigned short port, std::function receive_callback, - bool multicast) + const unsigned short port, const std::string& listen_interface, + std::function receive_callback, bool multicast) : socket_(io_service), receive_callback(receive_callback) { - boost::asio::ip::udp::endpoint listen_endpoint( - boost::asio::ip::make_address(ip_address), port); + boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); + if (isIpv6(ip_address)) + { + boost_ip = boost::asio::ip::make_address(ip_address + "%" + listen_interface); + } + boost::asio::ip::udp::endpoint listen_endpoint(boost_ip, port); socket_.open(listen_endpoint.protocol()); socket_.set_option(boost::asio::socket_base::reuse_address(true)); try @@ -113,6 +120,7 @@ ProtoUdpListener::ProtoUdpListener( if (multicast) { + setupMulticast(boost_ip, listen_interface); // Join the multicast group. socket_.set_option(boost::asio::ip::multicast::join_group( boost::asio::ip::address::from_string(ip_address))); @@ -199,6 +207,17 @@ void ProtoUdpListener::handleDataReception( } } +template +ProtoUdpListener::setupMulticast(boost::asio::ip::address ip_address, + const std::string& listen_interface) +{ + if (ip_address.is_v4()) + { + + } + socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); +} + template ProtoUdpListener::~ProtoUdpListener() { diff --git a/src/software/networking/udp/udp_network_factory.hpp b/src/software/networking/udp/udp_network_factory.hpp new file mode 100644 index 0000000000..24e823b580 --- /dev/null +++ b/src/software/networking/udp/udp_network_factory.hpp @@ -0,0 +1,13 @@ +#pragma once + +class UdpNetworkFactory +{ + public: + template + static createThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, + const std::string& interface, std::function receive_callback, bool multicast); + + template + static createThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, + const std::string& interface, std::function receive_callback, bool multicast); +}; From 236ddc71858b58131dbf88421babba2bf5285cfd Mon Sep 17 00:00:00 2001 From: arun Date: Mon, 27 May 2024 00:01:34 -0700 Subject: [PATCH 02/50] wip --- src/software/networking/udp/network_utils.cpp | 3 +-- src/software/networking/udp/network_utils.h | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/software/networking/udp/network_utils.cpp b/src/software/networking/udp/network_utils.cpp index 4433449a52..998a69ef8b 100644 --- a/src/software/networking/udp/network_utils.cpp +++ b/src/software/networking/udp/network_utils.cpp @@ -1,7 +1,6 @@ #include "software/networking/udp/network_utils.h" -#include -#include +#include bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4) { diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 8b3e1a4d94..077ba15846 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -1,6 +1,9 @@ #pragma once -std::string getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); +#include +#include + +bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); /** * @brief Check if the given string is a valid IPv6 address From 8a457b2834db958531b414c2067dcb87eb7e7b68 Mon Sep 17 00:00:00 2001 From: arun Date: Mon, 27 May 2024 00:38:55 -0700 Subject: [PATCH 03/50] wip --- src/software/networking/udp/BUILD | 1 + .../networking/udp/proto_udp_listener.hpp | 18 ++++++---- .../udp/threaded_proto_udp_sender.hpp | 2 +- .../networking/udp/threaded_udp_sender.cpp | 4 +-- .../networking/udp/threaded_udp_sender.h | 2 +- src/software/networking/udp/udp_sender.cpp | 35 ++++++++++++++++--- src/software/networking/udp/udp_sender.h | 4 ++- 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/software/networking/udp/BUILD b/src/software/networking/udp/BUILD index de84270aa2..145386a1b1 100644 --- a/src/software/networking/udp/BUILD +++ b/src/software/networking/udp/BUILD @@ -33,6 +33,7 @@ cc_library( ], visibility = ["//visibility:private"], deps = [ + ":network_utils", "@boost//:asio", ], ) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index e6556014ce..ce04fdc4b9 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -67,7 +67,8 @@ class ProtoUdpListener void handleDataReception(const boost::system::error_code& error, size_t num_bytes_received); - void setupMulticast(); + void setupMulticast(const boost::asio::ip::address& ip_address, + const std::string& listen_interface); /** * Start listening for data @@ -121,9 +122,6 @@ ProtoUdpListener::ProtoUdpListener( if (multicast) { setupMulticast(boost_ip, listen_interface); - // Join the multicast group. - socket_.set_option(boost::asio::ip::multicast::join_group( - boost::asio::ip::address::from_string(ip_address))); } startListen(); @@ -208,12 +206,20 @@ void ProtoUdpListener::handleDataReception( } template -ProtoUdpListener::setupMulticast(boost::asio::ip::address ip_address, +ProtoUdpListener::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& listen_interface) { if (ip_address.is_v4()) { - + std::string interface_ip; + if (!getLocalIp(listen_interface, interface_ip)) + { + LOG(FATAL) << "Could not find the local ip address for the given interface: " + << listen_interface << std::endl; + } + socket_.set_option(boost::asio::ip::multicast::join_group( + ip_address, boost::asio::ip::address::from_string(interface_ip))); + return; } socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); } diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index 477cdeabcd..e67bbca967 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -21,7 +21,7 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * @param port The port to send SendProto data on * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, + ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast) : ThreadedUdpSender(ip_address, port, multicast) { diff --git a/src/software/networking/udp/threaded_udp_sender.cpp b/src/software/networking/udp/threaded_udp_sender.cpp index 93e4d2d027..16cc610df6 100644 --- a/src/software/networking/udp/threaded_udp_sender.cpp +++ b/src/software/networking/udp/threaded_udp_sender.cpp @@ -1,9 +1,9 @@ #include "software/networking/udp/threaded_udp_sender.h" ThreadedUdpSender::ThreadedUdpSender(const std::string& ip_address, - const unsigned short port, bool multicast) + const unsigned short port, const std::string& interface, bool multicast) : io_service(), - udp_sender(io_service, ip_address, port, multicast), + udp_sender(io_service, ip_address, port, interface, multicast), io_service_thread([this]() { io_service.run(); }) { } diff --git a/src/software/networking/udp/threaded_udp_sender.h b/src/software/networking/udp/threaded_udp_sender.h index 31c95981ec..11a1f9987f 100644 --- a/src/software/networking/udp/threaded_udp_sender.h +++ b/src/software/networking/udp/threaded_udp_sender.h @@ -20,7 +20,7 @@ class ThreadedUdpSender * @param port The port to send sendString data on * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedUdpSender(const std::string& ip_address, unsigned short port, bool multicast); + ThreadedUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast); ~ThreadedUdpSender(); diff --git a/src/software/networking/udp/udp_sender.cpp b/src/software/networking/udp/udp_sender.cpp index 83580ecd41..b43884d3cb 100644 --- a/src/software/networking/udp/udp_sender.cpp +++ b/src/software/networking/udp/udp_sender.cpp @@ -1,21 +1,27 @@ #include "udp_sender.h" +#include "software/networking/udp/network_utils.h" + #include UdpSender::UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - const unsigned short port, bool multicast) + const unsigned short port, const std::string& interface, bool multicast) : socket_(io_service) { - boost::asio::ip::address addr = boost::asio::ip::make_address(ip_address); + boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); + if (isIpv6(ip_address)) + { + boost_ip = boost::asio::ip::make_address(ip_address + "%" + interface); + } // The receiver endpoint identifies where this UdpSender will send data to - receiver_endpoint = boost::asio::ip::udp::endpoint(addr, port); + receiver_endpoint = boost::asio::ip::udp::endpoint(boost_ip, port); socket_.open(receiver_endpoint.protocol()); if (multicast) { - socket_.set_option(boost::asio::ip::multicast::join_group(addr)); + setupMulticast(boost_ip, interface); } } @@ -24,6 +30,27 @@ void UdpSender::sendString(const std::string& message) socket_.send_to(boost::asio::buffer(message, message.length()), receiver_endpoint); } +void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface) +{ + if (ip_address.is_v4()) + { + std::string interface_ip; + if (!getLocalIp(interface, interface_ip)) + { + std::cerr << "UdpSender: Could not get the local IP address for the interface " + "specified. (interface = " + << interface << ")" << std::endl; + } + + socket_.set_option(boost::asio::ip::multicast::join_group(ip_address.to_v4(), + boost::asio::ip::address::from_string(interface_ip).to_v4())); + return; + } + + socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); +} + + UdpSender::~UdpSender() { socket_.close(); diff --git a/src/software/networking/udp/udp_sender.h b/src/software/networking/udp/udp_sender.h index 791765f651..1e96047f6c 100644 --- a/src/software/networking/udp/udp_sender.h +++ b/src/software/networking/udp/udp_sender.h @@ -23,10 +23,12 @@ class UdpSender * @param multicast If true, joins the multicast group of given ip_address */ UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - unsigned short port, bool multicast); + unsigned short port, const std::string& interface, bool multicast); ~UdpSender(); + void setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface); + /** * Sends a string message to the initialized ip address and port * This function returns after the message has been sent. From a925f7cdf396a4cf32c330de685ac3a94131a717 Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 29 May 2024 23:43:16 -0700 Subject: [PATCH 04/50] mayhaps it works --- .../jetson_nano/services/network/network.cpp | 6 +++--- src/software/jetson_nano/services/network/network.h | 2 +- src/software/jetson_nano/thunderloop.cpp | 9 ++++----- src/software/logger/network_sink.cpp | 4 ++-- src/software/logger/plotjuggler_sink.cpp | 4 ++-- src/software/logger/plotjuggler_sink.h | 2 +- src/software/network_log_listener_main.cpp | 4 ++-- src/software/networking/udp/proto_udp_listener.hpp | 4 ++-- .../networking/udp/threaded_proto_udp_listener.hpp | 12 ++++++------ .../networking/udp/threaded_proto_udp_sender.hpp | 2 +- src/software/python_bindings.cpp | 4 ++-- .../binary_context_managers/game_controller.py | 5 +++-- src/software/thunderscope/robot_communication.py | 8 +++++--- src/software/thunderscope/thunderscope.py | 2 ++ src/software/thunderscope/thunderscope_main.py | 1 + 15 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/software/jetson_nano/services/network/network.cpp b/src/software/jetson_nano/services/network/network.cpp index fb554173db..bde99504c7 100644 --- a/src/software/jetson_nano/services/network/network.cpp +++ b/src/software/jetson_nano/services/network/network.cpp @@ -2,15 +2,15 @@ NetworkService::NetworkService(const std::string& ip_address, unsigned short primitive_listener_port, - unsigned short robot_status_sender_port, bool multicast) + unsigned short robot_status_sender_port, const std::string& interface, bool multicast) : primitive_tracker(ProtoTracker("primitive set")) { sender = std::make_unique>( - ip_address, robot_status_sender_port, multicast); + ip_address, robot_status_sender_port, interface, multicast); udp_listener_primitive_set = std::make_unique>( - ip_address, primitive_listener_port, + ip_address, primitive_listener_port, interface, boost::bind(&NetworkService::primitiveSetCallback, this, _1), multicast); radio_listener_primitive_set = diff --git a/src/software/jetson_nano/services/network/network.h b/src/software/jetson_nano/services/network/network.h index ac7447553e..6b8c1d07c1 100644 --- a/src/software/jetson_nano/services/network/network.h +++ b/src/software/jetson_nano/services/network/network.h @@ -26,7 +26,7 @@ class NetworkService * we should join the group */ NetworkService(const std::string& ip_address, unsigned short primitive_listener_port, - unsigned short robot_status_sender_port, bool multicast); + unsigned short robot_status_sender_port, const std::string& interface, bool multicast); /** * When the network service is polled, it sends the robot_status and returns diff --git a/src/software/jetson_nano/thunderloop.cpp b/src/software/jetson_nano/thunderloop.cpp index 975fd5aa0c..302a050dfd 100644 --- a/src/software/jetson_nano/thunderloop.cpp +++ b/src/software/jetson_nano/thunderloop.cpp @@ -57,9 +57,8 @@ extern "C" *(crash_msg.mutable_status()) = *robot_status; auto sender = std::make_unique>( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id)) + "%" + - network_interface, - ROBOT_CRASH_PORT, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id)), + ROBOT_CRASH_PORT, network_interface, true); sender->sendProto(crash_msg); std::cerr << "Broadcasting robot crash msg"; @@ -107,8 +106,8 @@ Thunderloop::Thunderloop(const RobotConstants_t& robot_constants, bool enable_lo << "THUNDERLOOP: Network Logger initialized! Next initializing Network Service"; network_service_ = std::make_unique( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)) + "%" + network_interface_, - PRIMITIVE_PORT, ROBOT_STATUS_PORT, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), + PRIMITIVE_PORT, ROBOT_STATUS_PORT, network_interface, true); LOG(INFO) << "THUNDERLOOP: Network Service initialized! Next initializing Power Service"; diff --git a/src/software/logger/network_sink.cpp b/src/software/logger/network_sink.cpp index 1b72097201..b0a6beebd5 100644 --- a/src/software/logger/network_sink.cpp +++ b/src/software/logger/network_sink.cpp @@ -12,8 +12,8 @@ NetworkSink::NetworkSink(unsigned int channel, const std::string& interface, int : robot_id(robot_id), log_merger(LogMerger(enable_log_merging)) { log_output.reset(new ThreadedProtoUdpSender( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel)) + "%" + interface, - ROBOT_LOGS_PORT, true)); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel)), + ROBOT_LOGS_PORT, interface, true)); } void NetworkSink::sendToNetwork(g3::LogMessageMover log_entry) diff --git a/src/software/logger/plotjuggler_sink.cpp b/src/software/logger/plotjuggler_sink.cpp index bd1099e94c..99eb693d21 100644 --- a/src/software/logger/plotjuggler_sink.cpp +++ b/src/software/logger/plotjuggler_sink.cpp @@ -5,8 +5,8 @@ #include "shared/constants.h" -PlotJugglerSink::PlotJugglerSink() - : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, false) +PlotJugglerSink::PlotJugglerSink(const std::string& interface) + : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, interface, false) { } diff --git a/src/software/logger/plotjuggler_sink.h b/src/software/logger/plotjuggler_sink.h index b4ab754fed..b7c25435ce 100644 --- a/src/software/logger/plotjuggler_sink.h +++ b/src/software/logger/plotjuggler_sink.h @@ -17,7 +17,7 @@ class PlotJugglerSink /** * Creates a PlotJugglerSink that sends udp packets to the PlotJuggler server */ - PlotJugglerSink(); + PlotJugglerSink(const std::string& interface="lo"); ~PlotJugglerSink() = default; diff --git a/src/software/network_log_listener_main.cpp b/src/software/network_log_listener_main.cpp index b762e63ef1..ec8e579cb1 100644 --- a/src/software/network_log_listener_main.cpp +++ b/src/software/network_log_listener_main.cpp @@ -94,8 +94,8 @@ int main(int argc, char **argv) }; auto log_input = std::make_unique>( - std::string(ROBOT_MULTICAST_CHANNELS.at(args.channel)) + "%" + args.interface, - ROBOT_LOGS_PORT, robot_log_callback, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(args.channel)), + ROBOT_LOGS_PORT, args.interface, robot_log_callback, true); LOG(INFO) << "Network logger listening on channel " diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index ce04fdc4b9..13769bfa9f 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -206,7 +206,7 @@ void ProtoUdpListener::handleDataReception( } template -ProtoUdpListener::setupMulticast(const boost::asio::ip::address& ip_address, +void ProtoUdpListener::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& listen_interface) { if (ip_address.is_v4()) @@ -218,7 +218,7 @@ ProtoUdpListener::setupMulticast(const boost::asio::ip::address& << listen_interface << std::endl; } socket_.set_option(boost::asio::ip::multicast::join_group( - ip_address, boost::asio::ip::address::from_string(interface_ip))); + ip_address.to_v4(), boost::asio::ip::address::from_string(interface_ip).to_v4())); return; } socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 674819ee0f..207a1dd374 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -23,7 +23,7 @@ class ThreadedProtoUdpListener * from the network * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, + ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, std::function receive_callback, bool multicast); @@ -37,7 +37,7 @@ class ThreadedProtoUdpListener * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network */ - ThreadedProtoUdpListener(unsigned short port, + ThreadedProtoUdpListener(unsigned short port, const std::string& interface, std::function receive_callback); /** @@ -60,10 +60,10 @@ class ThreadedProtoUdpListener template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const std::string& ip_address, const unsigned short port, + const std::string& ip_address, const unsigned short port, const std::string& interface, std::function receive_callback, bool multicast) : io_service(), - udp_listener(io_service, ip_address, port, receive_callback, multicast) + udp_listener(io_service, ip_address, port, interface, receive_callback, multicast) { // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); @@ -71,8 +71,8 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const unsigned short port, std::function receive_callback) - : io_service(), udp_listener(io_service, port, receive_callback) + const unsigned short port, const std::string& interface, std::function receive_callback) + : io_service(), udp_listener(io_service, port, interface, receive_callback) { // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index e67bbca967..b92288696c 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -23,7 +23,7 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender */ ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast) - : ThreadedUdpSender(ip_address, port, multicast) + : ThreadedUdpSender(ip_address, port, interface, multicast) { } diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index f5143e6a94..8bc9a0bb6d 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -59,7 +59,7 @@ void declareThreadedProtoUdpSender(py::module& m, std::string name) std::string pyclass_name = name + "ProtoUdpSender"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init()) + .def(py::init()) .def("send_proto", &Class::sendProto); } @@ -93,7 +93,7 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init&, bool>()) + .def(py::init&, bool>()) .def("close", &Class::close); } diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index a8c01f87cb..bb8e26b540 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -27,12 +27,13 @@ class Gamecontroller(object): 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, interface: str = "lo") -> None: """Run Gamecontroller :param supress_logs: Whether to suppress the logs """ self.supress_logs = supress_logs + self.interface = interface # We need to find 2 free ports to use for the gamecontroller # so that we can run multiple gamecontroller instances in parallel @@ -150,7 +151,7 @@ def __send_referee_command(data: Referee) -> None: autoref_proto_unix_io.send_proto(Referee, data) self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( - Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, + Gamecontroller.REFEREE_IP, self.referee_port, self.interface, __send_referee_command, True, ) blue_full_system_proto_unix_io.register_observer( diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 8f37b76664..4513dd1b18 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -303,15 +303,17 @@ def __enter__(self) -> "self": ) self.receive_robot_log = tbots_cpp.RobotLogProtoListener( - self.multicast_channel + "%" + self.interface, + self.multicast_channel, ROBOT_LOGS_PORT, + self.interface, lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, ) self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( - self.multicast_channel + "%" + self.interface, + self.multicast_channel, ROBOT_CRASH_PORT, + self.interface, lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), True, ) @@ -321,7 +323,7 @@ def __enter__(self) -> "self": self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( - self.multicast_channel + "%" + self.interface, PRIMITIVE_PORT, True + self.multicast_channel, PRIMITIVE_PORT, self.interface, True ) self.running = True diff --git a/src/software/thunderscope/thunderscope.py b/src/software/thunderscope/thunderscope.py index a64957a370..08c3303b71 100644 --- a/src/software/thunderscope/thunderscope.py +++ b/src/software/thunderscope/thunderscope.py @@ -120,6 +120,8 @@ def __init__( lambda: QMessageBox.information(self.window, "Help", THUNDERSCOPE_HELP_TEXT) ) + print("arun: thunderscope initializing") + def reset_layout(self) -> None: """Reset the layout to the default layout""" saved_layout_path = pathlib.Path(LAST_OPENED_LAYOUT_PATH) diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index fd53817470..0358cd93a4 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -449,6 +449,7 @@ def __ticker(tick_rate_ms: int) -> None: log_path=args.yellow_full_system_runtime_dir, time_provider=autoref.time_provider if args.enable_autoref else None, ) as yellow_logger: + print("arun: initialized everything") tscope.register_refresh_function(gamecontroller.refresh) From 625496646ae21167352547ecb7c62fb33a7380da Mon Sep 17 00:00:00 2001 From: arun Date: Sun, 2 Jun 2024 23:39:14 -0700 Subject: [PATCH 05/50] wip --- src/proto/parameters.proto | 9 +++++++++ .../networking/udp/threaded_proto_udp_listener.hpp | 8 ++++---- .../networking/udp/threaded_proto_udp_sender.hpp | 4 ++-- src/software/python_bindings.cpp | 5 +++-- .../binary_context_managers/game_controller.py | 2 +- .../thunderscope/common/proto_configuration_widget.py | 2 ++ .../thunderscope/common/proto_parameter_tree_util.py | 3 +++ src/software/thunderscope/constants.py | 2 +- src/software/thunderscope/robot_communication.py | 9 ++++++--- 9 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/proto/parameters.proto b/src/proto/parameters.proto index 2d09fc26c8..fec330be9b 100644 --- a/src/proto/parameters.proto +++ b/src/proto/parameters.proto @@ -49,6 +49,15 @@ message AiControlConfig // Override the existing play with the Play enum provided required PlayName override_ai_play = 2 [default = UseAiSelection]; + + // The referee interface + required string referee_interface = 3 [default = "lo"]; + + // The robot status interface + required string robot_status_interface = 4 [default = "lo"]; + + // The vision network interface + required string vision_network_interface = 5 [default = "lo"]; } message AiParameterConfig diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 207a1dd374..199d76276c 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -23,9 +23,9 @@ class ThreadedProtoUdpListener * from the network * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, + ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, std::function receive_callback, - bool multicast); + bool multicast, const std::string& interface); /** * Creates a ThreadedProtoUdpListener that will listen for ReceiveProtoT packets @@ -60,8 +60,8 @@ class ThreadedProtoUdpListener template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const std::string& ip_address, const unsigned short port, const std::string& interface, - std::function receive_callback, bool multicast) + const std::string& ip_address, const unsigned short port, + std::function receive_callback, bool multicast, const std::string& interface) : io_service(), udp_listener(io_service, ip_address, port, interface, receive_callback, multicast) { diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index b92288696c..c3388510fa 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -21,8 +21,8 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * @param port The port to send SendProto data on * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, - bool multicast) + ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, + bool multicast, const std::string& interface="lo") : ThreadedUdpSender(ip_address, port, interface, multicast) { } diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 8bc9a0bb6d..50d789f2e9 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -59,7 +59,8 @@ void declareThreadedProtoUdpSender(py::module& m, std::string name) std::string pyclass_name = name + "ProtoUdpSender"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init()) + .def(py::init()) + .def(py::init()) .def("send_proto", &Class::sendProto); } @@ -93,7 +94,7 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init&, bool>()) + .def(py::init&, bool, std::string>()) .def("close", &Class::close); } diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index bb8e26b540..677a522edb 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -151,7 +151,7 @@ def __send_referee_command(data: Referee) -> None: autoref_proto_unix_io.send_proto(Referee, data) self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( - Gamecontroller.REFEREE_IP, self.referee_port, self.interface, __send_referee_command, True, + Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, self.interface ) blue_full_system_proto_unix_io.register_observer( diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 6401d4239e..5d9ff67d8f 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -5,6 +5,7 @@ from thefuzz import fuzz from proto.import_all_protos import * from software.thunderscope.common import proto_parameter_tree_util +from software.thunderscope.constants import CUSTOM_PARAMETERS_OVERRIDE class ProtoConfigurationWidget(QWidget): @@ -119,6 +120,7 @@ def config_proto_to_param_dict(self, message, search_term=None): """ + print(CUSTOM_PARAMETERS_OVERRIDE) field_list = proto_parameter_tree_util.config_proto_to_field_list( message, search_term=search_term, diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index 16576ef7c1..cba8173645 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -170,6 +170,9 @@ def config_proto_to_field_list( if fuzz.partial_ratio(search_term, key) < search_filter_threshold: continue + print(descriptor) + print(type(descriptor)) + print(descriptor.name) if descriptor.type == descriptor.TYPE_MESSAGE: field_list.append( { diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 40b7e0563f..f35142b7b3 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -2,6 +2,7 @@ from proto.import_all_protos import * from enum import Enum, IntEnum from proto.robot_log_msg_pb2 import LogLevel +from proto.parameters_pb2 import AiControlConfig import textwrap @@ -197,7 +198,6 @@ class EstopMode(IntEnum): """ ) - def is_field_message_empty(field: Field) -> bool: """ Checks if a field message is empty diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 4513dd1b18..980aadc3a3 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -109,6 +109,7 @@ def setup_for_fullsystem(self) -> None: SSL_VISION_PORT, lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), True, + self.interface, ) self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( @@ -116,6 +117,7 @@ def setup_for_fullsystem(self) -> None: SSL_REFEREE_PORT, lambda data: self.current_proto_unix_io.send_proto(Referee, data), True, + "wlp3s0", ) self.robots_connected_to_fullsystem = { @@ -300,22 +302,23 @@ def __enter__(self) -> "self": ROBOT_STATUS_PORT, lambda data: self.__forward_to_proto_unix_io(RobotStatus, data), True, + self.interface, ) self.receive_robot_log = tbots_cpp.RobotLogProtoListener( self.multicast_channel, ROBOT_LOGS_PORT, - self.interface, lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, + self.interface, ) self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( self.multicast_channel, ROBOT_CRASH_PORT, - self.interface, lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), True, + self.interface, ) # Create multicast senders @@ -323,7 +326,7 @@ def __enter__(self) -> "self": self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( - self.multicast_channel, PRIMITIVE_PORT, self.interface, True + self.multicast_channel, PRIMITIVE_PORT, True, self.interface, ) self.running = True From 7bee165d1a3db487b7d943851123d7540a35e18c Mon Sep 17 00:00:00 2001 From: arun Date: Mon, 3 Jun 2024 23:37:38 -0700 Subject: [PATCH 06/50] wip --- src/proto/parameters.proto | 21 +++++++----- src/software/thunderscope/common/BUILD | 3 ++ .../common/proto_configuration_widget.py | 2 -- .../common/proto_parameter_tree_util.py | 32 ++++++++++++++++--- src/software/thunderscope/constants.py | 1 - src/software/thunderscope/requirements.txt | 1 + .../thunderscope/robot_communication.py | 9 ++++++ 7 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/proto/parameters.proto b/src/proto/parameters.proto index fec330be9b..df56f2eeff 100644 --- a/src/proto/parameters.proto +++ b/src/proto/parameters.proto @@ -50,14 +50,7 @@ message AiControlConfig // Override the existing play with the Play enum provided required PlayName override_ai_play = 2 [default = UseAiSelection]; - // The referee interface - required string referee_interface = 3 [default = "lo"]; - - // The robot status interface - required string robot_status_interface = 4 [default = "lo"]; - - // The vision network interface - required string vision_network_interface = 5 [default = "lo"]; + required NetworkConfig network_config = 3; } message AiParameterConfig @@ -428,3 +421,15 @@ message PossessionTrackerConfig (bounds).max_double_value = 10.0 ]; } + +message NetworkConfig +{ + // The referee interface + required string referee_interface = 1 [default = "lo"]; + + // The robot status interface + required string robot_status_interface = 2 [default = "lo"]; + + // The vision network interface + required string vision_network_interface = 3 [default = "lo"]; +} diff --git a/src/software/thunderscope/common/BUILD b/src/software/thunderscope/common/BUILD index 3f7077fa67..e916cb221d 100644 --- a/src/software/thunderscope/common/BUILD +++ b/src/software/thunderscope/common/BUILD @@ -1,5 +1,7 @@ package(default_visibility = ["//visibility:public"]) +load("@thunderscope_deps//:requirements.bzl", "requirement") + py_library( name = "proto_configuration_widget", srcs = ["proto_configuration_widget.py"], @@ -26,6 +28,7 @@ py_library( srcs = ["proto_parameter_tree_util.py"], deps = [ ":common_widgets", + requirement("netifaces"), ], ) diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 5d9ff67d8f..6401d4239e 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -5,7 +5,6 @@ from thefuzz import fuzz from proto.import_all_protos import * from software.thunderscope.common import proto_parameter_tree_util -from software.thunderscope.constants import CUSTOM_PARAMETERS_OVERRIDE class ProtoConfigurationWidget(QWidget): @@ -120,7 +119,6 @@ def config_proto_to_param_dict(self, message, search_term=None): """ - print(CUSTOM_PARAMETERS_OVERRIDE) field_list = proto_parameter_tree_util.config_proto_to_field_list( message, search_term=search_term, diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index cba8173645..925b0cd06e 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -2,8 +2,13 @@ from pyqtgraph import parametertree from google.protobuf.json_format import MessageToDict from thefuzz import fuzz +import netifaces +CUSTOM_PARAMETERS_OVERRIDE = {"referee_interface": "__create_network_enum", + "robot_status_interface": "__create_network_enum", + "vision_network_interface": "__create_network_enum"} + def __create_int_parameter_writable(key, value, descriptor): """Converts an int field of a proto to a NumericParameterItem with the min/max bounds set according to the provided ParameterRangeOptions @@ -124,6 +129,26 @@ def __create_parameter_read_only(key, value, descriptor): return {"name": key, "type": "str", "value": value, "readonly": True} +def __create_network_enum(key, value, _): + """Converts an enum field in a protobuf to a ListParameter. Uses + the options to lookup all possible enum values and provides them + as a dropdown option. + + :param key: The name of the parameter + :param value: The default value + :param descriptor: The proto descriptor + + """ + network_interfaces = netifaces.interfaces() + + return parametertree.parameterTypes.ListParameter( + name=key, + default=None, + value=value, + limits=network_interfaces + ) + + def get_string_val(descriptor, value): """ Converts the given value to a string depending on the descriptor type @@ -170,10 +195,9 @@ def config_proto_to_field_list( if fuzz.partial_ratio(search_term, key) < search_filter_threshold: continue - print(descriptor) - print(type(descriptor)) - print(descriptor.name) - if descriptor.type == descriptor.TYPE_MESSAGE: + if descriptor.name in CUSTOM_PARAMETERS_OVERRIDE.keys(): + field_list.append(eval(CUSTOM_PARAMETERS_OVERRIDE[descriptor.name])(key, value, descriptor)) + elif descriptor.type == descriptor.TYPE_MESSAGE: field_list.append( { "name": key, diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index f35142b7b3..4dfbccbd1d 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -2,7 +2,6 @@ from proto.import_all_protos import * from enum import Enum, IntEnum from proto.robot_log_msg_pb2 import LogLevel -from proto.parameters_pb2 import AiControlConfig import textwrap diff --git a/src/software/thunderscope/requirements.txt b/src/software/thunderscope/requirements.txt index fef73d9322..16d1ee1ab1 100644 --- a/src/software/thunderscope/requirements.txt +++ b/src/software/thunderscope/requirements.txt @@ -1,2 +1,3 @@ +netifaces==0.11.0 numpy==1.24.4 pyqtgraph==0.13.3 diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 980aadc3a3..59ddfc9e9a 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -75,6 +75,11 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) + self.thunderbots_config_buffer = ThreadSafeBuffer(1, ThunderbotsConfig) + self.current_proto_unix_io.register_observer( + ThunderbotsConfig, self.thunderbots_config_buffer + ) + self.send_estop_state_thread = threading.Thread( target=self.__send_estop_state, daemon=True ) @@ -224,6 +229,10 @@ def __run_primitive_set(self) -> None: """ while self.running: + thunderbots_config = self.thunderbots_config_buffer.get(block=False, return_cached=False) + if thunderbots_config is not None: + print(thunderbots_config) + # total primitives for all robots robot_primitives = {} From dfe793fe52f87ae44b62d98d843bc79fa60f96d6 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 4 Jun 2024 23:30:11 -0700 Subject: [PATCH 07/50] wip --- src/proto/parameters.proto | 4 +- .../common/proto_parameter_tree_util.py | 6 +- .../thunderscope/robot_communication.py | 147 ++++++++++++------ .../thunderscope/thunderscope_main.py | 13 -- 4 files changed, 102 insertions(+), 68 deletions(-) diff --git a/src/proto/parameters.proto b/src/proto/parameters.proto index df56f2eeff..448e3be9c0 100644 --- a/src/proto/parameters.proto +++ b/src/proto/parameters.proto @@ -430,6 +430,6 @@ message NetworkConfig // The robot status interface required string robot_status_interface = 2 [default = "lo"]; - // The vision network interface - required string vision_network_interface = 3 [default = "lo"]; + // The vision interface + required string vision_interface = 3 [default = "lo"]; } diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index 925b0cd06e..b240b4fdff 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -5,9 +5,9 @@ import netifaces -CUSTOM_PARAMETERS_OVERRIDE = {"referee_interface": "__create_network_enum", - "robot_status_interface": "__create_network_enum", - "vision_network_interface": "__create_network_enum"} +CUSTOM_PARAMETERS_OVERRIDE = {"referee_interface": "__create_network_enum", + "robot_status_interface": "__create_network_enum", + "vision_interface": "__create_network_enum"} def __create_int_parameter_writable(key, value, descriptor): """Converts an int field of a proto to a NumericParameterItem with diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 59ddfc9e9a..956e5eb140 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -22,7 +22,6 @@ def __init__( self, current_proto_unix_io: ProtoUnixIO, multicast_channel: str, - interface: str, estop_mode: EstopMode, estop_path: os.PathLike = None, estop_baudrate: int = 115200, @@ -32,20 +31,24 @@ def __init__( :param current_proto_unix_io: the current proto unix io object :param multicast_channel: The multicast channel to use - :param interface: The interface to use :param estop_mode: what estop mode we are running right now, of type EstopMode :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 """ + self.is_setup_for_fullsystem = False self.receive_ssl_referee_proto = None self.receive_ssl_wrapper = None + + self.receive_robot_status = None + self.receive_robot_log = None + self.receive_robot_crash = None + self.sequence_number = 0 self.last_time = time.time() self.current_proto_unix_io = current_proto_unix_io self.multicast_channel = str(multicast_channel) - self.interface = interface self.estop_mode = estop_mode self.estop_path = estop_path @@ -75,6 +78,7 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) + self.network_config = NetworkConfig() self.thunderbots_config_buffer = ThreadSafeBuffer(1, ThunderbotsConfig) self.current_proto_unix_io.register_observer( ThunderbotsConfig, self.thunderbots_config_buffer @@ -105,29 +109,97 @@ def __init__( except Exception: raise Exception(f"Invalid Estop found at location {self.estop_path}") - def setup_for_fullsystem(self) -> None: + def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: str = "lo") -> None: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default """ - self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( - SSL_VISION_ADDRESS, - SSL_VISION_PORT, - lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), + change_referee_interface = (referee_interface != self.network_config.referee_interface) + change_vision_interface = (vision_interface != self.network_config.vision_interface) + + if self.receive_ssl_wrapper is not None and change_referee_interface: + self.receive_ssl_wrapper.close() + + if self.receive_ssl_referee_proto is not None and change_referee_interface: + self.receive_ssl_referee_proto.close() + + if self.receive_ssl_wrapper is None or change_referee_interface: + self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( + SSL_VISION_ADDRESS, + SSL_VISION_PORT, + lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), + True, + vision_interface, + ) + + if self.receive_ssl_referee_proto is None or change_vision_interface: + self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( + SSL_REFEREE_ADDRESS, + SSL_REFEREE_PORT, + lambda data: self.current_proto_unix_io.send_proto(Referee, data), + True, + referee_interface, + ) + + self.robots_connected_to_fullsystem = { + robot_id for robot_id in range(MAX_ROBOT_IDS_PER_SIDE) + } + + self.network_config.referee_interface = referee_interface + self.network_config.vision_interface = vision_interface + self.is_setup_for_fullsystem = True + + print(f"[RobotCommunication] Connected to referee on {referee_interface} and vision on {vision_interface}") + + + def __setup_for_robot_communication(self, robot_interface: str = "lo") -> None: + if robot_interface == self.network_config.robot_status_interface: + return + + if self.receive_robot_status is not None: + self.receive_robot_status.close() + + if self.receive_robot_log is not None: + self.receive_robot_log.close() + + if self.receive_robot_crash is not None: + self.receive_robot_crash.close() + + # Create the multicast listeners + self.receive_robot_status = tbots_cpp.RobotStatusProtoListener( + self.multicast_channel, + ROBOT_STATUS_PORT, + lambda data: self.__forward_to_proto_unix_io(RobotStatus, data), True, - self.interface, + robot_interface, ) - self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( - SSL_REFEREE_ADDRESS, - SSL_REFEREE_PORT, - lambda data: self.current_proto_unix_io.send_proto(Referee, data), + self.receive_robot_log = tbots_cpp.RobotLogProtoListener( + self.multicast_channel, + ROBOT_LOGS_PORT, + lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, - "wlp3s0", + robot_interface, ) - self.robots_connected_to_fullsystem = { - robot_id for robot_id in range(MAX_ROBOT_IDS_PER_SIDE) - } + self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( + self.multicast_channel, + ROBOT_CRASH_PORT, + lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), + True, + robot_interface, + ) + + # Create multicast senders + if self.enable_radio: + self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() + else: + self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( + self.multicast_channel, PRIMITIVE_PORT, True, robot_interface + ) + + self.network_config.robot_status_interface = robot_interface + + print(f"[RobotCommunication] Robot interface set to {robot_interface}") def close_for_fullsystem(self) -> None: if self.receive_ssl_wrapper: @@ -231,7 +303,13 @@ def __run_primitive_set(self) -> None: while self.running: thunderbots_config = self.thunderbots_config_buffer.get(block=False, return_cached=False) if thunderbots_config is not None: - print(thunderbots_config) + network_config = thunderbots_config.ai_config.ai_control_config.network_config + if self.is_setup_for_fullsystem: + self.setup_for_fullsystem(referee_interface=network_config.referee_interface, + vision_interface=network_config.vision_interface) + self.__setup_for_robot_communication( + robot_interface=network_config.robot_status_interface + ) # total primitives for all robots robot_primitives = {} @@ -305,38 +383,7 @@ def __enter__(self) -> "self": for RobotStatus, RobotLogs, and RobotCrash msgs, and multicast sender for PrimitiveSet """ - # Create the multicast listeners - self.receive_robot_status = tbots_cpp.RobotStatusProtoListener( - self.multicast_channel + "%" + self.interface, - ROBOT_STATUS_PORT, - lambda data: self.__forward_to_proto_unix_io(RobotStatus, data), - True, - self.interface, - ) - - self.receive_robot_log = tbots_cpp.RobotLogProtoListener( - self.multicast_channel, - ROBOT_LOGS_PORT, - lambda data: self.__forward_to_proto_unix_io(RobotLog, data), - True, - self.interface, - ) - - self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( - self.multicast_channel, - ROBOT_CRASH_PORT, - lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), - True, - self.interface, - ) - - # Create multicast senders - if self.enable_radio: - self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() - else: - self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( - self.multicast_channel, PRIMITIVE_PORT, True, self.interface, - ) + self.__setup_for_robot_communication() self.running = True diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 0358cd93a4..1846d3bbd7 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -135,13 +135,6 @@ action="store_true", help="Run robots diagnostics for Manual or Xbox control; estop required", ) - parser.add_argument( - "--interface", - action="store", - type=str, - default=None, - help="Which interface to communicate over", - ) parser.add_argument( "--channel", action="store", @@ -220,11 +213,6 @@ args = parser.parse_args() - # Sanity check that an interface was provided - if args.run_blue or args.run_yellow: - if args.interface is None: - parser.error("Must specify interface") - ########################################################################### # Visualize CPP Tests # ########################################################################### @@ -313,7 +301,6 @@ with 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, From ee9fdf2891327dbdaa89b809802ff8211e361780 Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 5 Jun 2024 23:40:52 -0700 Subject: [PATCH 08/50] wip --- .../common/proto_configuration_widget.py | 5 ++++- .../thunderscope/robot_communication.py | 20 +++---------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 6401d4239e..134a35e82c 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -6,15 +6,18 @@ from proto.import_all_protos import * from software.thunderscope.common import proto_parameter_tree_util +from threading import Thread +import time + class ProtoConfigurationWidget(QWidget): + DELAYED_CONFIGURATION_TIMEOUT_S = 5 """Creates a searchable parameter widget that can take any protobuf, and convert it into a pyqtgraph ParameterTree. This will allow users to modify the values. """ - def __init__( self, proto_to_configure, on_change_callback, search_filter_threshold=60, ): diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 956e5eb140..5498f01be8 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -116,13 +116,7 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: change_referee_interface = (referee_interface != self.network_config.referee_interface) change_vision_interface = (vision_interface != self.network_config.vision_interface) - if self.receive_ssl_wrapper is not None and change_referee_interface: - self.receive_ssl_wrapper.close() - - if self.receive_ssl_referee_proto is not None and change_referee_interface: - self.receive_ssl_referee_proto.close() - - if self.receive_ssl_wrapper is None or change_referee_interface: + if change_referee_interface: self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, @@ -131,7 +125,7 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: vision_interface, ) - if self.receive_ssl_referee_proto is None or change_vision_interface: + if change_vision_interface: self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( SSL_REFEREE_ADDRESS, SSL_REFEREE_PORT, @@ -155,15 +149,6 @@ def __setup_for_robot_communication(self, robot_interface: str = "lo") -> None: if robot_interface == self.network_config.robot_status_interface: return - if self.receive_robot_status is not None: - self.receive_robot_status.close() - - if self.receive_robot_log is not None: - self.receive_robot_log.close() - - if self.receive_robot_crash is not None: - self.receive_robot_crash.close() - # Create the multicast listeners self.receive_robot_status = tbots_cpp.RobotStatusProtoListener( self.multicast_channel, @@ -303,6 +288,7 @@ def __run_primitive_set(self) -> None: while self.running: thunderbots_config = self.thunderbots_config_buffer.get(block=False, return_cached=False) if thunderbots_config is not None: + print(f"[RobotCommunication] Received new ThunderbotsConfig") network_config = thunderbots_config.ai_config.ai_control_config.network_config if self.is_setup_for_fullsystem: self.setup_for_fullsystem(referee_interface=network_config.referee_interface, From 8d02281ce75927aab25d70bfbef893e3e0063fc6 Mon Sep 17 00:00:00 2001 From: arun Date: Sat, 8 Jun 2024 00:49:06 -0700 Subject: [PATCH 09/50] debugging --- .../networking/udp/proto_udp_listener.hpp | 25 ++++++-- .../udp/threaded_proto_udp_listener.hpp | 2 +- .../thunderscope/robot_communication.py | 59 ++++++++++++++----- .../thunderscope/widget_setup_functions.py | 1 + 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 13769bfa9f..afa5b2c309 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -173,6 +173,7 @@ void ProtoUdpListener::handleDataReception( { if (!running_) { + LOG(WARNING) << "arun returning"; return; } @@ -181,19 +182,26 @@ void ProtoUdpListener::handleDataReception( auto packet_data = ReceiveProtoT(); packet_data.ParseFromArray(raw_received_data_.data(), static_cast(num_bytes_received)); + LOG(WARNING) << "calling receive callback"; receive_callback(packet_data); // Once we've handled the data, start listening again startListen(); + LOG(WARNING) << "starting to listen"; } else { - // Start listening again to receive the next data - startListen(); - LOG(WARNING) << "An unknown network error occurred when attempting to receive " << TYPENAME(ReceiveProtoT) << " Data. The boost system error is: " << error.message() << std::endl; + + if (!running_) + { + LOG(WARNING) << "arun returning"; + return; + } + // Start listening again to receive the next data + startListen(); } if (num_bytes_received > MAX_BUFFER_LENGTH) @@ -227,12 +235,12 @@ void ProtoUdpListener::setupMulticast(const boost::asio::ip::addr template ProtoUdpListener::~ProtoUdpListener() { - close(); } template void ProtoUdpListener::close() { + LOG(WARNING) << "closing socket"; running_ = false; // Shutdown both send and receive on the socket @@ -243,7 +251,14 @@ void ProtoUdpListener::close() LOG(WARNING) << "An unknown network error occurred when attempting to shutdown UDP socket for " << TYPENAME(ReceiveProtoT) - << ". The boost system error is: " << error_code.message() << std::endl; + << ". The boost system error is: " << error_code << ": " << error_code.message() << std::endl; + LOG(WARNING) << "arun"; + } + + LOG(WARNING) << boost::asio::error::not_connected; + if (error_code == boost::asio::error::not_connected) + { + return; } socket_.close(error_code); diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 199d76276c..5ae221c225 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -65,6 +65,7 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( : io_service(), udp_listener(io_service, ip_address, port, interface, receive_callback, multicast) { + std::cout << "arun" << std::endl; // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); } @@ -81,7 +82,6 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( template ThreadedProtoUdpListener::~ThreadedProtoUdpListener() { - close(); } diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 5498f01be8..63f21b3602 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -44,6 +44,7 @@ def __init__( self.receive_robot_status = None self.receive_robot_log = None self.receive_robot_crash = None + self.send_primitive_set = None self.sequence_number = 0 self.last_time = time.time() @@ -79,9 +80,9 @@ def __init__( ) self.network_config = NetworkConfig() - self.thunderbots_config_buffer = ThreadSafeBuffer(1, ThunderbotsConfig) + self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( - ThunderbotsConfig, self.thunderbots_config_buffer + NetworkConfig, self.network_config_buffer ) self.send_estop_state_thread = threading.Thread( @@ -115,8 +116,14 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: """ change_referee_interface = (referee_interface != self.network_config.referee_interface) change_vision_interface = (vision_interface != self.network_config.vision_interface) + print(referee_interface) + print(vision_interface) + print(change_referee_interface) + print(change_vision_interface) - if change_referee_interface: + if change_vision_interface: + if self.receive_ssl_wrapper is not None: + self.receive_ssl_wrapper.close() self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, @@ -125,13 +132,26 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: vision_interface, ) - if change_vision_interface: - self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( - SSL_REFEREE_ADDRESS, - SSL_REFEREE_PORT, - lambda data: self.current_proto_unix_io.send_proto(Referee, data), - True, - referee_interface, + if change_referee_interface: + if self.receive_ssl_referee_proto is not None: + self.receive_ssl_referee_proto.close() + print("arun") + + print(referee_interface) + print(SSL_REFEREE_ADDRESS) + print(SSL_REFEREE_PORT) + print(type(Referee())) + print(type(referee_interface)) + print(type(str("arun"))) + + + self.receive_ssl_referee = tbots_cpp.SSLRefereeProtoListener( + SSL_REFEREE_ADDRESS, + SSL_REFEREE_PORT, + lambda data: print(data), + #lambda data: self.__forward_to_proto_unix_io(Referee, data), + True, + "lo", ) self.robots_connected_to_fullsystem = { @@ -193,6 +213,17 @@ def close_for_fullsystem(self) -> None: if self.receive_ssl_referee_proto: self.receive_ssl_referee_proto.close() + def __close_for_robot_communication(self) -> None: + if self.receive_robot_status: + self.receive_robot_status.close() + + if self.receive_robot_log: + self.receive_robot_log.close() + + if self.receive_robot_crash: + self.receive_robot_crash.close() + + def toggle_keyboard_estop(self) -> None: """ If keyboard estop is being used, toggles the estop state @@ -286,13 +317,13 @@ def __run_primitive_set(self) -> None: """ while self.running: - thunderbots_config = self.thunderbots_config_buffer.get(block=False, return_cached=False) - if thunderbots_config is not None: - print(f"[RobotCommunication] Received new ThunderbotsConfig") - network_config = thunderbots_config.ai_config.ai_control_config.network_config + network_config = self.network_config_buffer.get(block=False, return_cached=False) + if network_config is not None: + print(f"[RobotCommunication] Received new NetworkConfig") if self.is_setup_for_fullsystem: self.setup_for_fullsystem(referee_interface=network_config.referee_interface, vision_interface=network_config.vision_interface) + self.__close_for_robot_communication() self.__setup_for_robot_communication( robot_interface=network_config.robot_status_interface ) diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 891c7d38f7..176a8febaf 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -204,6 +204,7 @@ def on_change_callback( attr: Any, value: Any, updated_proto: ThunderbotsConfig ) -> None: proto_unix_io.send_proto(ThunderbotsConfig, updated_proto) + proto_unix_io.send_proto(NetworkConfig, updated_proto.ai_config.ai_control_config.network_config) return ProtoConfigurationWidget(config, on_change_callback) From b83c1528c5e3da9c95142f556183e9e18a12fb90 Mon Sep 17 00:00:00 2001 From: arun Date: Sat, 8 Jun 2024 03:25:03 -0700 Subject: [PATCH 10/50] should clean this up --- .../networking/udp/proto_udp_listener.hpp | 3 +- .../udp/threaded_proto_udp_listener.hpp | 1 + src/software/python_bindings.cpp | 14 +++- .../thunderscope/robot_communication.py | 73 +++++++++++-------- 4 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index afa5b2c309..a72c92fd02 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -222,8 +222,9 @@ void ProtoUdpListener::setupMulticast(const boost::asio::ip::addr std::string interface_ip; if (!getLocalIp(listen_interface, interface_ip)) { - LOG(FATAL) << "Could not find the local ip address for the given interface: " + LOG(WARNING) << "Could not find the local ip address for the given interface: " << listen_interface << std::endl; + return; } socket_.set_option(boost::asio::ip::multicast::join_group( ip_address.to_v4(), boost::asio::ip::address::from_string(interface_ip).to_v4())); diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 5ae221c225..5a776ef4aa 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -82,6 +82,7 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( template ThreadedProtoUdpListener::~ThreadedProtoUdpListener() { + close(); } diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 50d789f2e9..33a13a739d 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -94,8 +94,20 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init&, bool, std::string>()) + .def(py::init([](const std::string& ip, unsigned short port, + const std::function& callback, bool multicast, + const std::string& interface) { + return std::make_shared(ip, port, callback, multicast, interface); + })) .def("close", &Class::close); + + std::stringstream ss; + ss << "create" << pyclass_name; + m.def(ss.str().c_str(), [](const std::string& ip, unsigned short port, + const std::function& callback, + bool multicast, const std::string& interface) { + return std::make_shared(ip, port, callback, multicast, interface); + }); } /** diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 63f21b3602..fc22f6b30e 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -27,6 +27,7 @@ def __init__( estop_baudrate: int = 115200, enable_radio: bool = False, ): + self.i = 0 """Initialize the communication with the robots :param current_proto_unix_io: the current proto unix io object @@ -121,38 +122,47 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: print(change_referee_interface) print(change_vision_interface) - if change_vision_interface: - if self.receive_ssl_wrapper is not None: - self.receive_ssl_wrapper.close() - self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( - SSL_VISION_ADDRESS, - SSL_VISION_PORT, - lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), - True, - vision_interface, - ) - - if change_referee_interface: - if self.receive_ssl_referee_proto is not None: - self.receive_ssl_referee_proto.close() - print("arun") - - print(referee_interface) - print(SSL_REFEREE_ADDRESS) - print(SSL_REFEREE_PORT) - print(type(Referee())) - print(type(referee_interface)) - print(type(str("arun"))) - - - self.receive_ssl_referee = tbots_cpp.SSLRefereeProtoListener( - SSL_REFEREE_ADDRESS, - SSL_REFEREE_PORT, - lambda data: print(data), - #lambda data: self.__forward_to_proto_unix_io(Referee, data), + if change_vision_interface or change_referee_interface: + my_str_as_bytes = str.encode(vision_interface) + self.referee_interface = referee_interface + assert(isinstance(referee_interface, str)) + interfaces = ["lo", "wlp3s0", referee_interface, vision_interface] + print(interfaces) + vi = str(vision_interface) + v2 = str(vi) + try : + self.receive_ssl_wrapper = tbots_cpp.createSSLWrapperPacketProtoListener( + SSL_VISION_ADDRESS, + SSL_VISION_PORT, + lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), True, - "lo", - ) + v2 + ) + except RuntimeError: + # print stacktrace + import traceback + traceback.print_exc() + + self.i = (self.i + 1) % len(interfaces) + + print(repr(referee_interface)) + print(repr("wlp3s0")) + my_str_as_bytes = str.encode(referee_interface) + print(my_str_as_bytes) + self.vision_interface = vision_interface + try: + self.receive_ssl_referee_proto = tbots_cpp.createSSLRefereeProtoListener( + SSL_REFEREE_ADDRESS, + SSL_REFEREE_PORT, + lambda data: self.__forward_to_proto_unix_io(Referee, data), + True, + v2 + ) + except RuntimeError: + # print stacktrace + import traceback + traceback.print_exc() + print(v2) self.robots_connected_to_fullsystem = { robot_id for robot_id in range(MAX_ROBOT_IDS_PER_SIDE) @@ -323,7 +333,6 @@ def __run_primitive_set(self) -> None: if self.is_setup_for_fullsystem: self.setup_for_fullsystem(referee_interface=network_config.referee_interface, vision_interface=network_config.vision_interface) - self.__close_for_robot_communication() self.__setup_for_robot_communication( robot_interface=network_config.robot_status_interface ) From 50ba57c11deefddfa97a2079fc3cc4b512e350af Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 11 Jun 2024 23:39:10 -0700 Subject: [PATCH 11/50] wip --- .../networking/udp/proto_udp_listener.hpp | 40 ++++++++++++------- .../udp/threaded_proto_udp_listener.hpp | 15 ++++--- src/software/python_bindings.cpp | 15 +------ 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index a72c92fd02..e9d44e41c4 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -34,7 +34,7 @@ class ProtoUdpListener ProtoUdpListener(boost::asio::io_service& io_service, const std::string& ip_address, unsigned short port, const std::string& listen_interface, std::function receive_callback, - bool multicast); + bool multicast, std::optional& error); /** * Creates an ProtoUdpListener that will listen for ReceiveProtoT packets from @@ -48,7 +48,8 @@ class ProtoUdpListener * from the network */ ProtoUdpListener(boost::asio::io_service& io_service, unsigned short port, - std::function receive_callback); + std::function receive_callback, + std::optional& error); /** * Closes the socket associated to the UDP listener */ @@ -88,13 +89,15 @@ class ProtoUdpListener // Whether or not the listener is running bool running_ = true; + + std::optional error_; }; template ProtoUdpListener::ProtoUdpListener( boost::asio::io_service& io_service, const std::string& ip_address, const unsigned short port, const std::string& listen_interface, - std::function receive_callback, bool multicast) + std::function receive_callback, bool multicast, std::optional& error) : socket_(io_service), receive_callback(receive_callback) { boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); @@ -111,17 +114,26 @@ ProtoUdpListener::ProtoUdpListener( } catch (const boost::exception& ex) { - LOG(FATAL) << "UdpListener: There was an issue binding the socket to " + std::stringstream ss; + ss << "UdpListener: There was an issue binding the socket to " "the listen_endpoint when trying to connect to the " "address. This may be due to another instance of the " "UdpListener running and using the port already. " "(ip = " - << ip_address << ", port = " << port << ")" << std::endl; + << ip_address << ", port = " << port << ")"; + error_ = ss.str(); + error = error_; + return; } if (multicast) { setupMulticast(boost_ip, listen_interface); + if (error_) + { + error = error_; + return; + } } startListen(); @@ -130,7 +142,7 @@ ProtoUdpListener::ProtoUdpListener( template ProtoUdpListener::ProtoUdpListener( boost::asio::io_service& io_service, const unsigned short port, - std::function receive_callback) + std::function receive_callback, std::optional& error) : socket_(io_service), receive_callback(receive_callback) { boost::asio::ip::udp::endpoint listen_endpoint(boost::asio::ip::udp::v6(), port); @@ -143,12 +155,15 @@ ProtoUdpListener::ProtoUdpListener( } catch (const boost::exception& ex) { - LOG(FATAL) << "UdpListener: There was an issue binding the socket to " + std::stringstream ss; + ss << "UdpListener: There was an issue binding the socket to " "the listen_endpoint when trying to connect to the " "address. This may be due to another instance of the " "UdpListener running and using the port already. " "(port = " - << port << ")" << std::endl; + << port << ")"; + error = ss.str(); + return; } startListen(); @@ -173,7 +188,6 @@ void ProtoUdpListener::handleDataReception( { if (!running_) { - LOG(WARNING) << "arun returning"; return; } @@ -182,11 +196,9 @@ void ProtoUdpListener::handleDataReception( auto packet_data = ReceiveProtoT(); packet_data.ParseFromArray(raw_received_data_.data(), static_cast(num_bytes_received)); - LOG(WARNING) << "calling receive callback"; receive_callback(packet_data); // Once we've handled the data, start listening again startListen(); - LOG(WARNING) << "starting to listen"; } else { @@ -197,7 +209,6 @@ void ProtoUdpListener::handleDataReception( if (!running_) { - LOG(WARNING) << "arun returning"; return; } // Start listening again to receive the next data @@ -222,8 +233,10 @@ void ProtoUdpListener::setupMulticast(const boost::asio::ip::addr std::string interface_ip; if (!getLocalIp(listen_interface, interface_ip)) { - LOG(WARNING) << "Could not find the local ip address for the given interface: " + std::stringstream ss; + ss << "Could not find the local ip address for the given interface: " << listen_interface << std::endl; + error_ = ss.str(); return; } socket_.set_option(boost::asio::ip::multicast::join_group( @@ -241,7 +254,6 @@ ProtoUdpListener::~ProtoUdpListener() template void ProtoUdpListener::close() { - LOG(WARNING) << "closing socket"; running_ = false; // Shutdown both send and receive on the socket diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 5a776ef4aa..54c135669b 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -25,7 +25,7 @@ class ThreadedProtoUdpListener */ ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, std::function receive_callback, - bool multicast, const std::string& interface); + bool multicast, const std::string& interface, std::optional& error = std::nullopt); /** * Creates a ThreadedProtoUdpListener that will listen for ReceiveProtoT packets @@ -38,7 +38,8 @@ class ThreadedProtoUdpListener * from the network */ ThreadedProtoUdpListener(unsigned short port, const std::string& interface, - std::function receive_callback); + std::function receive_callback, + std::optional& error = std::nullopt); /** * Closes the socket and stops the IO service thread @@ -61,9 +62,10 @@ class ThreadedProtoUdpListener template ThreadedProtoUdpListener::ThreadedProtoUdpListener( const std::string& ip_address, const unsigned short port, - std::function receive_callback, bool multicast, const std::string& interface) + std::function receive_callback, bool multicast, const std::string& interface, + std::optional& error) : io_service(), - udp_listener(io_service, ip_address, port, interface, receive_callback, multicast) + udp_listener(io_service, ip_address, port, interface, receive_callback, multicast, error) { std::cout << "arun" << std::endl; // start the thread to run the io_service in the background @@ -72,8 +74,9 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const unsigned short port, const std::string& interface, std::function receive_callback) - : io_service(), udp_listener(io_service, port, interface, receive_callback) + const unsigned short port, const std::string& interface, std::function receive_callback, + std::optional& error) + : io_service(), udp_listener(io_service, port, interface, receive_callback, error) { // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 33a13a739d..973f518525 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -94,20 +94,9 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init([](const std::string& ip, unsigned short port, - const std::function& callback, bool multicast, - const std::string& interface) { - return std::make_shared(ip, port, callback, multicast, interface); - })) + .def(py::init&, bool, const std::string&, + std::optional&>()) .def("close", &Class::close); - - std::stringstream ss; - ss << "create" << pyclass_name; - m.def(ss.str().c_str(), [](const std::string& ip, unsigned short port, - const std::function& callback, - bool multicast, const std::string& interface) { - return std::make_shared(ip, port, callback, multicast, interface); - }); } /** From def143a66c578d15434c651464980a861bc7e8b7 Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 12 Jun 2024 23:23:51 -0700 Subject: [PATCH 12/50] various thunderscope improvements --- .../penalty_kick/penalty_kick_play_test.cpp | 2 +- .../jetson_nano/services/network/network.cpp | 7 +- src/software/network_log_listener_main.cpp | 7 +- .../networking/udp/proto_udp_listener.hpp | 7 - .../udp/threaded_proto_udp_listener.hpp | 9 +- .../udp/threaded_proto_udp_sender.hpp | 4 +- src/software/python_bindings.cpp | 5 +- src/software/thunderscope/BUILD | 1 + src/software/thunderscope/requirements.txt | 1 + .../thunderscope/robot_communication.py | 141 +++++++++--------- .../thunderscope/thunderscope_main.py | 2 - 11 files changed, 96 insertions(+), 90 deletions(-) diff --git a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp index 2058dd3231..8a2cad34dc 100644 --- a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp +++ b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp @@ -66,7 +66,7 @@ TEST_F(PenaltyKickPlayTest, test_penalty_kick_setup) } // TODO (#3106): Re-enable test once robot 1 and 2 tactic assignment do not oscillate -TEST_F(PenaltyKickPlayTest, DISABLED_test_penalty_kick_take) +TEST_F(PenaltyKickPlayTest, test_penalty_kick_take) { Vector behind_ball_direction = (field.friendlyPenaltyMark() - field.enemyGoalCenter()).normalize(); diff --git a/src/software/jetson_nano/services/network/network.cpp b/src/software/jetson_nano/services/network/network.cpp index bde99504c7..cbb9f6d9eb 100644 --- a/src/software/jetson_nano/services/network/network.cpp +++ b/src/software/jetson_nano/services/network/network.cpp @@ -8,10 +8,15 @@ NetworkService::NetworkService(const std::string& ip_address, sender = std::make_unique>( ip_address, robot_status_sender_port, interface, multicast); + std::optional error; udp_listener_primitive_set = std::make_unique>( ip_address, primitive_listener_port, interface, - boost::bind(&NetworkService::primitiveSetCallback, this, _1), multicast); + boost::bind(&NetworkService::primitiveSetCallback, this, _1), multicast, error); + if (error) + { + LOG(FATAL) << *error; + } radio_listener_primitive_set = std::make_unique>( diff --git a/src/software/network_log_listener_main.cpp b/src/software/network_log_listener_main.cpp index ec8e579cb1..435db2deea 100644 --- a/src/software/network_log_listener_main.cpp +++ b/src/software/network_log_listener_main.cpp @@ -93,9 +93,14 @@ int main(int argc, char **argv) logFromNetworking(log); }; + std::optional error; auto log_input = std::make_unique>( std::string(ROBOT_MULTICAST_CHANNELS.at(args.channel)), - ROBOT_LOGS_PORT, args.interface, robot_log_callback, true); + ROBOT_LOGS_PORT, args.interface, robot_log_callback, true, error); + if (error) + { + LOG(FATAL) << *error; + } LOG(INFO) << "Network logger listening on channel " diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index e9d44e41c4..51c1033ccb 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -265,13 +265,6 @@ void ProtoUdpListener::close() << "An unknown network error occurred when attempting to shutdown UDP socket for " << TYPENAME(ReceiveProtoT) << ". The boost system error is: " << error_code << ": " << error_code.message() << std::endl; - LOG(WARNING) << "arun"; - } - - LOG(WARNING) << boost::asio::error::not_connected; - if (error_code == boost::asio::error::not_connected) - { - return; } socket_.close(error_code); diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 54c135669b..d458744fec 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -23,9 +23,9 @@ class ThreadedProtoUdpListener * from the network * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, + ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, std::function receive_callback, - bool multicast, const std::string& interface, std::optional& error = std::nullopt); + bool multicast, std::optional& error = std::nullopt); /** * Creates a ThreadedProtoUdpListener that will listen for ReceiveProtoT packets @@ -61,13 +61,12 @@ class ThreadedProtoUdpListener template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const std::string& ip_address, const unsigned short port, - std::function receive_callback, bool multicast, const std::string& interface, + const std::string& ip_address, const unsigned short port, const std::string& interface, + std::function receive_callback, bool multicast, std::optional& error) : io_service(), udp_listener(io_service, ip_address, port, interface, receive_callback, multicast, error) { - std::cout << "arun" << std::endl; // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); } diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index c3388510fa..b92288696c 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -21,8 +21,8 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * @param port The port to send SendProto data on * @param multicast If true, joins the multicast group of given ip_address */ - ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, - bool multicast, const std::string& interface="lo") + ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, + bool multicast) : ThreadedUdpSender(ip_address, port, interface, multicast) { } diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 973f518525..2c26395880 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -59,8 +59,7 @@ void declareThreadedProtoUdpSender(py::module& m, std::string name) std::string pyclass_name = name + "ProtoUdpSender"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init()) - .def(py::init()) + .def(py::init()) .def("send_proto", &Class::sendProto); } @@ -94,7 +93,7 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init&, bool, const std::string&, + .def(py::init&, bool, std::optional&>()) .def("close", &Class::close); } diff --git a/src/software/thunderscope/BUILD b/src/software/thunderscope/BUILD index 57ee02d06e..318ff1d8f7 100644 --- a/src/software/thunderscope/BUILD +++ b/src/software/thunderscope/BUILD @@ -156,6 +156,7 @@ py_library( ], deps = [ "//software/thunderscope:constants", + requirement("colorama"), ], ) diff --git a/src/software/thunderscope/requirements.txt b/src/software/thunderscope/requirements.txt index 16d1ee1ab1..99f50fc355 100644 --- a/src/software/thunderscope/requirements.txt +++ b/src/software/thunderscope/requirements.txt @@ -1,3 +1,4 @@ +colorama==0.4.6 netifaces==0.11.0 numpy==1.24.4 pyqtgraph==0.13.3 diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index fc22f6b30e..ca024e294f 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -7,12 +7,15 @@ from proto.import_all_protos import * from pyqtgraph.Qt import QtCore from software.thunderscope.proto_unix_io import ProtoUnixIO +from colorama import Fore, Style from typing import Type import threading import time import os from google.protobuf.message import Message +DISCONNECTED = "DISCONNECTED" + class RobotCommunication(object): @@ -27,7 +30,6 @@ def __init__( estop_baudrate: int = 115200, enable_radio: bool = False, ): - self.i = 0 """Initialize the communication with the robots :param current_proto_unix_io: the current proto unix io object @@ -80,7 +82,9 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - self.network_config = NetworkConfig() + self.robot_status_interface = DISCONNECTED + self.vision_interface = DISCONNECTED + self.referee_interface = DISCONNECTED self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer @@ -115,93 +119,85 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default """ - change_referee_interface = (referee_interface != self.network_config.referee_interface) - change_vision_interface = (vision_interface != self.network_config.vision_interface) - print(referee_interface) - print(vision_interface) - print(change_referee_interface) - print(change_vision_interface) - - if change_vision_interface or change_referee_interface: - my_str_as_bytes = str.encode(vision_interface) - self.referee_interface = referee_interface - assert(isinstance(referee_interface, str)) - interfaces = ["lo", "wlp3s0", referee_interface, vision_interface] - print(interfaces) - vi = str(vision_interface) - v2 = str(vi) - try : - self.receive_ssl_wrapper = tbots_cpp.createSSLWrapperPacketProtoListener( - SSL_VISION_ADDRESS, - SSL_VISION_PORT, - lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), - True, - v2 - ) - except RuntimeError: - # print stacktrace - import traceback - traceback.print_exc() - - self.i = (self.i + 1) % len(interfaces) - - print(repr(referee_interface)) - print(repr("wlp3s0")) - my_str_as_bytes = str.encode(referee_interface) - print(my_str_as_bytes) - self.vision_interface = vision_interface - try: - self.receive_ssl_referee_proto = tbots_cpp.createSSLRefereeProtoListener( - SSL_REFEREE_ADDRESS, - SSL_REFEREE_PORT, - lambda data: self.__forward_to_proto_unix_io(Referee, data), - True, - v2 - ) - except RuntimeError: - # print stacktrace - import traceback - traceback.print_exc() - print(v2) + change_referee_interface = (referee_interface != self.referee_interface) and (referee_interface != DISCONNECTED) + change_vision_interface = (vision_interface != self.vision_interface) and (vision_interface != DISCONNECTED) + + error = None + if change_vision_interface: + + self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( + SSL_VISION_ADDRESS, + SSL_VISION_PORT, + vision_interface, + lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), + True, + error, + ) + + if error: + print(f"Error setting up vision interface: {error}") + return - self.robots_connected_to_fullsystem = { - robot_id for robot_id in range(MAX_ROBOT_IDS_PER_SIDE) - } + self.vision_interface = vision_interface if not error else DISCONNECTED + + if change_referee_interface: + self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( + SSL_REFEREE_ADDRESS, + SSL_REFEREE_PORT, + referee_interface, + lambda data: self.__forward_to_proto_unix_io(Referee, data), + True, + error, + ) - self.network_config.referee_interface = referee_interface - self.network_config.vision_interface = vision_interface - self.is_setup_for_fullsystem = True + if error: + print(f"Error setting up referee interface: {error}") + return - print(f"[RobotCommunication] Connected to referee on {referee_interface} and vision on {vision_interface}") + self.referee_interface = referee_interface if not error else DISCONNECTED + if not self.is_setup_for_fullsystem: + self.robots_connected_to_fullsystem = { + robot_id for robot_id in range(MAX_ROBOT_IDS_PER_SIDE) + } - def __setup_for_robot_communication(self, robot_interface: str = "lo") -> None: - if robot_interface == self.network_config.robot_status_interface: + self.is_setup_for_fullsystem = True + + self.__print_current_network_config() + + + def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> None: + if robot_status_interface == self.robot_status_interface or robot_status_interface == DISCONNECTED: return + error = None + # Create the multicast listeners self.receive_robot_status = tbots_cpp.RobotStatusProtoListener( self.multicast_channel, ROBOT_STATUS_PORT, + robot_status_interface, lambda data: self.__forward_to_proto_unix_io(RobotStatus, data), True, - robot_interface, + error ) self.receive_robot_log = tbots_cpp.RobotLogProtoListener( self.multicast_channel, ROBOT_LOGS_PORT, + robot_status_interface, lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, - robot_interface, + error ) self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( self.multicast_channel, ROBOT_CRASH_PORT, + robot_status_interface, lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), True, - robot_interface, + error ) # Create multicast senders @@ -209,12 +205,14 @@ def __setup_for_robot_communication(self, robot_interface: str = "lo") -> None: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( - self.multicast_channel, PRIMITIVE_PORT, True, robot_interface + self.multicast_channel, PRIMITIVE_PORT, robot_status_interface, True ) - self.network_config.robot_status_interface = robot_interface + if error: + print(f"Error setting up robot status interface: {error}") - print(f"[RobotCommunication] Robot interface set to {robot_interface}") + self.robot_status_interface = robot_status_interface if not error else DISCONNECTED + self.__print_current_network_config() def close_for_fullsystem(self) -> None: if self.receive_ssl_wrapper: @@ -334,7 +332,7 @@ def __run_primitive_set(self) -> None: self.setup_for_fullsystem(referee_interface=network_config.referee_interface, vision_interface=network_config.vision_interface) self.__setup_for_robot_communication( - robot_interface=network_config.robot_status_interface + robot_status_interface=network_config.robot_status_interface ) # total primitives for all robots @@ -409,8 +407,6 @@ def __enter__(self) -> "self": for RobotStatus, RobotLogs, and RobotCrash msgs, and multicast sender for PrimitiveSet """ - self.__setup_for_robot_communication() - self.running = True self.send_estop_state_thread.start() @@ -431,3 +427,12 @@ def __exit__(self, type, value, traceback) -> None: self.receive_robot_log.close() self.receive_robot_status.close() self.run_primitive_set_thread.join() + + def __print_current_network_config(self) -> None: + def output_string(comm_name: str, status: str) -> str: + colour = Fore.RED if status == DISCONNECTED else Fore.GREEN + return f"{comm_name} {colour}{status} {Style.RESET_ALL}" + + print(output_string("Robot Status\t", self.robot_status_interface)) + print(output_string("Vision\t\t", self.vision_interface)) + print(output_string("Referee\t\t", self.referee_interface)) diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 1846d3bbd7..dd604582b8 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -436,8 +436,6 @@ def __ticker(tick_rate_ms: int) -> None: log_path=args.yellow_full_system_runtime_dir, time_provider=autoref.time_provider if args.enable_autoref else None, ) as yellow_logger: - print("arun: initialized everything") - tscope.register_refresh_function(gamecontroller.refresh) autoref_proto_unix_io = ProtoUnixIO() From e70630cec59c4754282ad7efdc46228deb4cccb4 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 13 Jun 2024 23:40:52 -0700 Subject: [PATCH 13/50] add some unit tests --- src/software/networking/udp/BUILD | 22 ++++++++++ .../networking/udp/network_utils_test.cpp | 42 +++++++++++++++++++ .../udp/threaded_proto_udp_listener_test.cpp | 10 +++++ 3 files changed, 74 insertions(+) create mode 100644 src/software/networking/udp/network_utils_test.cpp create mode 100644 src/software/networking/udp/threaded_proto_udp_listener_test.cpp diff --git a/src/software/networking/udp/BUILD b/src/software/networking/udp/BUILD index 145386a1b1..f8b324c632 100644 --- a/src/software/networking/udp/BUILD +++ b/src/software/networking/udp/BUILD @@ -10,6 +10,17 @@ cc_library( ], ) +cc_test( + name = "network_utils_test", + srcs = [ + "network_utils_test.cpp", + ], + deps = [ + ":network_utils", + "//shared/test_util:tbots_gtest_main", + ], +) + cc_library( name = "proto_udp_listener", hdrs = [ @@ -49,6 +60,17 @@ cc_library( ], ) +cc_test( + name = "threaded_proto_udp_listener_test", + srcs = [ + "threaded_proto_udp_listener_test.cpp", + ], + deps = [ + ":threaded_proto_udp_listener", + "//shared/test_util:tbots_gtest_main", + ], +) + cc_library( name = "threaded_proto_udp_sender", hdrs = [ diff --git a/src/software/networking/udp/network_utils_test.cpp b/src/software/networking/udp/network_utils_test.cpp new file mode 100644 index 0000000000..1079355636 --- /dev/null +++ b/src/software/networking/udp/network_utils_test.cpp @@ -0,0 +1,42 @@ +#include + +#include "software/networking/udp/network_utils.h" + +TEST(NetworkUtilsTest, getLocalIpValidInterface) +{ + std::string interface = "lo"; + std::string ip_address; + EXPECT_TRUE(getLocalIp(interface, ip_address, true)); + EXPECT_EQ(ip_address, "127.0.0.1"); +} + +TEST(NetworkUtilsTest, getLocalIpInvalidInterface) +{ + std::string interface = "interfaceymcinterfaceface"; + std::string ip_address; + EXPECT_FALSE(getLocalIp(interface, ip_address, true)); +} + +TEST(NetworkUtilsTest, isIpv6Valid) +{ + std::string ip_address = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + EXPECT_TRUE(isIpv6(ip_address)); +} + +TEST(NetworkUtilsTest, isIpv6ForIpv4) +{ + std::string ip_address = "127.0.0.1"; + EXPECT_FALSE(isIpv6(ip_address)); +} + +TEST(NetworkUtilsTest, isIpv6ForIpv4Mapped) +{ + std::string ip_address = "::ffff:0:0"; + EXPECT_TRUE(isIpv6(ip_address)); +} + +TEST(NetworkUtilsTest, isIpv6ForLoopback) +{ + std::string ip_address = "::1"; + EXPECT_TRUE(isIpv6(ip_address)); +} diff --git a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp new file mode 100644 index 0000000000..8127caa087 --- /dev/null +++ b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp @@ -0,0 +1,10 @@ +#include "software/networking/udp/threaded_proto_udp_listener.hpp" + +#include + +TEST(ThreadedProtoUdpListener, error_finding_ip_address) +{ + std::optional error; + ThreadedProtoUdpListener("192.168.0.2", 40000, "interfacemcinterfaceface", [](const auto&){}, true, error); + EXPECT_TRUE(error.has_value()); +} From db86590ffe8005846e50c589d58716ae42605148 Mon Sep 17 00:00:00 2001 From: arun Date: Sat, 15 Jun 2024 02:37:02 -0700 Subject: [PATCH 14/50] wip --- src/software/networking/udp/proto_udp_listener.hpp | 1 + .../udp/threaded_proto_udp_listener_test.cpp | 12 ++++++++++-- src/software/python_bindings.cpp | 7 +++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 51c1033ccb..ba7b7b93ac 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -237,6 +237,7 @@ void ProtoUdpListener::setupMulticast(const boost::asio::ip::addr ss << "Could not find the local ip address for the given interface: " << listen_interface << std::endl; error_ = ss.str(); + std::cerr << error_.value() << std::endl; return; } socket_.set_option(boost::asio::ip::multicast::join_group( diff --git a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp index 8127caa087..2acb902173 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp @@ -1,10 +1,18 @@ +#include "google/protobuf/empty.pb.h" #include "software/networking/udp/threaded_proto_udp_listener.hpp" #include -TEST(ThreadedProtoUdpListener, error_finding_ip_address) +TEST(ThreadedProtoUdpListenerTest, error_finding_ip_address) { std::optional error; - ThreadedProtoUdpListener("192.168.0.2", 40000, "interfacemcinterfaceface", [](const auto&){}, true, error); + ThreadedProtoUdpListener("224.5.23.1", 40000, "interfacemcinterfaceface", [](const auto&){}, true, error); + EXPECT_TRUE(error.has_value()); +} + +TEST(ThreadedProtoUdpListenerTest, error_creating_socket) +{ + std::optional error; + ThreadedProtoUdpListener("224.5.23.1", 1023, "lo", [](const auto&){}, true, error); EXPECT_TRUE(error.has_value()); } diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index 2c26395880..cb107cf878 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -93,8 +93,11 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init&, bool, - std::optional&>()) + .def(py::init([](const std::string& ip_address, unsigned short port, const std::string& interface, + const std::function& callback, bool multicast, std::optional& error) + { + return std::make_shared(ip_address, port, interface, callback, multicast, error); + })) .def("close", &Class::close); } From e5f75479c4fc94d6b75daf96fd4bb3a878b6aff2 Mon Sep 17 00:00:00 2001 From: arun Date: Sun, 16 Jun 2024 20:11:01 -0700 Subject: [PATCH 15/50] wip --- src/software/logger/BUILD | 1 + src/software/python_bindings.cpp | 10 ++++ .../common/proto_configuration_widget.py | 7 +++ .../thunderscope/robot_communication.py | 52 ++++++++----------- .../thunderscope/thunderscope_config.py | 10 ++++ .../thunderscope/widget_setup_functions.py | 18 +++++++ 6 files changed, 69 insertions(+), 29 deletions(-) diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index d369d7f735..5ccd7bbab5 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -28,6 +28,7 @@ cc_library( "@g3log", "@g3sinks", ], + linkopts = ["-lstdc++fs"], ) cc_library( diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index cb107cf878..c3cabaf28e 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -99,6 +99,16 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) return std::make_shared(ip_address, port, interface, callback, multicast, error); })) .def("close", &Class::close); + + std::stringstream ss; + ss << "create" << pyclass_name; + m.def(ss.str().c_str(), [](const std::string& ip_address, unsigned short port, + const std::string& interface, + const std::function& callback, bool multicast) { + std::optional error; + std::shared_ptr listener = std::make_shared(ip_address, port, interface, callback, multicast, error); + return std::make_tuple(listener, error); + }); } /** diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 134a35e82c..113ccb48ed 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -64,6 +64,9 @@ def __init__( layout.addWidget(self.search_query) layout.addWidget(self.param_tree) + self.first_shot_thread = Thread(target=self.__first_shot, daemon=True) + self.first_shot_thread.start() + def __handle_search_query_changed(self, search_term): """Given a new search term, reconfigure the parameter tree with parameters that match the term. @@ -158,3 +161,7 @@ def build_proto(self, message, current_attr=None): exec(f"{current_attr}.{key} = {value}") else: self.build_proto(value, f"{current_attr}.{key}") + + def __first_shot(self): + time.sleep(ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S) + self.on_change_callback(None, None, self.proto_to_configure) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index ca024e294f..ec8df43322 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -82,9 +82,9 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - self.robot_status_interface = DISCONNECTED - self.vision_interface = DISCONNECTED - self.referee_interface = DISCONNECTED + self.current_network_config = NetworkConfig(robot_status_interface=DISCONNECTED, + vision_interface=DISCONNECTED, + referee_interface=DISCONNECTED) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer @@ -115,46 +115,43 @@ def __init__( except Exception: raise Exception(f"Invalid Estop found at location {self.estop_path}") - def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: str = "lo") -> None: + def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_interface: str = DISCONNECTED) -> None: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default """ - change_referee_interface = (referee_interface != self.referee_interface) and (referee_interface != DISCONNECTED) - change_vision_interface = (vision_interface != self.vision_interface) and (vision_interface != DISCONNECTED) + change_referee_interface = (referee_interface != self.current_network_config.referee_interface)\ + and (referee_interface != DISCONNECTED) + change_vision_interface = (vision_interface != self.current_network_config.vision_interface)\ + and (vision_interface != DISCONNECTED) - error = None if change_vision_interface: - self.receive_ssl_wrapper = tbots_cpp.SSLWrapperPacketProtoListener( + self.receive_ssl_wrapper, error = tbots_cpp.createSSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, vision_interface, lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), True, - error, ) if error: print(f"Error setting up vision interface: {error}") - return - self.vision_interface = vision_interface if not error else DISCONNECTED + self.current_network_config.vision_interface = vision_interface if not error else DISCONNECTED if change_referee_interface: - self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( + self.receive_ssl_referee_proto, error = tbots_cpp.createSSLRefereeProtoListener( SSL_REFEREE_ADDRESS, SSL_REFEREE_PORT, referee_interface, lambda data: self.__forward_to_proto_unix_io(Referee, data), True, - error, ) if error: print(f"Error setting up referee interface: {error}") - return - self.referee_interface = referee_interface if not error else DISCONNECTED + self.current_network_config.referee_interface = referee_interface if not error else DISCONNECTED if not self.is_setup_for_fullsystem: self.robots_connected_to_fullsystem = { @@ -167,37 +164,33 @@ def setup_for_fullsystem(self, referee_interface: str = "lo", vision_interface: def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> None: - if robot_status_interface == self.robot_status_interface or robot_status_interface == DISCONNECTED: + if robot_status_interface == self.current_network_config.robot_status_interface\ + or robot_status_interface == DISCONNECTED: return - error = None - # Create the multicast listeners - self.receive_robot_status = tbots_cpp.RobotStatusProtoListener( + self.receive_robot_status, error = tbots_cpp.createRobotStatusProtoListener( self.multicast_channel, ROBOT_STATUS_PORT, robot_status_interface, lambda data: self.__forward_to_proto_unix_io(RobotStatus, data), True, - error ) - self.receive_robot_log = tbots_cpp.RobotLogProtoListener( + self.receive_robot_log, error = tbots_cpp.createRobotLogProtoListener( self.multicast_channel, ROBOT_LOGS_PORT, robot_status_interface, lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, - error ) - self.receive_robot_crash = tbots_cpp.RobotCrashProtoListener( + self.receive_robot_crash, error = tbots_cpp.createRobotCrashProtoListener( self.multicast_channel, ROBOT_CRASH_PORT, robot_status_interface, lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), True, - error ) # Create multicast senders @@ -211,7 +204,7 @@ def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> if error: print(f"Error setting up robot status interface: {error}") - self.robot_status_interface = robot_status_interface if not error else DISCONNECTED + self.current_network_config.robot_status_interface = robot_status_interface if not error else DISCONNECTED self.__print_current_network_config() def close_for_fullsystem(self) -> None: @@ -325,7 +318,8 @@ def __run_primitive_set(self) -> None: """ while self.running: - network_config = self.network_config_buffer.get(block=False, return_cached=False) + network_config = self.network_config_buffer.get(block=False if self.send_primitive_set else True, + return_cached=False) if network_config is not None: print(f"[RobotCommunication] Received new NetworkConfig") if self.is_setup_for_fullsystem: @@ -433,6 +427,6 @@ def output_string(comm_name: str, status: str) -> str: colour = Fore.RED if status == DISCONNECTED else Fore.GREEN return f"{comm_name} {colour}{status} {Style.RESET_ALL}" - print(output_string("Robot Status\t", self.robot_status_interface)) - print(output_string("Vision\t\t", self.vision_interface)) - print(output_string("Referee\t\t", self.referee_interface)) + print(output_string("Robot Status\t", self.current_network_config.robot_status_interface)) + print(output_string("Vision\t\t", self.current_network_config.vision_interface)) + print(output_string("Referee\t\t", self.current_network_config.referee_interface)) diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 769facc474..91b203e571 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -253,6 +253,16 @@ def configure_base_diagnostics( :return: list of widget data for Diagnostics """ return [ + TScopeWidget( + name="Network Configuration", + widget=setup_network_config_widget( + **{ + "proto_unix_io": diagnostics_proto_unix_io, + } + ), + has_refresh_func=False, + stretch=WidgetStretchData(x=3), + ), TScopeWidget( name="Logs", widget=setup_log_widget(**{"proto_unix_io": diagnostics_proto_unix_io}), diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 176a8febaf..5915520c06 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -208,6 +208,24 @@ def on_change_callback( return ProtoConfigurationWidget(config, on_change_callback) +def setup_network_config_widget( + proto_unix_io: ProtoUnixIO +) -> ProtoConfigurationWidget: + """Setup the network configuration widget + + :param proto_unix_io: The proto unix io object + + :returns: The proto configuration widget + """ + config = NetworkConfig() + + def on_change_callback( + attr: Any, value: Any, updated_proto: NetworkConfig + ) -> None: + proto_unix_io.send_proto(NetworkConfig, updated_proto) + + return ProtoConfigurationWidget(config, on_change_callback) + def setup_log_widget(proto_unix_io: ProtoUnixIO) -> g3logWidget: """Setup the wiget that receives logs from full system From 059aa3f03f552692e8991650d9c6e18903d2fdf6 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 18 Jun 2024 22:21:35 -0700 Subject: [PATCH 16/50] wip --- .../jetson_nano/services/network/network.h | 1 + src/software/logger/plotjuggler_sink.h | 2 ++ src/software/networking/udp/network_utils.h | 11 ++++++++++ .../networking/udp/proto_udp_listener.hpp | 9 ++++++++ .../thunderscope/thunderscope_config.py | 21 ++++++++++--------- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/software/jetson_nano/services/network/network.h b/src/software/jetson_nano/services/network/network.h index 6b8c1d07c1..c2d79597ad 100644 --- a/src/software/jetson_nano/services/network/network.h +++ b/src/software/jetson_nano/services/network/network.h @@ -22,6 +22,7 @@ class NetworkService * @param ip_address The IP Address the service should connect to * @param primitive_listener_port The port to listen for primitive protos * @param robot_status_sender_port The port to send robot status + * @param interface the interface to listen and send on * @param multicast If true, then the provided IP address is a multicast address and * we should join the group */ diff --git a/src/software/logger/plotjuggler_sink.h b/src/software/logger/plotjuggler_sink.h index b7c25435ce..b6354d7f9a 100644 --- a/src/software/logger/plotjuggler_sink.h +++ b/src/software/logger/plotjuggler_sink.h @@ -16,6 +16,8 @@ class PlotJugglerSink public: /** * Creates a PlotJugglerSink that sends udp packets to the PlotJuggler server + * + * @param interface The interface to send Plotjuggler UDP packets on */ PlotJugglerSink(const std::string& interface="lo"); diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 077ba15846..157c9fe85a 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -3,6 +3,17 @@ #include #include +/** + * @brief Given an interface, get the IP address associated with that interface + * + * The modified ip_address is valid only if the function returns true + * + * @param interface The interface to get the IP address from + * @param ip_address A reference to the std::string that will store the IP address if found + * @param ipv4 If true, get the IPv4 address, otherwise get the IPv6 address + * + * @return true if the IP address was found, false otherwise + */ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); /** diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index ba7b7b93ac..d187fb33ce 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -136,6 +136,15 @@ ProtoUdpListener::ProtoUdpListener( } } + if (!socket_.is_open()) + { + std::stringstream ss; + ss << "UdpListener: The socket is not open after attempting to bind to the listen endpoint"; + error_ = ss.str(); + error = error_; + return; + } + startListen(); } diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 91b203e571..d8c2abddd3 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -253,16 +253,6 @@ def configure_base_diagnostics( :return: list of widget data for Diagnostics """ return [ - TScopeWidget( - name="Network Configuration", - widget=setup_network_config_widget( - **{ - "proto_unix_io": diagnostics_proto_unix_io, - } - ), - has_refresh_func=False, - stretch=WidgetStretchData(x=3), - ), TScopeWidget( name="Logs", widget=setup_log_widget(**{"proto_unix_io": diagnostics_proto_unix_io}), @@ -275,6 +265,17 @@ def configure_base_diagnostics( position="below", anchor="Logs", ), + TScopeWidget( + name="Network Configuration", + widget=setup_network_config_widget( + **{ + "proto_unix_io": diagnostics_proto_unix_io, + } + ), + position="below", + anchor="Error Log", + has_refresh_func=False, + ), TScopeWidget( name="Drive and Dribbler", widget=setup_drive_and_dribbler_widget( From c19d3037bf704f2b9d046a75fdd17885915bb4c8 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 18 Jun 2024 23:26:40 -0700 Subject: [PATCH 17/50] wip --- src/proto/parameters.proto | 1 + src/software/logger/plotjuggler_sink.cpp | 7 ++- src/software/logger/plotjuggler_sink.h | 3 ++ src/software/networking/udp/BUILD | 11 +++++ .../networking/udp/proto_udp_listener.hpp | 44 ++++++++++--------- .../udp/threaded_proto_udp_listener.hpp | 10 ++++- .../udp/threaded_proto_udp_listener_test.cpp | 12 ++++- .../udp/threaded_proto_udp_sender.hpp | 10 +++-- .../udp/threaded_proto_udp_sender_test.cpp | 19 ++++++++ .../networking/udp/threaded_udp_sender.cpp | 5 ++- .../networking/udp/threaded_udp_sender.h | 7 ++- src/software/networking/udp/udp_sender.cpp | 15 ++++--- src/software/networking/udp/udp_sender.h | 17 +++++-- 13 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 src/software/networking/udp/threaded_proto_udp_sender_test.cpp diff --git a/src/proto/parameters.proto b/src/proto/parameters.proto index 448e3be9c0..26e404b219 100644 --- a/src/proto/parameters.proto +++ b/src/proto/parameters.proto @@ -50,6 +50,7 @@ message AiControlConfig // Override the existing play with the Play enum provided required PlayName override_ai_play = 2 [default = UseAiSelection]; + // Interfaces for various network listeners required NetworkConfig network_config = 3; } diff --git a/src/software/logger/plotjuggler_sink.cpp b/src/software/logger/plotjuggler_sink.cpp index 99eb693d21..b0d2ea2505 100644 --- a/src/software/logger/plotjuggler_sink.cpp +++ b/src/software/logger/plotjuggler_sink.cpp @@ -1,4 +1,3 @@ - #include "software/logger/plotjuggler_sink.h" #include @@ -6,8 +5,12 @@ #include "shared/constants.h" PlotJugglerSink::PlotJugglerSink(const std::string& interface) - : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, interface, false) + : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, interface, false, error) { + if (error.has_value()) + { + std::cerr << "Error setting up UDP sender for PlotJugglerSink: " << error.value(); + } } void PlotJugglerSink::sendToPlotJuggler(g3::LogMessageMover log_entry) diff --git a/src/software/logger/plotjuggler_sink.h b/src/software/logger/plotjuggler_sink.h index b6354d7f9a..781c756dae 100644 --- a/src/software/logger/plotjuggler_sink.h +++ b/src/software/logger/plotjuggler_sink.h @@ -32,6 +32,9 @@ class PlotJugglerSink void sendToPlotJuggler(g3::LogMessageMover log_entry); private: + // Any error that occurs during the creation of the UDP sender will be stored here + std::optional error; + ThreadedUdpSender udp_sender; }; diff --git a/src/software/networking/udp/BUILD b/src/software/networking/udp/BUILD index f8b324c632..464a822e8a 100644 --- a/src/software/networking/udp/BUILD +++ b/src/software/networking/udp/BUILD @@ -81,6 +81,17 @@ cc_library( ], ) +cc_test( + name = "threaded_proto_udp_sender_test", + srcs = [ + "threaded_proto_udp_sender_test.cpp", + ], + deps = [ + ":threaded_proto_udp_sender", + "//shared/test_util:tbots_gtest_main", + ], +) + cc_library( name = "threaded_udp_sender", srcs = [ diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index d187fb33ce..23005862b8 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -22,14 +22,18 @@ class ProtoUdpListener * ReceiveProtoT packet received, the receive_callback will be called to perform any * operations desired by the caller * + * The caller must check that the error is not set before using the listener + * * @param io_service The io_service to use to service incoming ReceiveProtoT data * @param ip_address The ip address of on which to listen for the given ReceiveProtoT * packets (IPv4 in dotted decimal or IPv6 in hex string) example IPv4: 192.168.0.2 - * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 (the interface is specified after %) + * example IPv6: ff02::c3d0:42d2:bb8 * @param port The port on which to listen for ReceiveProtoT packets + * @param listen_interface The interface to listen on * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network * @param multicast If true, joins the multicast group of given ip_address + * @param error A user-provided optional string to store any error messages */ ProtoUdpListener(boost::asio::io_service& io_service, const std::string& ip_address, unsigned short port, const std::string& listen_interface, @@ -42,10 +46,13 @@ class ProtoUdpListener * received, the receive_callback will be called to perform any operations desired by * the caller * + * The caller must check that the error is not set before using the listener + * * @param io_service The io_service to use to service incoming ReceiveProtoT data * @param port The port on which to listen for ReceiveProtoT packets * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network + * @param error A user-provided optional string to store any error messages */ ProtoUdpListener(boost::asio::io_service& io_service, unsigned short port, std::function receive_callback, @@ -68,8 +75,17 @@ class ProtoUdpListener void handleDataReception(const boost::system::error_code& error, size_t num_bytes_received); + /** + * Sets up multicast for the given ip_address and listen_interface + * + * Any errors during setup will be stored in the error string + * + * @param ip_address The ip address of the multicast group to join + * @param listen_interface The interface to listen on + * @param error A user-provided optional string to store any error messages + */ void setupMulticast(const boost::asio::ip::address& ip_address, - const std::string& listen_interface); + const std::string& listen_interface, std::optional& error); /** * Start listening for data @@ -89,8 +105,6 @@ class ProtoUdpListener // Whether or not the listener is running bool running_ = true; - - std::optional error_; }; template @@ -121,30 +135,19 @@ ProtoUdpListener::ProtoUdpListener( "UdpListener running and using the port already. " "(ip = " << ip_address << ", port = " << port << ")"; - error_ = ss.str(); - error = error_; + error = ss.str(); return; } if (multicast) { - setupMulticast(boost_ip, listen_interface); - if (error_) + setupMulticast(boost_ip, listen_interface, error); + if (error) { - error = error_; return; } } - if (!socket_.is_open()) - { - std::stringstream ss; - ss << "UdpListener: The socket is not open after attempting to bind to the listen endpoint"; - error_ = ss.str(); - error = error_; - return; - } - startListen(); } @@ -235,7 +238,7 @@ void ProtoUdpListener::handleDataReception( template void ProtoUdpListener::setupMulticast(const boost::asio::ip::address& ip_address, - const std::string& listen_interface) + const std::string& listen_interface, std::optional& error) { if (ip_address.is_v4()) { @@ -245,8 +248,7 @@ void ProtoUdpListener::setupMulticast(const boost::asio::ip::addr std::stringstream ss; ss << "Could not find the local ip address for the given interface: " << listen_interface << std::endl; - error_ = ss.str(); - std::cerr << error_.value() << std::endl; + error = ss.str(); return; } socket_.set_option(boost::asio::ip::multicast::join_group( diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index d458744fec..120128e166 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -15,13 +15,17 @@ class ThreadedProtoUdpListener * ReceiveProtoT packet received, the receive_callback will be called to perform any * operations desired by the caller. * + * Any caller using this constructor should ensure that error is not set before using the listener. + * * @param ip_address The ip address on which to listen for the given ReceiveProtoT * packets (IPv4 in dotted decimal or IPv6 in hex string) example IPv4: 192.168.0.2 - * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 (the interface is specified after %) + * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 * @param port The port on which to listen for ReceiveProtoT packets + * @param interface The interface on which to listen for ReceiveProtoT packets * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network * @param multicast If true, joins the multicast group of given ip_address + * @param errror A user-provided optional string to store any error messages */ ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, std::function receive_callback, @@ -33,9 +37,13 @@ class ThreadedProtoUdpListener * packet received, the receive_callback will be called to perform any operations * desired by the caller. * + * Any caller using this constructor should ensure that error is not set before using the listener. + * * @param port The port on which to listen for ReceiveProtoT packets + * @param interface The interface on which to listen for ReceiveProtoT packets * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network + * @param error A user-provided optional string to store any error messages */ ThreadedProtoUdpListener(unsigned short port, const std::string& interface, std::function receive_callback, diff --git a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp index 2acb902173..6f27eea8d4 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp @@ -1,9 +1,10 @@ -#include "google/protobuf/empty.pb.h" #include "software/networking/udp/threaded_proto_udp_listener.hpp" +#include "google/protobuf/empty.pb.h" + #include -TEST(ThreadedProtoUdpListenerTest, error_finding_ip_address) +TEST(ThreadedProtoUdpListenerTest, error_finding_local_ip_address) { std::optional error; ThreadedProtoUdpListener("224.5.23.1", 40000, "interfacemcinterfaceface", [](const auto&){}, true, error); @@ -16,3 +17,10 @@ TEST(ThreadedProtoUdpListenerTest, error_creating_socket) ThreadedProtoUdpListener("224.5.23.1", 1023, "lo", [](const auto&){}, true, error); EXPECT_TRUE(error.has_value()); } + +TEST(ThreadedProtoUdpListenerTest, no_error_creating_socket) +{ + std::optional error; + ThreadedProtoUdpListener("224.5.23.0", 40000, "lo", [](const auto&){}, true, error); + EXPECT_FALSE(error.has_value()); +} diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index b92288696c..ccb578d55c 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -14,16 +14,20 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * Creates a UdpSender that sends the SendProto over the network on the * given address and port. * + * Any callers must check the error string to see if the initialization was successful before using the object. + * * @param ip_address The ip address to send data on * (IPv4 in dotted decimal or IPv6 in hex string) * example IPv4: 192.168.0.2 - * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 (the interface is specified after %) + * example IPv6: ff02::c3d0:42d2:bb8 * @param port The port to send SendProto data on + * @param interface The interface to send data on * @param multicast If true, joins the multicast group of given ip_address + * @param error An optional user-provided string that will be set to an error message if an error occurs */ ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, - bool multicast) - : ThreadedUdpSender(ip_address, port, interface, multicast) + bool multicast, std::optional& error = std::nullopt) + : ThreadedUdpSender(ip_address, port, interface, multicast, error) { } diff --git a/src/software/networking/udp/threaded_proto_udp_sender_test.cpp b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp new file mode 100644 index 0000000000..dee9b902a2 --- /dev/null +++ b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp @@ -0,0 +1,19 @@ +#include "software/networking/udp/threaded_proto_udp_sender.hpp" + +#include "google/protobuf/empty.pb.h" + +#include + +TEST(ThreadedProtoUdpSenderTest, error_finding_ip_address) +{ + std::optional error; + ThreadedProtoUdpSender("224.5.23.1", 40000, "interfacemcinterfaceface", true, error); + EXPECT_TRUE(error.has_value()); +} + +TEST(ThreadedProtoUdpSenderTest, no_error_creating_socket) +{ + std::optional error; + ThreadedProtoUdpSender("224.5.23.1", 40000, "lo", true, error); + EXPECT_FALSE(error.has_value()); +} diff --git a/src/software/networking/udp/threaded_udp_sender.cpp b/src/software/networking/udp/threaded_udp_sender.cpp index 16cc610df6..52c3b44ede 100644 --- a/src/software/networking/udp/threaded_udp_sender.cpp +++ b/src/software/networking/udp/threaded_udp_sender.cpp @@ -1,9 +1,10 @@ #include "software/networking/udp/threaded_udp_sender.h" ThreadedUdpSender::ThreadedUdpSender(const std::string& ip_address, - const unsigned short port, const std::string& interface, bool multicast) + const unsigned short port, const std::string& interface, bool multicast, + std::optional& error) : io_service(), - udp_sender(io_service, ip_address, port, interface, multicast), + udp_sender(io_service, ip_address, port, interface, multicast, error), io_service_thread([this]() { io_service.run(); }) { } diff --git a/src/software/networking/udp/threaded_udp_sender.h b/src/software/networking/udp/threaded_udp_sender.h index 11a1f9987f..2131573d40 100644 --- a/src/software/networking/udp/threaded_udp_sender.h +++ b/src/software/networking/udp/threaded_udp_sender.h @@ -13,14 +13,19 @@ class ThreadedUdpSender * Creates a UdpSender that sends the sendString over the network on the * given address and port. * + * All callers must check the error string to see if the initialization was successful before using the object. + * * @param ip_address The ip address to send data on * (IPv4 in dotted decimal or IPv6 in hex string) * example IPv4: 192.168.0.2 * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 (the interface is specified after %) * @param port The port to send sendString data on + * @param interface The interface to send data on * @param multicast If true, joins the multicast group of given ip_address + * @param error An optional user-provided string that will be set to an error message if an error occurs */ - ThreadedUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast); + ThreadedUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast, + std::optional& error); ~ThreadedUdpSender(); diff --git a/src/software/networking/udp/udp_sender.cpp b/src/software/networking/udp/udp_sender.cpp index b43884d3cb..2c6ede089c 100644 --- a/src/software/networking/udp/udp_sender.cpp +++ b/src/software/networking/udp/udp_sender.cpp @@ -5,7 +5,8 @@ #include UdpSender::UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - const unsigned short port, const std::string& interface, bool multicast) + const unsigned short port, const std::string& interface, bool multicast, + std::optional& error) : socket_(io_service) { boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); @@ -21,7 +22,7 @@ UdpSender::UdpSender(boost::asio::io_service& io_service, const std::string& ip_ if (multicast) { - setupMulticast(boost_ip, interface); + setupMulticast(boost_ip, interface, error); } } @@ -30,16 +31,20 @@ void UdpSender::sendString(const std::string& message) socket_.send_to(boost::asio::buffer(message, message.length()), receiver_endpoint); } -void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface) +void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface, std::optional& error) { if (ip_address.is_v4()) { std::string interface_ip; if (!getLocalIp(interface, interface_ip)) { - std::cerr << "UdpSender: Could not get the local IP address for the interface " + std::stringstream ss; + ss << "UdpSender: Could not get the local IP address for the interface " "specified. (interface = " - << interface << ")" << std::endl; + << interface << ")"; + error = ss.str(); + + return; } socket_.set_option(boost::asio::ip::multicast::join_group(ip_address.to_v4(), diff --git a/src/software/networking/udp/udp_sender.h b/src/software/networking/udp/udp_sender.h index 1e96047f6c..124170e2ca 100644 --- a/src/software/networking/udp/udp_sender.h +++ b/src/software/networking/udp/udp_sender.h @@ -11,8 +11,7 @@ class UdpSender * Creates a UdpSender that sends strings over the network to the * given address and port. * - * The UdpSender will be assigned a random available port on creation - * so it doesn't conflict with anything else on the system. + * Callers must ensure that error is not set before using this object. * * @param io_service The io_service to use to service outgoing SendString data * @param ip_address The ip address to send data on @@ -21,13 +20,23 @@ class UdpSender * example IPv6: ff02::c3d0:42d2:bb8%wlp4s0 (the interface is specified after %) * @param port The port to send SendString data to * @param multicast If true, joins the multicast group of given ip_address + * @param error A user-provided optional string to store any errors that occur */ UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - unsigned short port, const std::string& interface, bool multicast); + unsigned short port, const std::string& interface, bool multicast, std::optional& error); ~UdpSender(); - void setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface); + /** + * Set up multicast for the given multicast ip address and interface + * + * Any errors during setup will be stored in the error string + * + * @param ip_address The multicast ip address to join + * @param interface The interface to join the multicast group on + * @param error A user-provided optional string to store any errors that occur + */ + void setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface, std::optional& error); /** * Sends a string message to the initialized ip address and port From ab5a9887384d1502bc76d2d886f33f6b1460c90c Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 20 Jun 2024 00:10:24 -0700 Subject: [PATCH 18/50] wip & cleanup --- src/software/networking/udp/network_utils.h | 7 +++- .../networking/udp/network_utils_test.cpp | 1 + .../networking/udp/proto_udp_listener.hpp | 1 + .../udp/threaded_proto_udp_listener_test.cpp | 1 + .../udp/threaded_proto_udp_sender.hpp | 3 +- .../udp/threaded_proto_udp_sender_test.cpp | 2 +- .../networking/udp/udp_network_factory.hpp | 13 ------- src/software/python_bindings.cpp | 29 ++++++++++----- .../game_controller.py | 5 +-- .../common/proto_configuration_widget.py | 5 ++- .../common/proto_parameter_tree_util.py | 11 +++++- src/software/thunderscope/constants.py | 1 + .../thunderscope/robot_communication.py | 37 ++++++++++++------- src/software/thunderscope/thunderscope.py | 2 - .../thunderscope/widget_setup_functions.py | 7 ++++ 15 files changed, 76 insertions(+), 49 deletions(-) delete mode 100644 src/software/networking/udp/udp_network_factory.hpp diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 157c9fe85a..1ebca06a45 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -4,7 +4,7 @@ #include /** - * @brief Given an interface, get the IP address associated with that interface + * Given an interface, get the IP address associated with that interface * * The modified ip_address is valid only if the function returns true * @@ -17,7 +17,10 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); /** - * @brief Check if the given string is a valid IPv6 address + * Check if the given string follows the IPv6 address format + * + * Addresses that are actually an "embedded IPv4 address" are still considered as an IPv6 address since it follows the + * IPv6 address format * * @param ip_address The string to check * @return true if the string is a valid IPv6 address, false otherwise diff --git a/src/software/networking/udp/network_utils_test.cpp b/src/software/networking/udp/network_utils_test.cpp index 1079355636..7762700f53 100644 --- a/src/software/networking/udp/network_utils_test.cpp +++ b/src/software/networking/udp/network_utils_test.cpp @@ -31,6 +31,7 @@ TEST(NetworkUtilsTest, isIpv6ForIpv4) TEST(NetworkUtilsTest, isIpv6ForIpv4Mapped) { + // This is actually an IPv4 address mapped to an IPv6 address std::string ip_address = "::ffff:0:0"; EXPECT_TRUE(isIpv6(ip_address)); } diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 23005862b8..3f897bccd1 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -223,6 +223,7 @@ void ProtoUdpListener::handleDataReception( { return; } + // Start listening again to receive the next data startListen(); } diff --git a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp index 6f27eea8d4..42f7d9ba3e 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp @@ -14,6 +14,7 @@ TEST(ThreadedProtoUdpListenerTest, error_finding_local_ip_address) TEST(ThreadedProtoUdpListenerTest, error_creating_socket) { std::optional error; + // This will always fail because it requires root privileges to open this port ThreadedProtoUdpListener("224.5.23.1", 1023, "lo", [](const auto&){}, true, error); EXPECT_TRUE(error.has_value()); } diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index ccb578d55c..158f70ca98 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "software/networking/udp/threaded_udp_sender.h" @@ -26,7 +27,7 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * @param error An optional user-provided string that will be set to an error message if an error occurs */ ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, - bool multicast, std::optional& error = std::nullopt) + bool multicast, std::optional& error) : ThreadedUdpSender(ip_address, port, interface, multicast, error) { } diff --git a/src/software/networking/udp/threaded_proto_udp_sender_test.cpp b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp index dee9b902a2..8e6c070d0b 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp @@ -4,7 +4,7 @@ #include -TEST(ThreadedProtoUdpSenderTest, error_finding_ip_address) +TEST(ThreadedProtoUdpSenderTest, error_finding_local_ip_address) { std::optional error; ThreadedProtoUdpSender("224.5.23.1", 40000, "interfacemcinterfaceface", true, error); diff --git a/src/software/networking/udp/udp_network_factory.hpp b/src/software/networking/udp/udp_network_factory.hpp deleted file mode 100644 index 24e823b580..0000000000 --- a/src/software/networking/udp/udp_network_factory.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -class UdpNetworkFactory -{ - public: - template - static createThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, - const std::string& interface, std::function receive_callback, bool multicast); - - template - static createThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, - const std::string& interface, std::function receive_callback, bool multicast); -}; diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index c3cabaf28e..45f00ca243 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -59,8 +59,20 @@ void declareThreadedProtoUdpSender(py::module& m, std::string name) std::string pyclass_name = name + "ProtoUdpSender"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init()) .def("send_proto", &Class::sendProto); + + std::string create_pyclass_name = "create" + pyclass_name; + m.def(create_pyclass_name.c_str(), [](const std::string& ip_address, unsigned short port, + const std::string& interface, bool multicast) { + // Pybind doesn't bind references in some cases + // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) + std::optional error; + std::shared_ptr sender = std::make_shared(ip_address, port, interface, multicast, error); + + // Return the sender and the error message to the Python side + // Use as: sender, error = create{name}ProtoUdpSender(...) + return std::make_tuple(sender, error); + }); } /** @@ -93,20 +105,19 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) std::string pyclass_name = name + "ProtoListener"; py::class_>(m, pyclass_name.c_str(), py::buffer_protocol(), py::dynamic_attr()) - .def(py::init([](const std::string& ip_address, unsigned short port, const std::string& interface, - const std::function& callback, bool multicast, std::optional& error) - { - return std::make_shared(ip_address, port, interface, callback, multicast, error); - })) .def("close", &Class::close); - std::stringstream ss; - ss << "create" << pyclass_name; - m.def(ss.str().c_str(), [](const std::string& ip_address, unsigned short port, + std::string create_pyclass_name = "create" + pyclass_name; + m.def(create_pyclass_name.c_str(), [](const std::string& ip_address, unsigned short port, const std::string& interface, const std::function& callback, bool multicast) { + // Pybind doesn't bind references in some cases + // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) std::optional error; std::shared_ptr listener = std::make_shared(ip_address, port, interface, callback, multicast, error); + + // Return the listener and the error message to the Python side + // Use as: listener, error = create{name}ProtoListener(...) return std::make_tuple(listener, error); }); } diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 677a522edb..7aa2b9276a 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -27,13 +27,12 @@ class Gamecontroller(object): REFEREE_IP = "224.5.23.1" CI_MODE_OUTPUT_RECEIVE_BUFFER_SIZE = 9000 - def __init__(self, supress_logs: bool = False, interface: str = "lo") -> None: + def __init__(self, supress_logs: bool = False) -> None: """Run Gamecontroller :param supress_logs: Whether to suppress the logs """ self.supress_logs = supress_logs - self.interface = interface # We need to find 2 free ports to use for the gamecontroller # so that we can run multiple gamecontroller instances in parallel @@ -151,7 +150,7 @@ def __send_referee_command(data: Referee) -> None: autoref_proto_unix_io.send_proto(Referee, data) self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( - Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, self.interface + Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, "lo" ) blue_full_system_proto_unix_io.register_observer( diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 113ccb48ed..c56dc2b62e 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -12,6 +12,7 @@ class ProtoConfigurationWidget(QWidget): DELAYED_CONFIGURATION_TIMEOUT_S = 5 + """How long to wait after startup to send the first configuration to our AI""" """Creates a searchable parameter widget that can take any protobuf, and convert it into a pyqtgraph ParameterTree. This will allow users @@ -124,7 +125,6 @@ def config_proto_to_param_dict(self, message, search_term=None): :param search_term: The search filter """ - field_list = proto_parameter_tree_util.config_proto_to_field_list( message, search_term=search_term, @@ -163,5 +163,6 @@ def build_proto(self, message, current_attr=None): self.build_proto(value, f"{current_attr}.{key}") def __first_shot(self): + """Send the current configuration to the AI after a delay""" time.sleep(ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S) - self.on_change_callback(None, None, self.proto_to_configure) + self.on_change_callback(str(self.proto_to_configure), self.proto_to_configure, self.proto_to_configure) diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index b240b4fdff..4601d0957f 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -5,6 +5,15 @@ import netifaces +""" +Instead of the using the generic parameter parsing, this constant can be used to define custom handlers for specific +fields in the proto. + +To define a custom handler: + 1. The key is the name of the field in the proto + 2. The value is the function that will be called to parse the field. Make sure the function is callable from this + file +""" CUSTOM_PARAMETERS_OVERRIDE = {"referee_interface": "__create_network_enum", "robot_status_interface": "__create_network_enum", "vision_interface": "__create_network_enum"} @@ -136,8 +145,6 @@ def __create_network_enum(key, value, _): :param key: The name of the parameter :param value: The default value - :param descriptor: The proto descriptor - """ network_interfaces = netifaces.interfaces() diff --git a/src/software/thunderscope/constants.py b/src/software/thunderscope/constants.py index 4dfbccbd1d..40b7e0563f 100644 --- a/src/software/thunderscope/constants.py +++ b/src/software/thunderscope/constants.py @@ -197,6 +197,7 @@ class EstopMode(IntEnum): """ ) + def is_field_message_empty(field: Field) -> bool: """ Checks if a field message is empty diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index ec8df43322..4a5a795cb2 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -15,6 +15,7 @@ from google.protobuf.message import Message DISCONNECTED = "DISCONNECTED" +"""A constant to represent a disconnected interface""" class RobotCommunication(object): @@ -118,6 +119,9 @@ def __init__( def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_interface: str = DISCONNECTED) -> None: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default + + :param referee_interface: the interface to listen for referee data + :param vision_interface: the interface to listen for vision data """ change_referee_interface = (referee_interface != self.current_network_config.referee_interface)\ and (referee_interface != DISCONNECTED) @@ -160,10 +164,14 @@ def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_int self.is_setup_for_fullsystem = True - self.__print_current_network_config() - def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> None: + """ + Set up senders and listeners for communicating with the robots + + :param robot_status_interface: the interface to listen/send for robot status data. Ignored for sending + primitives if using radio + """ if robot_status_interface == self.current_network_config.robot_status_interface\ or robot_status_interface == DISCONNECTED: return @@ -197,7 +205,7 @@ def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> if self.enable_radio: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: - self.send_primitive_set = tbots_cpp.PrimitiveSetProtoUdpSender( + self.send_primitive_set, error = tbots_cpp.createPrimitiveSetProtoUdpSender( self.multicast_channel, PRIMITIVE_PORT, robot_status_interface, True ) @@ -205,7 +213,6 @@ def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> print(f"Error setting up robot status interface: {error}") self.current_network_config.robot_status_interface = robot_status_interface if not error else DISCONNECTED - self.__print_current_network_config() def close_for_fullsystem(self) -> None: if self.receive_ssl_wrapper: @@ -214,16 +221,6 @@ def close_for_fullsystem(self) -> None: if self.receive_ssl_referee_proto: self.receive_ssl_referee_proto.close() - def __close_for_robot_communication(self) -> None: - if self.receive_robot_status: - self.receive_robot_status.close() - - if self.receive_robot_log: - self.receive_robot_log.close() - - if self.receive_robot_crash: - self.receive_robot_crash.close() - def toggle_keyboard_estop(self) -> None: """ @@ -328,6 +325,7 @@ def __run_primitive_set(self) -> None: self.__setup_for_robot_communication( robot_status_interface=network_config.robot_status_interface ) + self.__print_current_network_config() # total primitives for all robots robot_primitives = {} @@ -423,7 +421,18 @@ def __exit__(self, type, value, traceback) -> None: self.run_primitive_set_thread.join() def __print_current_network_config(self) -> None: + """ + Prints the current network configuration to the console + """ def output_string(comm_name: str, status: str) -> str: + """ + Returns a formatted string with the communication name and status + + Any status other than DISCONNECTED will be coloured green, otherwise red + + :param comm_name: the name of the communication + :param status: the status of the communication + """ colour = Fore.RED if status == DISCONNECTED else Fore.GREEN return f"{comm_name} {colour}{status} {Style.RESET_ALL}" diff --git a/src/software/thunderscope/thunderscope.py b/src/software/thunderscope/thunderscope.py index 08c3303b71..a64957a370 100644 --- a/src/software/thunderscope/thunderscope.py +++ b/src/software/thunderscope/thunderscope.py @@ -120,8 +120,6 @@ def __init__( lambda: QMessageBox.information(self.window, "Help", THUNDERSCOPE_HELP_TEXT) ) - print("arun: thunderscope initializing") - def reset_layout(self) -> None: """Reset the layout to the default layout""" saved_layout_path = pathlib.Path(LAST_OPENED_LAYOUT_PATH) diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 5915520c06..c8d5d51f82 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -222,6 +222,13 @@ def setup_network_config_widget( def on_change_callback( attr: Any, value: Any, updated_proto: NetworkConfig ) -> None: + """ + Callback function that sends updated network configuration + + :param attr: The parameter that changed + :param value: The new value + :param updated_proto: The updated network configuration + """ proto_unix_io.send_proto(NetworkConfig, updated_proto) return ProtoConfigurationWidget(config, on_change_callback) From 1d36ec87ca6f1fbdfb2d4a482c16c4634e3c9496 Mon Sep 17 00:00:00 2001 From: arun Date: Sat, 22 Jun 2024 03:03:12 -0700 Subject: [PATCH 19/50] wip --- .../binary_context_managers/game_controller.py | 5 ++++- .../thunderscope/common/proto_parameter_tree_util.py | 5 ++--- src/software/thunderscope/robot_communication.py | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 7aa2b9276a..4da85447da 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -149,10 +149,13 @@ def __send_referee_command(data: Referee) -> None: if autoref_proto_unix_io is not None: autoref_proto_unix_io.send_proto(Referee, data) - self.receive_referee_command = tbots_cpp.SSLRefereeProtoListener( + self.receive_referee_command, error = tbots_cpp.createSSLRefereeProtoListener( Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, "lo" ) + if error: + print("[Gamecontroller] Failed to bind to the referee port and listen to referee messages") + blue_full_system_proto_unix_io.register_observer( ManualGCCommand, self.command_override_buffer ) diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index 4601d0957f..0e350ee176 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -139,9 +139,8 @@ def __create_parameter_read_only(key, value, descriptor): def __create_network_enum(key, value, _): - """Converts an enum field in a protobuf to a ListParameter. Uses - the options to lookup all possible enum values and provides them - as a dropdown option. + """ + Lists all the network interfaces available on the system as enum options for the given parameter field. :param key: The name of the parameter :param value: The default value diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 9a86755798..e2195da998 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -314,9 +314,9 @@ def __run_primitive_set(self) -> None: is useful to dip in and out of robot diagnostics. """ - while self.running: - network_config = self.network_config_buffer.get(block=False if self.send_primitive_set else True, + network_config = self.network_config_buffer.get(block=False if self.send_primitive_set else True, return_cached=False) + while self.running: if network_config is not None: print(f"[RobotCommunication] Received new NetworkConfig") if self.is_setup_for_fullsystem: @@ -327,6 +327,9 @@ def __run_primitive_set(self) -> None: ) self.__print_current_network_config() + # Set up network on the next tick + network_config = self.network_config_buffer.get(block=False, return_cached=False) + # total primitives for all robots robot_primitives = {} From 84d9ae4a7bb38a11e6f81820fddcbecc7c732223 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 25 Jun 2024 23:06:07 -0700 Subject: [PATCH 20/50] cleanup --- src/software/logger/BUILD | 1 - .../thunderscope/binary_context_managers/game_controller.py | 2 +- src/software/thunderscope/common/proto_configuration_widget.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index 48f1ded128..5ccd7bbab5 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -19,7 +19,6 @@ cc_library( "custom_logging_levels.h", "logger.h", ], - linkopts = ["-lstdc++fs"], deps = [ ":coloured_cout_sink", ":csv_sink", diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 4da85447da..48a4056578 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -150,7 +150,7 @@ def __send_referee_command(data: Referee) -> None: autoref_proto_unix_io.send_proto(Referee, data) self.receive_referee_command, error = tbots_cpp.createSSLRefereeProtoListener( - Gamecontroller.REFEREE_IP, self.referee_port, __send_referee_command, True, "lo" + Gamecontroller.REFEREE_IP, self.referee_port, "lo", __send_referee_command, True ) if error: diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index c56dc2b62e..9785e14411 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -165,4 +165,4 @@ def build_proto(self, message, current_attr=None): def __first_shot(self): """Send the current configuration to the AI after a delay""" time.sleep(ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S) - self.on_change_callback(str(self.proto_to_configure), self.proto_to_configure, self.proto_to_configure) + self.on_change_callback(str(self.proto_to_configure), None, self.proto_to_configure) From 80e353dea02404aa1f67d7ad7bfe8e83cf0a6ac5 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 25 Jun 2024 23:21:08 -0700 Subject: [PATCH 21/50] revert unintentional change --- .../ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp index 8a2cad34dc..2058dd3231 100644 --- a/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp +++ b/src/software/ai/hl/stp/play/penalty_kick/penalty_kick_play_test.cpp @@ -66,7 +66,7 @@ TEST_F(PenaltyKickPlayTest, test_penalty_kick_setup) } // TODO (#3106): Re-enable test once robot 1 and 2 tactic assignment do not oscillate -TEST_F(PenaltyKickPlayTest, test_penalty_kick_take) +TEST_F(PenaltyKickPlayTest, DISABLED_test_penalty_kick_take) { Vector behind_ball_direction = (field.friendlyPenaltyMark() - field.enemyGoalCenter()).normalize(); From 22eb8a88f4195ef41873f37a110ad9402da1bd58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 06:32:15 +0000 Subject: [PATCH 22/50] [pre-commit.ci lite] apply automatic fixes --- .../jetson_nano/services/network/network.cpp | 6 +- .../jetson_nano/services/network/network.h | 3 +- src/software/jetson_nano/thunderloop.cpp | 8 +- src/software/logger/BUILD | 2 +- src/software/logger/network_sink.cpp | 4 +- src/software/logger/plotjuggler_sink.cpp | 3 +- src/software/logger/plotjuggler_sink.h | 2 +- src/software/network_log_listener_main.cpp | 4 +- src/software/networking/udp/BUILD | 6 +- src/software/networking/udp/network_utils.cpp | 6 +- src/software/networking/udp/network_utils.h | 12 +-- .../networking/udp/network_utils_test.cpp | 6 +- .../networking/udp/proto_udp_listener.hpp | 49 ++++++----- .../udp/threaded_proto_udp_listener.hpp | 24 +++-- .../udp/threaded_proto_udp_listener_test.cpp | 13 +-- .../udp/threaded_proto_udp_sender.hpp | 11 ++- .../udp/threaded_proto_udp_sender_test.cpp | 10 ++- .../networking/udp/threaded_udp_sender.cpp | 3 +- .../networking/udp/threaded_udp_sender.h | 11 ++- src/software/networking/udp/udp_sender.cpp | 23 ++--- src/software/networking/udp/udp_sender.h | 6 +- src/software/python_bindings.cpp | 50 ++++++----- .../game_controller.py | 10 ++- .../common/proto_configuration_widget.py | 5 +- .../common/proto_parameter_tree_util.py | 22 +++-- .../thunderscope/robot_communication.py | 88 +++++++++++++------ .../thunderscope/thunderscope_config.py | 4 +- .../thunderscope/widget_setup_functions.py | 13 ++- 28 files changed, 243 insertions(+), 161 deletions(-) diff --git a/src/software/jetson_nano/services/network/network.cpp b/src/software/jetson_nano/services/network/network.cpp index 03a02a3d70..e244c610b8 100644 --- a/src/software/jetson_nano/services/network/network.cpp +++ b/src/software/jetson_nano/services/network/network.cpp @@ -2,7 +2,8 @@ NetworkService::NetworkService(const std::string& ip_address, unsigned short primitive_listener_port, - unsigned short robot_status_sender_port, const std::string& interface, bool multicast) + unsigned short robot_status_sender_port, + const std::string& interface, bool multicast) : primitive_tracker(ProtoTracker("primitive set")) { sender = std::make_unique>( @@ -12,7 +13,8 @@ NetworkService::NetworkService(const std::string& ip_address, udp_listener_primitive_set = std::make_unique>( ip_address, primitive_listener_port, interface, - boost::bind(&NetworkService::primitiveSetCallback, this, _1), multicast, error); + boost::bind(&NetworkService::primitiveSetCallback, this, _1), multicast, + error); if (error) { LOG(FATAL) << *error; diff --git a/src/software/jetson_nano/services/network/network.h b/src/software/jetson_nano/services/network/network.h index f9450a8efe..739f4e5713 100644 --- a/src/software/jetson_nano/services/network/network.h +++ b/src/software/jetson_nano/services/network/network.h @@ -29,7 +29,8 @@ class NetworkService * we should join the group */ NetworkService(const std::string& ip_address, unsigned short primitive_listener_port, - unsigned short robot_status_sender_port, const std::string& interface, bool multicast); + unsigned short robot_status_sender_port, const std::string& interface, + bool multicast); /** * When the network service is polled, it sends the robot_status and returns diff --git a/src/software/jetson_nano/thunderloop.cpp b/src/software/jetson_nano/thunderloop.cpp index 302a050dfd..a2e60687a3 100644 --- a/src/software/jetson_nano/thunderloop.cpp +++ b/src/software/jetson_nano/thunderloop.cpp @@ -57,8 +57,8 @@ extern "C" *(crash_msg.mutable_status()) = *robot_status; auto sender = std::make_unique>( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id)), - ROBOT_CRASH_PORT, network_interface, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id)), ROBOT_CRASH_PORT, + network_interface, true); sender->sendProto(crash_msg); std::cerr << "Broadcasting robot crash msg"; @@ -106,8 +106,8 @@ Thunderloop::Thunderloop(const RobotConstants_t& robot_constants, bool enable_lo << "THUNDERLOOP: Network Logger initialized! Next initializing Network Service"; network_service_ = std::make_unique( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), - PRIMITIVE_PORT, ROBOT_STATUS_PORT, network_interface, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), PRIMITIVE_PORT, + ROBOT_STATUS_PORT, network_interface, true); LOG(INFO) << "THUNDERLOOP: Network Service initialized! Next initializing Power Service"; diff --git a/src/software/logger/BUILD b/src/software/logger/BUILD index 5ccd7bbab5..b3842263a4 100644 --- a/src/software/logger/BUILD +++ b/src/software/logger/BUILD @@ -19,6 +19,7 @@ cc_library( "custom_logging_levels.h", "logger.h", ], + linkopts = ["-lstdc++fs"], deps = [ ":coloured_cout_sink", ":csv_sink", @@ -28,7 +29,6 @@ cc_library( "@g3log", "@g3sinks", ], - linkopts = ["-lstdc++fs"], ) cc_library( diff --git a/src/software/logger/network_sink.cpp b/src/software/logger/network_sink.cpp index b0a6beebd5..f45cffdbfa 100644 --- a/src/software/logger/network_sink.cpp +++ b/src/software/logger/network_sink.cpp @@ -12,8 +12,8 @@ NetworkSink::NetworkSink(unsigned int channel, const std::string& interface, int : robot_id(robot_id), log_merger(LogMerger(enable_log_merging)) { log_output.reset(new ThreadedProtoUdpSender( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel)), - ROBOT_LOGS_PORT, interface, true)); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel)), ROBOT_LOGS_PORT, interface, + true)); } void NetworkSink::sendToNetwork(g3::LogMessageMover log_entry) diff --git a/src/software/logger/plotjuggler_sink.cpp b/src/software/logger/plotjuggler_sink.cpp index b0d2ea2505..513a85243a 100644 --- a/src/software/logger/plotjuggler_sink.cpp +++ b/src/software/logger/plotjuggler_sink.cpp @@ -5,7 +5,8 @@ #include "shared/constants.h" PlotJugglerSink::PlotJugglerSink(const std::string& interface) - : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, interface, false, error) + : udp_sender(PLOTJUGGLER_GUI_DEFAULT_HOST, PLOTJUGGLER_GUI_DEFAULT_PORT, interface, + false, error) { if (error.has_value()) { diff --git a/src/software/logger/plotjuggler_sink.h b/src/software/logger/plotjuggler_sink.h index 781c756dae..ff40a4be47 100644 --- a/src/software/logger/plotjuggler_sink.h +++ b/src/software/logger/plotjuggler_sink.h @@ -19,7 +19,7 @@ class PlotJugglerSink * * @param interface The interface to send Plotjuggler UDP packets on */ - PlotJugglerSink(const std::string& interface="lo"); + PlotJugglerSink(const std::string& interface = "lo"); ~PlotJugglerSink() = default; diff --git a/src/software/network_log_listener_main.cpp b/src/software/network_log_listener_main.cpp index 435db2deea..55edb5e92e 100644 --- a/src/software/network_log_listener_main.cpp +++ b/src/software/network_log_listener_main.cpp @@ -95,8 +95,8 @@ int main(int argc, char **argv) std::optional error; auto log_input = std::make_unique>( - std::string(ROBOT_MULTICAST_CHANNELS.at(args.channel)), - ROBOT_LOGS_PORT, args.interface, robot_log_callback, true, error); + std::string(ROBOT_MULTICAST_CHANNELS.at(args.channel)), ROBOT_LOGS_PORT, + args.interface, robot_log_callback, true, error); if (error) { LOG(FATAL) << *error; diff --git a/src/software/networking/udp/BUILD b/src/software/networking/udp/BUILD index 464a822e8a..37513cdc2e 100644 --- a/src/software/networking/udp/BUILD +++ b/src/software/networking/udp/BUILD @@ -2,12 +2,12 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "network_utils", - hdrs = [ - "network_utils.h", - ], srcs = [ "network_utils.cpp", ], + hdrs = [ + "network_utils.h", + ], ) cc_test( diff --git a/src/software/networking/udp/network_utils.cpp b/src/software/networking/udp/network_utils.cpp index 998a69ef8b..88a47e7772 100644 --- a/src/software/networking/udp/network_utils.cpp +++ b/src/software/networking/udp/network_utils.cpp @@ -5,7 +5,7 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4) { struct ifaddrs* ifAddrStruct = nullptr; - struct ifaddrs* ifa = nullptr; + struct ifaddrs* ifa = nullptr; getifaddrs(&ifAddrStruct); @@ -16,7 +16,7 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 if (ipv4 && ifa->ifa_addr->sa_family == AF_INET) { char addressBuffer[INET_ADDRSTRLEN]; - struct sockaddr_in* sa = (struct sockaddr_in*) ifa->ifa_addr; + struct sockaddr_in* sa = (struct sockaddr_in*)ifa->ifa_addr; inet_ntop(AF_INET, &sa->sin_addr, addressBuffer, INET_ADDRSTRLEN); freeifaddrs(ifAddrStruct); ip_address = addressBuffer; @@ -25,7 +25,7 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 else if (!ipv4 && ifa->ifa_addr->sa_family == AF_INET6) { char addressBuffer[INET6_ADDRSTRLEN]; - struct sockaddr_in6* sa = (struct sockaddr_in6*) ifa->ifa_addr; + struct sockaddr_in6* sa = (struct sockaddr_in6*)ifa->ifa_addr; inet_ntop(AF_INET6, &sa->sin6_addr, addressBuffer, INET6_ADDRSTRLEN); freeifaddrs(ifAddrStruct); ip_address = addressBuffer; diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 1ebca06a45..2d6744b70a 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -1,6 +1,7 @@ #pragma once #include + #include /** @@ -9,19 +10,20 @@ * The modified ip_address is valid only if the function returns true * * @param interface The interface to get the IP address from - * @param ip_address A reference to the std::string that will store the IP address if found + * @param ip_address A reference to the std::string that will store the IP address if + * found * @param ipv4 If true, get the IPv4 address, otherwise get the IPv6 address * * @return true if the IP address was found, false otherwise */ -bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4=true); +bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 = true); /** * Check if the given string follows the IPv6 address format * - * Addresses that are actually an "embedded IPv4 address" are still considered as an IPv6 address since it follows the - * IPv6 address format - * + * Addresses that are actually an "embedded IPv4 address" are still considered as an IPv6 + * address since it follows the IPv6 address format + * * @param ip_address The string to check * @return true if the string is a valid IPv6 address, false otherwise */ diff --git a/src/software/networking/udp/network_utils_test.cpp b/src/software/networking/udp/network_utils_test.cpp index 7762700f53..dff9fc2ad2 100644 --- a/src/software/networking/udp/network_utils_test.cpp +++ b/src/software/networking/udp/network_utils_test.cpp @@ -1,10 +1,10 @@ -#include - #include "software/networking/udp/network_utils.h" +#include + TEST(NetworkUtilsTest, getLocalIpValidInterface) { - std::string interface = "lo"; + std::string interface = "lo"; std::string ip_address; EXPECT_TRUE(getLocalIp(interface, ip_address, true)); EXPECT_EQ(ip_address, "127.0.0.1"); diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 3f897bccd1..391d31e7b6 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -37,8 +37,8 @@ class ProtoUdpListener */ ProtoUdpListener(boost::asio::io_service& io_service, const std::string& ip_address, unsigned short port, const std::string& listen_interface, - std::function receive_callback, - bool multicast, std::optional& error); + std::function receive_callback, bool multicast, + std::optional& error); /** * Creates an ProtoUdpListener that will listen for ReceiveProtoT packets from @@ -85,7 +85,8 @@ class ProtoUdpListener * @param error A user-provided optional string to store any error messages */ void setupMulticast(const boost::asio::ip::address& ip_address, - const std::string& listen_interface, std::optional& error); + const std::string& listen_interface, + std::optional& error); /** * Start listening for data @@ -111,7 +112,8 @@ template ProtoUdpListener::ProtoUdpListener( boost::asio::io_service& io_service, const std::string& ip_address, const unsigned short port, const std::string& listen_interface, - std::function receive_callback, bool multicast, std::optional& error) + std::function receive_callback, bool multicast, + std::optional& error) : socket_(io_service), receive_callback(receive_callback) { boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); @@ -130,11 +132,11 @@ ProtoUdpListener::ProtoUdpListener( { std::stringstream ss; ss << "UdpListener: There was an issue binding the socket to " - "the listen_endpoint when trying to connect to the " - "address. This may be due to another instance of the " - "UdpListener running and using the port already. " - "(ip = " - << ip_address << ", port = " << port << ")"; + "the listen_endpoint when trying to connect to the " + "address. This may be due to another instance of the " + "UdpListener running and using the port already. " + "(ip = " + << ip_address << ", port = " << port << ")"; error = ss.str(); return; } @@ -154,7 +156,8 @@ ProtoUdpListener::ProtoUdpListener( template ProtoUdpListener::ProtoUdpListener( boost::asio::io_service& io_service, const unsigned short port, - std::function receive_callback, std::optional& error) + std::function receive_callback, + std::optional& error) : socket_(io_service), receive_callback(receive_callback) { boost::asio::ip::udp::endpoint listen_endpoint(boost::asio::ip::udp::v6(), port); @@ -169,11 +172,11 @@ ProtoUdpListener::ProtoUdpListener( { std::stringstream ss; ss << "UdpListener: There was an issue binding the socket to " - "the listen_endpoint when trying to connect to the " - "address. This may be due to another instance of the " - "UdpListener running and using the port already. " - "(port = " - << port << ")"; + "the listen_endpoint when trying to connect to the " + "address. This may be due to another instance of the " + "UdpListener running and using the port already. " + "(port = " + << port << ")"; error = ss.str(); return; } @@ -238,22 +241,24 @@ void ProtoUdpListener::handleDataReception( } template -void ProtoUdpListener::setupMulticast(const boost::asio::ip::address& ip_address, - const std::string& listen_interface, std::optional& error) +void ProtoUdpListener::setupMulticast( + const boost::asio::ip::address& ip_address, const std::string& listen_interface, + std::optional& error) { if (ip_address.is_v4()) { - std::string interface_ip; + std::string interface_ip; if (!getLocalIp(listen_interface, interface_ip)) { std::stringstream ss; ss << "Could not find the local ip address for the given interface: " - << listen_interface << std::endl; + << listen_interface << std::endl; error = ss.str(); return; } socket_.set_option(boost::asio::ip::multicast::join_group( - ip_address.to_v4(), boost::asio::ip::address::from_string(interface_ip).to_v4())); + ip_address.to_v4(), + boost::asio::ip::address::from_string(interface_ip).to_v4())); return; } socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); @@ -276,8 +281,8 @@ void ProtoUdpListener::close() { LOG(WARNING) << "An unknown network error occurred when attempting to shutdown UDP socket for " - << TYPENAME(ReceiveProtoT) - << ". The boost system error is: " << error_code << ": " << error_code.message() << std::endl; + << TYPENAME(ReceiveProtoT) << ". The boost system error is: " << error_code + << ": " << error_code.message() << std::endl; } socket_.close(error_code); diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index 120128e166..fa7a8cb10a 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -15,7 +15,8 @@ class ThreadedProtoUdpListener * ReceiveProtoT packet received, the receive_callback will be called to perform any * operations desired by the caller. * - * Any caller using this constructor should ensure that error is not set before using the listener. + * Any caller using this constructor should ensure that error is not set before using + * the listener. * * @param ip_address The ip address on which to listen for the given ReceiveProtoT * packets (IPv4 in dotted decimal or IPv6 in hex string) example IPv4: 192.168.0.2 @@ -27,9 +28,11 @@ class ThreadedProtoUdpListener * @param multicast If true, joins the multicast group of given ip_address * @param errror A user-provided optional string to store any error messages */ - ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, + ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, + const std::string& interface, std::function receive_callback, - bool multicast, std::optional& error = std::nullopt); + bool multicast, + std::optional& error = std::nullopt); /** * Creates a ThreadedProtoUdpListener that will listen for ReceiveProtoT packets @@ -37,7 +40,8 @@ class ThreadedProtoUdpListener * packet received, the receive_callback will be called to perform any operations * desired by the caller. * - * Any caller using this constructor should ensure that error is not set before using the listener. + * Any caller using this constructor should ensure that error is not set before using + * the listener. * * @param port The port on which to listen for ReceiveProtoT packets * @param interface The interface on which to listen for ReceiveProtoT packets @@ -69,11 +73,12 @@ class ThreadedProtoUdpListener template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const std::string& ip_address, const unsigned short port, const std::string& interface, - std::function receive_callback, bool multicast, - std::optional& error) + const std::string& ip_address, const unsigned short port, + const std::string& interface, std::function receive_callback, + bool multicast, std::optional& error) : io_service(), - udp_listener(io_service, ip_address, port, interface, receive_callback, multicast, error) + udp_listener(io_service, ip_address, port, interface, receive_callback, multicast, + error) { // start the thread to run the io_service in the background io_service_thread = std::thread([this]() { io_service.run(); }); @@ -81,7 +86,8 @@ ThreadedProtoUdpListener::ThreadedProtoUdpListener( template ThreadedProtoUdpListener::ThreadedProtoUdpListener( - const unsigned short port, const std::string& interface, std::function receive_callback, + const unsigned short port, const std::string& interface, + std::function receive_callback, std::optional& error) : io_service(), udp_listener(io_service, port, interface, receive_callback, error) { diff --git a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp index 42f7d9ba3e..68aa89921f 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_listener_test.cpp @@ -1,13 +1,14 @@ #include "software/networking/udp/threaded_proto_udp_listener.hpp" -#include "google/protobuf/empty.pb.h" - #include +#include "google/protobuf/empty.pb.h" + TEST(ThreadedProtoUdpListenerTest, error_finding_local_ip_address) { std::optional error; - ThreadedProtoUdpListener("224.5.23.1", 40000, "interfacemcinterfaceface", [](const auto&){}, true, error); + ThreadedProtoUdpListener( + "224.5.23.1", 40000, "interfacemcinterfaceface", [](const auto&) {}, true, error); EXPECT_TRUE(error.has_value()); } @@ -15,13 +16,15 @@ TEST(ThreadedProtoUdpListenerTest, error_creating_socket) { std::optional error; // This will always fail because it requires root privileges to open this port - ThreadedProtoUdpListener("224.5.23.1", 1023, "lo", [](const auto&){}, true, error); + ThreadedProtoUdpListener( + "224.5.23.1", 1023, "lo", [](const auto&) {}, true, error); EXPECT_TRUE(error.has_value()); } TEST(ThreadedProtoUdpListenerTest, no_error_creating_socket) { std::optional error; - ThreadedProtoUdpListener("224.5.23.0", 40000, "lo", [](const auto&){}, true, error); + ThreadedProtoUdpListener( + "224.5.23.0", 40000, "lo", [](const auto&) {}, true, error); EXPECT_FALSE(error.has_value()); } diff --git a/src/software/networking/udp/threaded_proto_udp_sender.hpp b/src/software/networking/udp/threaded_proto_udp_sender.hpp index 158f70ca98..dd67dd9d2d 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender.hpp +++ b/src/software/networking/udp/threaded_proto_udp_sender.hpp @@ -15,7 +15,8 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * Creates a UdpSender that sends the SendProto over the network on the * given address and port. * - * Any callers must check the error string to see if the initialization was successful before using the object. + * Any callers must check the error string to see if the initialization was successful + * before using the object. * * @param ip_address The ip address to send data on * (IPv4 in dotted decimal or IPv6 in hex string) @@ -24,10 +25,12 @@ class ThreadedProtoUdpSender : private ThreadedUdpSender * @param port The port to send SendProto data on * @param interface The interface to send data on * @param multicast If true, joins the multicast group of given ip_address - * @param error An optional user-provided string that will be set to an error message if an error occurs + * @param error An optional user-provided string that will be set to an error message + * if an error occurs */ - ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, - bool multicast, std::optional& error) + ThreadedProtoUdpSender(const std::string& ip_address, unsigned short port, + const std::string& interface, bool multicast, + std::optional& error) : ThreadedUdpSender(ip_address, port, interface, multicast, error) { } diff --git a/src/software/networking/udp/threaded_proto_udp_sender_test.cpp b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp index 8e6c070d0b..12dfc4ac7d 100644 --- a/src/software/networking/udp/threaded_proto_udp_sender_test.cpp +++ b/src/software/networking/udp/threaded_proto_udp_sender_test.cpp @@ -1,19 +1,21 @@ #include "software/networking/udp/threaded_proto_udp_sender.hpp" -#include "google/protobuf/empty.pb.h" - #include +#include "google/protobuf/empty.pb.h" + TEST(ThreadedProtoUdpSenderTest, error_finding_local_ip_address) { std::optional error; - ThreadedProtoUdpSender("224.5.23.1", 40000, "interfacemcinterfaceface", true, error); + ThreadedProtoUdpSender( + "224.5.23.1", 40000, "interfacemcinterfaceface", true, error); EXPECT_TRUE(error.has_value()); } TEST(ThreadedProtoUdpSenderTest, no_error_creating_socket) { std::optional error; - ThreadedProtoUdpSender("224.5.23.1", 40000, "lo", true, error); + ThreadedProtoUdpSender("224.5.23.1", 40000, "lo", true, + error); EXPECT_FALSE(error.has_value()); } diff --git a/src/software/networking/udp/threaded_udp_sender.cpp b/src/software/networking/udp/threaded_udp_sender.cpp index 52c3b44ede..4441a9752a 100644 --- a/src/software/networking/udp/threaded_udp_sender.cpp +++ b/src/software/networking/udp/threaded_udp_sender.cpp @@ -1,7 +1,8 @@ #include "software/networking/udp/threaded_udp_sender.h" ThreadedUdpSender::ThreadedUdpSender(const std::string& ip_address, - const unsigned short port, const std::string& interface, bool multicast, + const unsigned short port, + const std::string& interface, bool multicast, std::optional& error) : io_service(), udp_sender(io_service, ip_address, port, interface, multicast, error), diff --git a/src/software/networking/udp/threaded_udp_sender.h b/src/software/networking/udp/threaded_udp_sender.h index 2131573d40..8163a67055 100644 --- a/src/software/networking/udp/threaded_udp_sender.h +++ b/src/software/networking/udp/threaded_udp_sender.h @@ -13,7 +13,8 @@ class ThreadedUdpSender * Creates a UdpSender that sends the sendString over the network on the * given address and port. * - * All callers must check the error string to see if the initialization was successful before using the object. + * All callers must check the error string to see if the initialization was successful + * before using the object. * * @param ip_address The ip address to send data on * (IPv4 in dotted decimal or IPv6 in hex string) @@ -22,10 +23,12 @@ class ThreadedUdpSender * @param port The port to send sendString data on * @param interface The interface to send data on * @param multicast If true, joins the multicast group of given ip_address - * @param error An optional user-provided string that will be set to an error message if an error occurs + * @param error An optional user-provided string that will be set to an error message + * if an error occurs */ - ThreadedUdpSender(const std::string& ip_address, unsigned short port, const std::string& interface, bool multicast, - std::optional& error); + ThreadedUdpSender(const std::string& ip_address, unsigned short port, + const std::string& interface, bool multicast, + std::optional& error); ~ThreadedUdpSender(); diff --git a/src/software/networking/udp/udp_sender.cpp b/src/software/networking/udp/udp_sender.cpp index 2c6ede089c..f8be80d5cf 100644 --- a/src/software/networking/udp/udp_sender.cpp +++ b/src/software/networking/udp/udp_sender.cpp @@ -1,12 +1,12 @@ #include "udp_sender.h" -#include "software/networking/udp/network_utils.h" - #include +#include "software/networking/udp/network_utils.h" + UdpSender::UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - const unsigned short port, const std::string& interface, bool multicast, - std::optional& error) + const unsigned short port, const std::string& interface, + bool multicast, std::optional& error) : socket_(io_service) { boost::asio::ip::address boost_ip = boost::asio::ip::make_address(ip_address); @@ -31,7 +31,9 @@ void UdpSender::sendString(const std::string& message) socket_.send_to(boost::asio::buffer(message, message.length()), receiver_endpoint); } -void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface, std::optional& error) +void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, + const std::string& interface, + std::optional& error) { if (ip_address.is_v4()) { @@ -40,15 +42,16 @@ void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, const { std::stringstream ss; ss << "UdpSender: Could not get the local IP address for the interface " - "specified. (interface = " - << interface << ")"; + "specified. (interface = " + << interface << ")"; error = ss.str(); return; } - - socket_.set_option(boost::asio::ip::multicast::join_group(ip_address.to_v4(), - boost::asio::ip::address::from_string(interface_ip).to_v4())); + + socket_.set_option(boost::asio::ip::multicast::join_group( + ip_address.to_v4(), + boost::asio::ip::address::from_string(interface_ip).to_v4())); return; } diff --git a/src/software/networking/udp/udp_sender.h b/src/software/networking/udp/udp_sender.h index 124170e2ca..6b67ffe69a 100644 --- a/src/software/networking/udp/udp_sender.h +++ b/src/software/networking/udp/udp_sender.h @@ -23,7 +23,8 @@ class UdpSender * @param error A user-provided optional string to store any errors that occur */ UdpSender(boost::asio::io_service& io_service, const std::string& ip_address, - unsigned short port, const std::string& interface, bool multicast, std::optional& error); + unsigned short port, const std::string& interface, bool multicast, + std::optional& error); ~UdpSender(); @@ -36,7 +37,8 @@ class UdpSender * @param interface The interface to join the multicast group on * @param error A user-provided optional string to store any errors that occur */ - void setupMulticast(const boost::asio::ip::address& ip_address, const std::string& interface, std::optional& error); + void setupMulticast(const boost::asio::ip::address& ip_address, + const std::string& interface, std::optional& error); /** * Sends a string message to the initialized ip address and port diff --git a/src/software/python_bindings.cpp b/src/software/python_bindings.cpp index d249b30320..4f71b868ab 100644 --- a/src/software/python_bindings.cpp +++ b/src/software/python_bindings.cpp @@ -67,17 +67,19 @@ void declareThreadedProtoUdpSender(py::module& m, std::string name) .def("send_proto", &Class::sendProto); std::string create_pyclass_name = "create" + pyclass_name; - m.def(create_pyclass_name.c_str(), [](const std::string& ip_address, unsigned short port, - const std::string& interface, bool multicast) { - // Pybind doesn't bind references in some cases - // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) - std::optional error; - std::shared_ptr sender = std::make_shared(ip_address, port, interface, multicast, error); - - // Return the sender and the error message to the Python side - // Use as: sender, error = create{name}ProtoUdpSender(...) - return std::make_tuple(sender, error); - }); + m.def(create_pyclass_name.c_str(), + [](const std::string& ip_address, unsigned short port, + const std::string& interface, bool multicast) { + // Pybind doesn't bind references in some cases + // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) + std::optional error; + std::shared_ptr sender = + std::make_shared(ip_address, port, interface, multicast, error); + + // Return the sender and the error message to the Python side + // Use as: sender, error = create{name}ProtoUdpSender(...) + return std::make_tuple(sender, error); + }); } /** @@ -113,18 +115,20 @@ void declareThreadedProtoUdpListener(py::module& m, std::string name) .def("close", &Class::close); std::string create_pyclass_name = "create" + pyclass_name; - m.def(create_pyclass_name.c_str(), [](const std::string& ip_address, unsigned short port, - const std::string& interface, - const std::function& callback, bool multicast) { - // Pybind doesn't bind references in some cases - // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) - std::optional error; - std::shared_ptr listener = std::make_shared(ip_address, port, interface, callback, multicast, error); - - // Return the listener and the error message to the Python side - // Use as: listener, error = create{name}ProtoListener(...) - return std::make_tuple(listener, error); - }); + m.def(create_pyclass_name.c_str(), + [](const std::string& ip_address, unsigned short port, + const std::string& interface, const std::function& callback, + bool multicast) { + // Pybind doesn't bind references in some cases + // (https://pybind11.readthedocs.io/en/stable/faq.html#limitations-involving-reference-arguments) + std::optional error; + std::shared_ptr listener = std::make_shared( + ip_address, port, interface, callback, multicast, error); + + // Return the listener and the error message to the Python side + // Use as: listener, error = create{name}ProtoListener(...) + return std::make_tuple(listener, error); + }); } diff --git a/src/software/thunderscope/binary_context_managers/game_controller.py b/src/software/thunderscope/binary_context_managers/game_controller.py index 48a4056578..c44f3bfb46 100644 --- a/src/software/thunderscope/binary_context_managers/game_controller.py +++ b/src/software/thunderscope/binary_context_managers/game_controller.py @@ -150,11 +150,17 @@ def __send_referee_command(data: Referee) -> None: autoref_proto_unix_io.send_proto(Referee, data) self.receive_referee_command, error = tbots_cpp.createSSLRefereeProtoListener( - Gamecontroller.REFEREE_IP, self.referee_port, "lo", __send_referee_command, True + Gamecontroller.REFEREE_IP, + self.referee_port, + "lo", + __send_referee_command, + True, ) if error: - print("[Gamecontroller] Failed to bind to the referee port and listen to referee messages") + print( + "[Gamecontroller] Failed to bind to the referee port and listen to referee messages" + ) blue_full_system_proto_unix_io.register_observer( ManualGCCommand, self.command_override_buffer diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 9785e14411..5c7fef91ba 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -19,6 +19,7 @@ class ProtoConfigurationWidget(QWidget): to modify the values. """ + def __init__( self, proto_to_configure, on_change_callback, search_filter_threshold=60, ): @@ -165,4 +166,6 @@ def build_proto(self, message, current_attr=None): def __first_shot(self): """Send the current configuration to the AI after a delay""" time.sleep(ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S) - self.on_change_callback(str(self.proto_to_configure), None, self.proto_to_configure) + self.on_change_callback( + str(self.proto_to_configure), None, self.proto_to_configure + ) diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index 0e350ee176..91f5d25f58 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -14,9 +14,12 @@ 2. The value is the function that will be called to parse the field. Make sure the function is callable from this file """ -CUSTOM_PARAMETERS_OVERRIDE = {"referee_interface": "__create_network_enum", - "robot_status_interface": "__create_network_enum", - "vision_interface": "__create_network_enum"} +CUSTOM_PARAMETERS_OVERRIDE = { + "referee_interface": "__create_network_enum", + "robot_status_interface": "__create_network_enum", + "vision_interface": "__create_network_enum", +} + def __create_int_parameter_writable(key, value, descriptor): """Converts an int field of a proto to a NumericParameterItem with @@ -146,12 +149,9 @@ def __create_network_enum(key, value, _): :param value: The default value """ network_interfaces = netifaces.interfaces() - + return parametertree.parameterTypes.ListParameter( - name=key, - default=None, - value=value, - limits=network_interfaces + name=key, default=None, value=value, limits=network_interfaces ) @@ -202,7 +202,11 @@ def config_proto_to_field_list( continue if descriptor.name in CUSTOM_PARAMETERS_OVERRIDE.keys(): - field_list.append(eval(CUSTOM_PARAMETERS_OVERRIDE[descriptor.name])(key, value, descriptor)) + field_list.append( + eval(CUSTOM_PARAMETERS_OVERRIDE[descriptor.name])( + key, value, descriptor + ) + ) elif descriptor.type == descriptor.TYPE_MESSAGE: field_list.append( { diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index e2195da998..4613b0ef8b 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -83,12 +83,14 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - self.current_network_config = NetworkConfig(robot_status_interface=DISCONNECTED, - vision_interface=DISCONNECTED, - referee_interface=DISCONNECTED) + self.current_network_config = NetworkConfig( + robot_status_interface=DISCONNECTED, + vision_interface=DISCONNECTED, + referee_interface=DISCONNECTED, + ) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( - NetworkConfig, self.network_config_buffer + NetworkConfig, self.network_config_buffer ) self.send_estop_state_thread = threading.Thread( @@ -116,21 +118,30 @@ def __init__( except Exception: raise Exception(f"Invalid Estop found at location {self.estop_path}") - def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_interface: str = DISCONNECTED) -> None: + def setup_for_fullsystem( + self, + referee_interface: str = DISCONNECTED, + vision_interface: str = DISCONNECTED, + ) -> None: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data """ - change_referee_interface = (referee_interface != self.current_network_config.referee_interface)\ - and (referee_interface != DISCONNECTED) - change_vision_interface = (vision_interface != self.current_network_config.vision_interface)\ - and (vision_interface != DISCONNECTED) + change_referee_interface = ( + referee_interface != self.current_network_config.referee_interface + ) and (referee_interface != DISCONNECTED) + change_vision_interface = ( + vision_interface != self.current_network_config.vision_interface + ) and (vision_interface != DISCONNECTED) if change_vision_interface: - self.receive_ssl_wrapper, error = tbots_cpp.createSSLWrapperPacketProtoListener( + ( + self.receive_ssl_wrapper, + error, + ) = tbots_cpp.createSSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, vision_interface, @@ -141,10 +152,15 @@ def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_int if error: print(f"Error setting up vision interface: {error}") - self.current_network_config.vision_interface = vision_interface if not error else DISCONNECTED + self.current_network_config.vision_interface = ( + vision_interface if not error else DISCONNECTED + ) if change_referee_interface: - self.receive_ssl_referee_proto, error = tbots_cpp.createSSLRefereeProtoListener( + ( + self.receive_ssl_referee_proto, + error, + ) = tbots_cpp.createSSLRefereeProtoListener( SSL_REFEREE_ADDRESS, SSL_REFEREE_PORT, referee_interface, @@ -155,7 +171,9 @@ def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_int if error: print(f"Error setting up referee interface: {error}") - self.current_network_config.referee_interface = referee_interface if not error else DISCONNECTED + self.current_network_config.referee_interface = ( + referee_interface if not error else DISCONNECTED + ) if not self.is_setup_for_fullsystem: self.robots_connected_to_fullsystem = { @@ -164,16 +182,19 @@ def setup_for_fullsystem(self, referee_interface: str = DISCONNECTED, vision_int self.is_setup_for_fullsystem = True - - def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> None: + def __setup_for_robot_communication( + self, robot_status_interface: str = "lo" + ) -> None: """ Set up senders and listeners for communicating with the robots :param robot_status_interface: the interface to listen/send for robot status data. Ignored for sending primitives if using radio """ - if robot_status_interface == self.current_network_config.robot_status_interface\ - or robot_status_interface == DISCONNECTED: + if ( + robot_status_interface == self.current_network_config.robot_status_interface + or robot_status_interface == DISCONNECTED + ): return # Create the multicast listeners @@ -208,11 +229,13 @@ def __setup_for_robot_communication(self, robot_status_interface: str = "lo") -> self.send_primitive_set, error = tbots_cpp.createPrimitiveSetProtoUdpSender( self.multicast_channel, PRIMITIVE_PORT, robot_status_interface, True ) - + if error: print(f"Error setting up robot status interface: {error}") - self.current_network_config.robot_status_interface = robot_status_interface if not error else DISCONNECTED + self.current_network_config.robot_status_interface = ( + robot_status_interface if not error else DISCONNECTED + ) def close_for_fullsystem(self) -> None: if self.receive_ssl_wrapper: @@ -221,7 +244,6 @@ def close_for_fullsystem(self) -> None: if self.receive_ssl_referee_proto: self.receive_ssl_referee_proto.close() - def toggle_keyboard_estop(self) -> None: """ If keyboard estop is being used, toggles the estop state @@ -314,21 +336,26 @@ def __run_primitive_set(self) -> None: is useful to dip in and out of robot diagnostics. """ - network_config = self.network_config_buffer.get(block=False if self.send_primitive_set else True, - return_cached=False) + network_config = self.network_config_buffer.get( + block=False if self.send_primitive_set else True, return_cached=False + ) while self.running: if network_config is not None: print(f"[RobotCommunication] Received new NetworkConfig") if self.is_setup_for_fullsystem: - self.setup_for_fullsystem(referee_interface=network_config.referee_interface, - vision_interface=network_config.vision_interface) + self.setup_for_fullsystem( + referee_interface=network_config.referee_interface, + vision_interface=network_config.vision_interface, + ) self.__setup_for_robot_communication( robot_status_interface=network_config.robot_status_interface ) self.__print_current_network_config() # Set up network on the next tick - network_config = self.network_config_buffer.get(block=False, return_cached=False) + network_config = self.network_config_buffer.get( + block=False, return_cached=False + ) # total primitives for all robots robot_primitives = {} @@ -441,6 +468,7 @@ def __print_current_network_config(self) -> None: """ Prints the current network configuration to the console """ + def output_string(comm_name: str, status: str) -> str: """ Returns a formatted string with the communication name and status @@ -453,6 +481,12 @@ def output_string(comm_name: str, status: str) -> str: colour = Fore.RED if status == DISCONNECTED else Fore.GREEN return f"{comm_name} {colour}{status} {Style.RESET_ALL}" - print(output_string("Robot Status\t", self.current_network_config.robot_status_interface)) + print( + output_string( + "Robot Status\t", self.current_network_config.robot_status_interface + ) + ) print(output_string("Vision\t\t", self.current_network_config.vision_interface)) - print(output_string("Referee\t\t", self.current_network_config.referee_interface)) + print( + output_string("Referee\t\t", self.current_network_config.referee_interface) + ) diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 7938911a78..7eb429f89a 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -273,9 +273,7 @@ def configure_base_diagnostics( TScopeWidget( name="Network Configuration", widget=setup_network_config_widget( - **{ - "proto_unix_io": diagnostics_proto_unix_io, - } + **{"proto_unix_io": diagnostics_proto_unix_io,} ), position="below", anchor="Error Log", diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 30d956f471..9b2d851973 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -216,13 +216,14 @@ def on_change_callback( attr: Any, value: Any, updated_proto: ThunderbotsConfig ) -> None: proto_unix_io.send_proto(ThunderbotsConfig, updated_proto) - proto_unix_io.send_proto(NetworkConfig, updated_proto.ai_config.ai_control_config.network_config) + proto_unix_io.send_proto( + NetworkConfig, updated_proto.ai_config.ai_control_config.network_config + ) return ProtoConfigurationWidget(config, on_change_callback) -def setup_network_config_widget( - proto_unix_io: ProtoUnixIO -) -> ProtoConfigurationWidget: + +def setup_network_config_widget(proto_unix_io: ProtoUnixIO) -> ProtoConfigurationWidget: """Setup the network configuration widget :param proto_unix_io: The proto unix io object @@ -231,9 +232,7 @@ def setup_network_config_widget( """ config = NetworkConfig() - def on_change_callback( - attr: Any, value: Any, updated_proto: NetworkConfig - ) -> None: + def on_change_callback(attr: Any, value: Any, updated_proto: NetworkConfig) -> None: """ Callback function that sends updated network configuration From 2649a4aaa77d8586a927cfd156b511cd6f2f215a Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 27 Jun 2024 22:13:08 -0700 Subject: [PATCH 23/50] bug fix and update getting_started guide --- docs/getting-started.md | 15 ++++++++++----- .../jetson_nano/services/network/network.cpp | 8 ++++++-- src/software/thunderscope/robot_communication.py | 10 +++++++--- src/software/thunderscope/thunderscope_main.py | 8 ++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 0f52c75903..88b7517002 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -216,18 +216,17 @@ Now that you're setup, if you can run it on the command line, you can run it in - `[--run_diagnostics]` indicates if diagnostics should be loaded as well - If FullSystem is running, the robots receive input from the AI - If Diagnostics is enabled, the robots can also receive input from Manual controls or XBox controls + - Note: If you simply want to run diagnostics, you do not need to specify `--run_blue` or `--run_yellow` - This mode allows us to test and debug the robots by setting each robot's input to be either AI, Manual Control or XBox Control - Control mode for each robot can be set with each one's drop down menu in the Robot View widget - If we want to run it with real robots: - Open your terminal, `cd` into `Software/src` and run `ifconfig`. - - Pick the network interface you would like to use: - 1. If you are running things locally, you can pick any interface that is not `lo` - 2. If you would like to communicate with robots on the network, make sure to select the interface that is connected to the same network as the robots. + - Pick the network interface you would like to use. If you would like to communicate with robots on the network, make sure to select the interface that is connected to the same network as the robots. - For example, on a sample machine, the output may look like this: ``` - enp0s5: flags=4163 mtu 1500 + wlp3s0: flags=4163 mtu 1500 ... [omitted] ... @@ -239,10 +238,16 @@ Now that you're setup, if you can run it on the command line, you can run it in ``` - An appropriate interface we could choose is `enp0s5` + - Hint: If you are using a wired connection, the interface will likely start with `e-`. If you are using a wifi connection, the interface will likely start with `w-`. - If we are running the AI as "blue": `./tbots.py run thunderscope_main --interface=[interface_here] --run_blue` - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=enp0s5 --run_blue` + - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue` + - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface + - Note: You do not need to include the `--interface=[interface_here]` argument! You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. + - This mode is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". + - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. + - If you have specified `--run_diagnostics`, navigate to the "Network Configuration" widget. Here, you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. - This command will set up robot communication and the Unix full system binary context manager. The Unix full system context manager hooks up our AI, Backend and SensorFusion 2. Run AI along with Robot Diagnostics: - The Mechanical and Electrical sub-teams use Robot Diagnostics to test specific parts of the Robot. diff --git a/src/software/jetson_nano/services/network/network.cpp b/src/software/jetson_nano/services/network/network.cpp index 03a02a3d70..7336251ded 100644 --- a/src/software/jetson_nano/services/network/network.cpp +++ b/src/software/jetson_nano/services/network/network.cpp @@ -5,10 +5,14 @@ NetworkService::NetworkService(const std::string& ip_address, unsigned short robot_status_sender_port, const std::string& interface, bool multicast) : primitive_tracker(ProtoTracker("primitive set")) { + std::optional error; sender = std::make_unique>( - ip_address, robot_status_sender_port, interface, multicast); + ip_address, robot_status_sender_port, interface, multicast, error); + if (error) + { + LOG(FATAL) << *error; + } - std::optional error; udp_listener_primitive_set = std::make_unique>( ip_address, primitive_listener_port, interface, diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index e2195da998..5fc2232e4f 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -27,6 +27,7 @@ def __init__( current_proto_unix_io: ProtoUnixIO, multicast_channel: str, estop_mode: EstopMode, + interface: str = None, estop_path: os.PathLike = None, estop_baudrate: int = 115200, enable_radio: bool = False, @@ -36,6 +37,7 @@ def __init__( :param current_proto_unix_io: the current proto unix io object :param multicast_channel: The multicast channel to use :param estop_mode: what estop mode we are running right now, of type EstopMode + :param interface: The interface to use :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 @@ -83,9 +85,11 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - self.current_network_config = NetworkConfig(robot_status_interface=DISCONNECTED, - vision_interface=DISCONNECTED, - referee_interface=DISCONNECTED) + if interface is None: + interface = DISCONNECTED + self.current_network_config = NetworkConfig(robot_status_interface=interface, + vision_interface=interface, + referee_interface=interface) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index f39490a0f6..a03b4f0f34 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -135,6 +135,13 @@ action="store_true", help="Run robots diagnostics for Manual or Xbox control; estop required", ) + parser.add_argument( + "--interface", + action="store", + type=str, + default=None, + help="Which interface to communicate over", + ) parser.add_argument( "--channel", action="store", @@ -303,6 +310,7 @@ with 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, From 402258817e6d8277bd66d557b172a29fac3017a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 05:33:06 +0000 Subject: [PATCH 24/50] [pre-commit.ci lite] apply automatic fixes --- .../networking/udp/threaded_proto_udp_listener.hpp | 2 +- src/software/thunderscope/robot_communication.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index fa7a8cb10a..caecef4025 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -26,7 +26,7 @@ class ThreadedProtoUdpListener * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network * @param multicast If true, joins the multicast group of given ip_address - * @param errror A user-provided optional string to store any error messages + * @param error A user-provided optional string to store any error messages */ ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index af880676e3..140aa3a928 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -87,9 +87,11 @@ def __init__( if interface is None: interface = DISCONNECTED - self.current_network_config = NetworkConfig(robot_status_interface=interface, - vision_interface=interface, - referee_interface=interface) + self.current_network_config = NetworkConfig( + robot_status_interface=interface, + vision_interface=interface, + referee_interface=interface, + ) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer From 64ad3d4956d11b98fb94138629220ae52a57fef4 Mon Sep 17 00:00:00 2001 From: arun Date: Fri, 28 Jun 2024 23:39:38 -0700 Subject: [PATCH 25/50] formatting + fix CI --- docs/getting-started.md | 2 +- src/software/jetson_nano/thunderloop.cpp | 3 +- src/software/logger/network_sink.cpp | 8 ++- src/software/logger/plotjuggler_sink.cpp | 1 + .../udp/threaded_proto_udp_listener.hpp | 2 +- .../thunderscope/robot_communication.py | 52 +++++++++---------- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 88b7517002..a91a32a1b9 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -243,7 +243,7 @@ Now that you're setup, if you can run it on the command line, you can run it in - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue` - - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface + - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. - Note: You do not need to include the `--interface=[interface_here]` argument! You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. - This mode is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. diff --git a/src/software/jetson_nano/thunderloop.cpp b/src/software/jetson_nano/thunderloop.cpp index a2e60687a3..737aa6ac7f 100644 --- a/src/software/jetson_nano/thunderloop.cpp +++ b/src/software/jetson_nano/thunderloop.cpp @@ -56,9 +56,10 @@ extern "C" crash_msg.set_exit_signal(g3::signalToStr(signal_num)); *(crash_msg.mutable_status()) = *robot_status; + std::optional error; auto sender = std::make_unique>( std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id)), ROBOT_CRASH_PORT, - network_interface, true); + network_interface, true, error); sender->sendProto(crash_msg); std::cerr << "Broadcasting robot crash msg"; diff --git a/src/software/logger/network_sink.cpp b/src/software/logger/network_sink.cpp index f45cffdbfa..742c92dc13 100644 --- a/src/software/logger/network_sink.cpp +++ b/src/software/logger/network_sink.cpp @@ -11,9 +11,15 @@ NetworkSink::NetworkSink(unsigned int channel, const std::string& interface, int bool enable_log_merging) : robot_id(robot_id), log_merger(LogMerger(enable_log_merging)) { + std::optional error; log_output.reset(new ThreadedProtoUdpSender( std::string(ROBOT_MULTICAST_CHANNELS.at(channel)), ROBOT_LOGS_PORT, interface, - true)); + true, error)); + if (error) + { + std::cerr << error.value() << std::endl; + std::terminate(); + } } void NetworkSink::sendToNetwork(g3::LogMessageMover log_entry) diff --git a/src/software/logger/plotjuggler_sink.cpp b/src/software/logger/plotjuggler_sink.cpp index 513a85243a..ea0c6f7e9e 100644 --- a/src/software/logger/plotjuggler_sink.cpp +++ b/src/software/logger/plotjuggler_sink.cpp @@ -11,6 +11,7 @@ PlotJugglerSink::PlotJugglerSink(const std::string& interface) if (error.has_value()) { std::cerr << "Error setting up UDP sender for PlotJugglerSink: " << error.value(); + std::terminate(); } } diff --git a/src/software/networking/udp/threaded_proto_udp_listener.hpp b/src/software/networking/udp/threaded_proto_udp_listener.hpp index fa7a8cb10a..caecef4025 100644 --- a/src/software/networking/udp/threaded_proto_udp_listener.hpp +++ b/src/software/networking/udp/threaded_proto_udp_listener.hpp @@ -26,7 +26,7 @@ class ThreadedProtoUdpListener * @param receive_callback The function to run for every ReceiveProtoT packet received * from the network * @param multicast If true, joins the multicast group of given ip_address - * @param errror A user-provided optional string to store any error messages + * @param error A user-provided optional string to store any error messages */ ThreadedProtoUdpListener(const std::string& ip_address, unsigned short port, const std::string& interface, diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index af880676e3..45f20c9a45 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -8,15 +8,12 @@ from pyqtgraph.Qt import QtCore from software.thunderscope.proto_unix_io import ProtoUnixIO from colorama import Fore, Style -from typing import Type +from typing import Optional, Type import threading import time import os from google.protobuf.message import Message -DISCONNECTED = "DISCONNECTED" -"""A constant to represent a disconnected interface""" - class RobotCommunication(object): @@ -85,11 +82,11 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - if interface is None: - interface = DISCONNECTED - self.current_network_config = NetworkConfig(robot_status_interface=interface, - vision_interface=interface, - referee_interface=interface) + self.current_network_config = NetworkConfig( + robot_status_interface=interface, + vision_interface=interface, + referee_interface=interface, + ) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer @@ -122,8 +119,8 @@ def __init__( def setup_for_fullsystem( self, - referee_interface: str = DISCONNECTED, - vision_interface: str = DISCONNECTED, + referee_interface: Optional[str] = None, + vision_interface: Optional[str] = None, ) -> None: """ Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default @@ -131,12 +128,12 @@ def setup_for_fullsystem( :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data """ - change_referee_interface = ( + change_referee_interface = (referee_interface is not None) and ( referee_interface != self.current_network_config.referee_interface - ) and (referee_interface != DISCONNECTED) - change_vision_interface = ( + ) + change_vision_interface = (vision_interface is not None) and ( vision_interface != self.current_network_config.vision_interface - ) and (vision_interface != DISCONNECTED) + ) if change_vision_interface: @@ -155,7 +152,7 @@ def setup_for_fullsystem( print(f"Error setting up vision interface: {error}") self.current_network_config.vision_interface = ( - vision_interface if not error else DISCONNECTED + vision_interface if not error else None ) if change_referee_interface: @@ -173,9 +170,7 @@ def setup_for_fullsystem( if error: print(f"Error setting up referee interface: {error}") - self.current_network_config.referee_interface = ( - referee_interface if not error else DISCONNECTED - ) + self.current_network_config.referee_interface = None if not self.is_setup_for_fullsystem: self.robots_connected_to_fullsystem = { @@ -185,7 +180,7 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_status_interface: str = "lo" + self, robot_status_interface: Optional[str] = None ) -> None: """ Set up senders and listeners for communicating with the robots @@ -194,8 +189,9 @@ def __setup_for_robot_communication( primitives if using radio """ if ( - robot_status_interface == self.current_network_config.robot_status_interface - or robot_status_interface == DISCONNECTED + robot_status_interface is None + or robot_status_interface + == self.current_network_config.robot_status_interface ): return @@ -236,7 +232,7 @@ def __setup_for_robot_communication( print(f"Error setting up robot status interface: {error}") self.current_network_config.robot_status_interface = ( - robot_status_interface if not error else DISCONNECTED + robot_status_interface if not error else None ) def close_for_fullsystem(self) -> None: @@ -471,17 +467,19 @@ def __print_current_network_config(self) -> None: Prints the current network configuration to the console """ - def output_string(comm_name: str, status: str) -> str: + def output_string(comm_name: str, status: Optional[str]) -> str: """ Returns a formatted string with the communication name and status - Any status other than DISCONNECTED will be coloured green, otherwise red + If status is None, it will be coloured red, otherwise it's green :param comm_name: the name of the communication :param status: the status of the communication """ - colour = Fore.RED if status == DISCONNECTED else Fore.GREEN - return f"{comm_name} {colour}{status} {Style.RESET_ALL}" + colour = Fore.RED if status is None else Fore.GREEN + return f"{comm_name} {colour}{status if status else {0}} {Style.RESET_ALL}".format( + "DISCONNECTED" + ) print( output_string( From 5c0fcb436aecaf98440b197256ffc2427b0979c1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 06:52:54 +0000 Subject: [PATCH 26/50] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/robot_communication.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index af880676e3..140aa3a928 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -87,9 +87,11 @@ def __init__( if interface is None: interface = DISCONNECTED - self.current_network_config = NetworkConfig(robot_status_interface=interface, - vision_interface=interface, - referee_interface=interface) + self.current_network_config = NetworkConfig( + robot_status_interface=interface, + vision_interface=interface, + referee_interface=interface, + ) self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer From ff1e6bc8ab0e08aa6e2a826b96f01b11a7321973 Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 10 Jul 2024 00:41:45 -0700 Subject: [PATCH 27/50] address PR comments --- src/proto/parameters.proto | 8 +- .../common/proto_configuration_widget.py | 6 +- .../thunderscope/robot_communication.py | 89 +++++++++++++------ .../thunderscope/thunderscope_config.py | 9 -- .../thunderscope/widget_setup_functions.py | 22 ----- 5 files changed, 70 insertions(+), 64 deletions(-) diff --git a/src/proto/parameters.proto b/src/proto/parameters.proto index ce9c73e274..057b1ab938 100644 --- a/src/proto/parameters.proto +++ b/src/proto/parameters.proto @@ -441,11 +441,11 @@ message PossessionTrackerConfig message NetworkConfig { - // The referee interface - required string referee_interface = 1 [default = "lo"]; + // The robot communication interface + required string robot_communication_interface = 1 [default = "lo"]; - // The robot status interface - required string robot_status_interface = 2 [default = "lo"]; + // The referee interface + required string referee_interface = 2 [default = "lo"]; // The vision interface required string vision_interface = 3 [default = "lo"]; diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 5c7fef91ba..fcb3c2b021 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -11,15 +11,15 @@ class ProtoConfigurationWidget(QWidget): - DELAYED_CONFIGURATION_TIMEOUT_S = 5 - """How long to wait after startup to send the first configuration to our AI""" - """Creates a searchable parameter widget that can take any protobuf, and convert it into a pyqtgraph ParameterTree. This will allow users to modify the values. """ + DELAYED_CONFIGURATION_TIMEOUT_S = 5 + """How long to wait after startup to send the first configuration to our AI""" + def __init__( self, proto_to_configure, on_change_callback, search_filter_threshold=60, ): diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 140aa3a928..a5116a0481 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -8,7 +8,8 @@ from pyqtgraph.Qt import QtCore from software.thunderscope.proto_unix_io import ProtoUnixIO from colorama import Fore, Style -from typing import Type +import logging +from typing import Any, Callable, Tuple, Type import threading import time import os @@ -17,9 +18,9 @@ DISCONNECTED = "DISCONNECTED" """A constant to represent a disconnected interface""" +logger = logging.getLogger(__name__) class RobotCommunication(object): - """ Communicate with the robots """ def __init__( @@ -85,10 +86,16 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - if interface is None: + # we will be provided a proto configuration from the ProtoConfigurationWidget. If the user provides an + # interface, we will accept it as the first network configuration. If not, we will wait for this configuration + self.accept_next_network_config = True + if interface: + self.accept_next_network_config = False + else: interface = DISCONNECTED + self.current_network_config = NetworkConfig( - robot_status_interface=interface, + robot_communication_interface=interface, vision_interface=interface, referee_interface=interface, ) @@ -96,6 +103,8 @@ def __init__( self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer ) + if self.accept_next_network_config: + self.__setup_for_robot_communication(interface) self.send_estop_state_thread = threading.Thread( target=self.__send_estop_state, daemon=True @@ -122,6 +131,8 @@ def __init__( except Exception: raise Exception(f"Invalid Estop found at location {self.estop_path}") + self.__print_current_network_config() + def setup_for_fullsystem( self, referee_interface: str = DISCONNECTED, @@ -187,58 +198,77 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_status_interface: str = "lo" + self, robot_communication_interface: str = DISCONNECTED ) -> None: """ Set up senders and listeners for communicating with the robots - :param robot_status_interface: the interface to listen/send for robot status data. Ignored for sending + :param robot_communication_interface: the interface to listen/send for robot status data. Ignored for sending primitives if using radio """ if ( - robot_status_interface == self.current_network_config.robot_status_interface - or robot_status_interface == DISCONNECTED + robot_communication_interface == self.current_network_config.robot_communication_interface + or robot_communication_interface == DISCONNECTED ): return + is_listener_setup_successfully = True + + def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: + """ + Sets up a listener with the given creator function. Logs any errors that occur. + + :param listener_creator: the function to create the listener. It must return a type of + (listener object, error) + """ + listener, error = listener_creator() + if error: + is_listener_setup_successfully = False + logger.error(f"Error setting up robot status interface: {error}") + + return listener + + # Create the multicast listeners - self.receive_robot_status, error = tbots_cpp.createRobotStatusProtoListener( + self.receive_robot_status = setup_listener(lambda: tbots_cpp.createRobotStatusProtoListener( self.multicast_channel, ROBOT_STATUS_PORT, - robot_status_interface, + robot_communication_interface, self.__receive_robot_status, True, - ) + )) - self.receive_robot_log, error = tbots_cpp.createRobotLogProtoListener( + self.receive_robot_log = setup_listener(lambda: tbots_cpp.createRobotLogProtoListener( self.multicast_channel, ROBOT_LOGS_PORT, - robot_status_interface, + robot_communication_interface, lambda data: self.__forward_to_proto_unix_io(RobotLog, data), True, - ) + )) + - self.receive_robot_crash, error = tbots_cpp.createRobotCrashProtoListener( + self.receive_robot_crash = setup_listener(lambda: tbots_cpp.createRobotCrashProtoListener( self.multicast_channel, ROBOT_CRASH_PORT, - robot_status_interface, + robot_communication_interface, lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), True, - ) + )) # Create multicast senders if self.enable_radio: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: self.send_primitive_set, error = tbots_cpp.createPrimitiveSetProtoUdpSender( - self.multicast_channel, PRIMITIVE_PORT, robot_status_interface, True + self.multicast_channel, PRIMITIVE_PORT, robot_communication_interface, True ) - if error: - print(f"Error setting up robot status interface: {error}") + if error: + is_listener_setup_successfully = False + print(f"Error setting up primitive set sender: {error}") - self.current_network_config.robot_status_interface = ( - robot_status_interface if not error else DISCONNECTED + self.current_network_config.robot_communication_interface = ( + robot_communication_interface if is_listener_setup_successfully else DISCONNECTED ) def close_for_fullsystem(self) -> None: @@ -341,20 +371,27 @@ def __run_primitive_set(self) -> None: """ network_config = self.network_config_buffer.get( - block=False if self.send_primitive_set else True, return_cached=False + block=True if self.accept_next_network_config else False, return_cached=False ) while self.running: - if network_config is not None: + if network_config is not None and self.accept_next_network_config: print(f"[RobotCommunication] Received new NetworkConfig") + if self.is_setup_for_fullsystem: self.setup_for_fullsystem( referee_interface=network_config.referee_interface, vision_interface=network_config.vision_interface, ) self.__setup_for_robot_communication( - robot_status_interface=network_config.robot_status_interface + robot_communication_interface=network_config.robot_communication_interface ) self.__print_current_network_config() + elif network_config is not None: + logger.warning("[RobotCommunication] We received a proto configuration update with a newer network" + " configuration. We will ignore this update, likely because the interface was provided at startup but" + " the next update will be accepted.") + self.accept_next_network_config = True + self.__print_current_network_config() # Set up network on the next tick network_config = self.network_config_buffer.get( @@ -487,7 +524,7 @@ def output_string(comm_name: str, status: str) -> str: print( output_string( - "Robot Status\t", self.current_network_config.robot_status_interface + "Robot Status\t", self.current_network_config.robot_communication_interface ) ) print(output_string("Vision\t\t", self.current_network_config.vision_interface)) diff --git a/src/software/thunderscope/thunderscope_config.py b/src/software/thunderscope/thunderscope_config.py index 7eb429f89a..28a7c731b3 100644 --- a/src/software/thunderscope/thunderscope_config.py +++ b/src/software/thunderscope/thunderscope_config.py @@ -270,15 +270,6 @@ def configure_base_diagnostics( position="below", anchor="Logs", ), - TScopeWidget( - name="Network Configuration", - widget=setup_network_config_widget( - **{"proto_unix_io": diagnostics_proto_unix_io,} - ), - position="below", - anchor="Error Log", - has_refresh_func=False, - ), TScopeWidget( name="Drive and Dribbler", widget=setup_drive_and_dribbler_widget( diff --git a/src/software/thunderscope/widget_setup_functions.py b/src/software/thunderscope/widget_setup_functions.py index 9b2d851973..96a31f8188 100644 --- a/src/software/thunderscope/widget_setup_functions.py +++ b/src/software/thunderscope/widget_setup_functions.py @@ -223,28 +223,6 @@ def on_change_callback( return ProtoConfigurationWidget(config, on_change_callback) -def setup_network_config_widget(proto_unix_io: ProtoUnixIO) -> ProtoConfigurationWidget: - """Setup the network configuration widget - - :param proto_unix_io: The proto unix io object - - :returns: The proto configuration widget - """ - config = NetworkConfig() - - def on_change_callback(attr: Any, value: Any, updated_proto: NetworkConfig) -> None: - """ - Callback function that sends updated network configuration - - :param attr: The parameter that changed - :param value: The new value - :param updated_proto: The updated network configuration - """ - proto_unix_io.send_proto(NetworkConfig, updated_proto) - - return ProtoConfigurationWidget(config, on_change_callback) - - def setup_log_widget(proto_unix_io: ProtoUnixIO) -> g3logWidget: """Setup the wiget that receives logs from full system From e03965414af06a42821a3d7bd6b3ba6319b2ae57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 07:55:14 +0000 Subject: [PATCH 28/50] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/robot_communication.py | 82 +++++++++++-------- .../thunderscope/thunderscope_main.py | 10 ++- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 58ba7b5431..0d53314b1a 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) + class RobotCommunication(object): """ Communicate with the robots """ @@ -210,7 +211,8 @@ def __setup_for_robot_communication( primitives if using radio """ if ( - robot_communication_interface == self.current_network_config.robot_communication_interface + robot_communication_interface + == self.current_network_config.robot_communication_interface or robot_communication_interface == DISCONNECTED ): return @@ -226,44 +228,50 @@ def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: """ listener, error = listener_creator() if error: - is_listener_setup_successfully = False logger.error(f"Error setting up robot status interface: {error}") return listener - # Create the multicast listeners - self.receive_robot_status = setup_listener(lambda: tbots_cpp.createRobotStatusProtoListener( - self.multicast_channel, - ROBOT_STATUS_PORT, - robot_communication_interface, - self.__receive_robot_status, - True, - )) - - self.receive_robot_log = setup_listener(lambda: tbots_cpp.createRobotLogProtoListener( - self.multicast_channel, - ROBOT_LOGS_PORT, - robot_communication_interface, - lambda data: self.__forward_to_proto_unix_io(RobotLog, data), - True, - )) - - - self.receive_robot_crash = setup_listener(lambda: tbots_cpp.createRobotCrashProtoListener( - self.multicast_channel, - ROBOT_CRASH_PORT, - robot_communication_interface, - lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), - True, - )) + self.receive_robot_status = setup_listener( + lambda: tbots_cpp.createRobotStatusProtoListener( + self.multicast_channel, + ROBOT_STATUS_PORT, + robot_communication_interface, + self.__receive_robot_status, + True, + ) + ) + + self.receive_robot_log = setup_listener( + lambda: tbots_cpp.createRobotLogProtoListener( + self.multicast_channel, + ROBOT_LOGS_PORT, + robot_communication_interface, + lambda data: self.__forward_to_proto_unix_io(RobotLog, data), + True, + ) + ) + + self.receive_robot_crash = setup_listener( + lambda: tbots_cpp.createRobotCrashProtoListener( + self.multicast_channel, + ROBOT_CRASH_PORT, + robot_communication_interface, + lambda data: self.current_proto_unix_io.send_proto(RobotCrash, data), + True, + ) + ) # Create multicast senders if self.enable_radio: self.send_primitive_set = tbots_cpp.PrimitiveSetProtoRadioSender() else: self.send_primitive_set, error = tbots_cpp.createPrimitiveSetProtoUdpSender( - self.multicast_channel, PRIMITIVE_PORT, robot_communication_interface, True + self.multicast_channel, + PRIMITIVE_PORT, + robot_communication_interface, + True, ) if error: @@ -271,7 +279,9 @@ def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: print(f"Error setting up primitive set sender: {error}") self.current_network_config.robot_communication_interface = ( - robot_communication_interface if is_listener_setup_successfully else DISCONNECTED + robot_communication_interface + if is_listener_setup_successfully + else DISCONNECTED ) def close_for_fullsystem(self) -> None: @@ -374,7 +384,8 @@ def __run_primitive_set(self) -> None: """ network_config = self.network_config_buffer.get( - block=True if self.accept_next_network_config else False, return_cached=False + block=True if self.accept_next_network_config else False, + return_cached=False, ) while self.running: if network_config is not None and self.accept_next_network_config: @@ -390,9 +401,11 @@ def __run_primitive_set(self) -> None: ) self.__print_current_network_config() elif network_config is not None: - logger.warning("[RobotCommunication] We received a proto configuration update with a newer network" - " configuration. We will ignore this update, likely because the interface was provided at startup but" - " the next update will be accepted.") + logger.warning( + "[RobotCommunication] We received a proto configuration update with a newer network" + " configuration. We will ignore this update, likely because the interface was provided at startup but" + " the next update will be accepted." + ) self.accept_next_network_config = True self.__print_current_network_config() @@ -527,7 +540,8 @@ def output_string(comm_name: str, status: str) -> str: print( output_string( - "Robot Status\t", self.current_network_config.robot_communication_interface + "Robot Status\t", + self.current_network_config.robot_communication_interface, ) ) print(output_string("Vision\t\t", self.current_network_config.vision_interface)) diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 73b5857070..0a670bc4f1 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -230,11 +230,17 @@ # 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 or --run_yellow argument") + parser.error( + "--launch_gc has to be ran with --run_blue or --run_yellow argument" + ) # Sanity check that an interface was provided if we are running diagnostics since it will not load the network # configuration widget - if not (args.run_blue or args.run_yellow) and args.run_diagnostics and args.interface is None: + if ( + not (args.run_blue or args.run_yellow) + and args.run_diagnostics + and args.interface is None + ): parser.error("Must specify interface") ########################################################################### From 2ef6145268ce862d05d81567338b1aa55f83f5d7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 06:57:30 +0000 Subject: [PATCH 29/50] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/common/proto_configuration_widget.py | 8 ++++---- src/software/thunderscope/robot_communication.py | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/software/thunderscope/common/proto_configuration_widget.py b/src/software/thunderscope/common/proto_configuration_widget.py index 9cefd93fe5..98a53029f8 100644 --- a/src/software/thunderscope/common/proto_configuration_widget.py +++ b/src/software/thunderscope/common/proto_configuration_widget.py @@ -11,9 +11,6 @@ from typing import Any, Callable from PyQt6.QtWidgets import * -from threading import Thread -import time - class ProtoConfigurationWidget(QWidget): """Creates a searchable parameter widget that can take any protobuf, @@ -82,7 +79,10 @@ def __init__( layout.addWidget(self.search_query) layout.addWidget(self.param_tree) - self.run_onetime_async(ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S, self.send_proto_to_fullsystem) + self.run_onetime_async( + ProtoConfigurationWidget.DELAYED_CONFIGURATION_TIMEOUT_S, + self.send_proto_to_fullsystem, + ) def run_onetime_async(self, time_in_seconds: float, func: Callable): """ diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index efa989fb66..fc875e1198 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -544,7 +544,9 @@ def output_string(comm_name: str, status: str) -> str: self.current_network_config.robot_communication_interface, ) ) - logging.info(output_string("Vision\t\t", self.current_network_config.vision_interface)) + logging.info( + output_string("Vision\t\t", self.current_network_config.vision_interface) + ) logging.info( output_string("Referee\t\t", self.current_network_config.referee_interface) ) From 1ab3aa5a931f48e70851a1899eb626445b298afd Mon Sep 17 00:00:00 2001 From: arun Date: Wed, 10 Jul 2024 23:59:41 -0700 Subject: [PATCH 30/50] update getting started --- docs/getting-started.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index a91a32a1b9..372fa63f77 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -242,12 +242,13 @@ Now that you're setup, if you can run it on the command line, you can run it in - If we are running the AI as "blue": `./tbots.py run thunderscope_main --interface=[interface_here] --run_blue` - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue` - - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. - - Note: You do not need to include the `--interface=[interface_here]` argument! You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. - - This mode is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". + - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up comunication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. + - Note: You do not need to include the `--interface=[interface_here]` argument! You can run Thunderscope without it in some cases and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. + - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. + - Using the dynamic configuration widget is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. - - If you have specified `--run_diagnostics`, navigate to the "Network Configuration" widget. Here, you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. + - If you have specified `--run_diagnostics` without `--run_blue` nor `--run_yellow`, the dynamic configuration widget will not load. You will need to set the interface via the `--interface=[interface_here]` argument. + - For example: `./tbots.py run thunderscope_main --interface=[interface_here] --run_diagnostics` - This command will set up robot communication and the Unix full system binary context manager. The Unix full system context manager hooks up our AI, Backend and SensorFusion 2. Run AI along with Robot Diagnostics: - The Mechanical and Electrical sub-teams use Robot Diagnostics to test specific parts of the Robot. From 47f3fa57a42d467673e99b50d0adf0868b1be974 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:04:22 -0700 Subject: [PATCH 31/50] further improve docs --- docs/getting-started.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 372fa63f77..9c912f907c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -247,19 +247,20 @@ Now that you're setup, if you can run it on the command line, you can run it in - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. - Using the dynamic configuration widget is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. - - If you have specified `--run_diagnostics` without `--run_blue` nor `--run_yellow`, the dynamic configuration widget will not load. You will need to set the interface via the `--interface=[interface_here]` argument. - - For example: `./tbots.py run thunderscope_main --interface=[interface_here] --run_diagnostics` - This command will set up robot communication and the Unix full system binary context manager. The Unix full system context manager hooks up our AI, Backend and SensorFusion 2. Run AI along with Robot Diagnostics: - The Mechanical and Electrical sub-teams use Robot Diagnostics to test specific parts of the Robot. - If we want to run with one AI and Diagnostics - - `./tbots.py run thunderscope_main [--run_blue | --run_yellow] --run_diagnostics` will start Thunderscope + - `./tbots.py run thunderscope_main [--run_blue | --run_yellow] --run_diagnostics --interface=[interface_here]` will start Thunderscope - `[--run_blue | --run_yellow]` indicate which FullSystem to run - `--run_diagnostics` indicates if diagnostics should be loaded as well - Initially, the robots are all connected to the AI and only receive input from it - To change the input source for the robot, use the drop-down menu of that robot to change it between None, AI, and Manual - None means the robots are receiving no commands - More info about Manual control below + - `--interface=[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step + - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue --run_diagnostics` + - The `--interface` flag is optional. If you do not include it, you can set the interface in the dynamic configuration widget. See above for how to set the interface in the dynamic configuration widget. 3. Run only Diagnostics - To run just Diagnostics - `./tbots.py run thunderscope --run_diagnostics --interface ` From 286d95688282fa76f26ee298eae9a531b0260074 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:07:38 -0700 Subject: [PATCH 32/50] doc cleanup --- docs/getting-started.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 9c912f907c..ad4068e451 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -216,7 +216,6 @@ Now that you're setup, if you can run it on the command line, you can run it in - `[--run_diagnostics]` indicates if diagnostics should be loaded as well - If FullSystem is running, the robots receive input from the AI - If Diagnostics is enabled, the robots can also receive input from Manual controls or XBox controls - - Note: If you simply want to run diagnostics, you do not need to specify `--run_blue` or `--run_yellow` - This mode allows us to test and debug the robots by setting each robot's input to be either AI, Manual Control or XBox Control - Control mode for each robot can be set with each one's drop down menu in the Robot View widget @@ -238,12 +237,12 @@ Now that you're setup, if you can run it on the command line, you can run it in ``` - An appropriate interface we could choose is `enp0s5` - - Hint: If you are using a wired connection, the interface will likely start with `e-`. If you are using a wifi connection, the interface will likely start with `w-`. + - Hint: If you are using a wired connection, the interface will likely start with `e-`. If you are using a WiFi connection, the interface will likely start with `w-`. - If we are running the AI as "blue": `./tbots.py run thunderscope_main --interface=[interface_here] --run_blue` - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up comunication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. - - Note: You do not need to include the `--interface=[interface_here]` argument! You can run Thunderscope without it in some cases and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. + - For instance, a call to run the AI as blue on WiFi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up comunication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. + - **Note: You do not need to include the `--interface=[interface_here]` argument!** You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. - Using the dynamic configuration widget is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. @@ -259,7 +258,7 @@ Now that you're setup, if you can run it on the command line, you can run it in - None means the robots are receiving no commands - More info about Manual control below - `--interface=[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - - For instance, a call to run the AI as blue on wifi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue --run_diagnostics` + - For instance, a call to run the AI as blue on WiFi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue --run_diagnostics` - The `--interface` flag is optional. If you do not include it, you can set the interface in the dynamic configuration widget. See above for how to set the interface in the dynamic configuration widget. 3. Run only Diagnostics - To run just Diagnostics From 438951c532bdfc6c0e972fd92f38e47723ce5e40 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:10:12 -0700 Subject: [PATCH 33/50] simplify getting_started more --- docs/getting-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index ad4068e451..fac7904924 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -236,14 +236,14 @@ Now that you're setup, if you can run it on the command line, you can run it in ... ``` - - An appropriate interface we could choose is `enp0s5` + - An appropriate interface we could choose is `wlp3s0` - Hint: If you are using a wired connection, the interface will likely start with `e-`. If you are using a WiFi connection, the interface will likely start with `w-`. - If we are running the AI as "blue": `./tbots.py run thunderscope_main --interface=[interface_here] --run_blue` - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - For instance, a call to run the AI as blue on WiFi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up comunication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. - **Note: You do not need to include the `--interface=[interface_here]` argument!** You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. - - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port. If you include `--run_blue` or `--run_yellow`, Thunderscope will also listen for vision messages and referee messages on this interface. + - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port as well as receive vision and referee messages. - Using the dynamic configuration widget is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". - If you have specified `--run_blue` or `--run_yellow`, navigate to the "Parameters" widget. In "ai_config" > "ai_control_config" > "network_config", you can set the appropriate interface using the dropdowns for robot, vision and referee message communication. - This command will set up robot communication and the Unix full system binary context manager. The Unix full system context manager hooks up our AI, Backend and SensorFusion From 1feeb214ceeeb6bbd3db014e3e83cb663743f346 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:12:21 -0700 Subject: [PATCH 34/50] remove unnecessary socket closing --- src/software/thunderscope/robot_communication.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index fc875e1198..305ef74b3c 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -284,13 +284,6 @@ def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: else DISCONNECTED ) - def close_for_fullsystem(self) -> None: - if self.receive_ssl_wrapper: - self.receive_ssl_wrapper.close() - - if self.receive_ssl_referee_proto: - self.receive_ssl_referee_proto.close() - def toggle_keyboard_estop(self) -> None: """ If keyboard estop is being used, toggles the estop state @@ -515,10 +508,6 @@ def __exit__(self, type, value, traceback) -> None: """ self.running = False - self.close_for_fullsystem() - - self.receive_robot_log.close() - self.receive_robot_status.close() self.run_primitive_set_thread.join() def __print_current_network_config(self) -> None: From 7fa3752433555b43aacc03d684952404a69fa56b Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:38:58 -0700 Subject: [PATCH 35/50] cleanup edge case handling of robot_communication binding --- .../thunderscope/robot_communication.py | 64 +++++++++++-------- .../thunderscope/thunderscope_main.py | 3 +- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 305ef74b3c..2e4fe0e18b 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -9,7 +9,7 @@ from software.thunderscope.proto_unix_io import ProtoUnixIO from colorama import Fore, Style import logging -from typing import Any, Callable, Tuple, Type +from typing import Any, Callable, Optional, Tuple, Type import threading import time import os @@ -108,8 +108,9 @@ def __init__( self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer ) - if self.accept_next_network_config: - self.__setup_for_robot_communication(interface) + # We will ignore the first network configuration update only if the interface is provided + if not self.accept_next_network_config: + self.__setup_for_robot_communication(interface, True) self.send_estop_state_thread = threading.Thread( target=self.__send_estop_state, daemon=True @@ -136,25 +137,35 @@ def __init__( except Exception: raise Exception(f"Invalid Estop found at location {self.estop_path}") - self.__print_current_network_config() + self.print_current_network_config() def setup_for_fullsystem( self, - referee_interface: str = DISCONNECTED, - vision_interface: str = DISCONNECTED, + referee_interface: Optional[str] = None, + vision_interface: Optional[str] = None, + force_reconnect: bool = False, ) -> None: """ - Sets up a listener for SSL vision and referee data, and connects all robots to fullsystem as default + Sets up a listener for SSL vision and referee data :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data + :param force_reconnect: whether to force a reconnection regardless of whether our stored state says we are + already connected """ - change_referee_interface = ( - referee_interface != self.current_network_config.referee_interface - ) and (referee_interface != DISCONNECTED) - change_vision_interface = ( - vision_interface != self.current_network_config.vision_interface - ) and (vision_interface != DISCONNECTED) + new_referee_interface = referee_interface if referee_interface else self.current_network_config.referee_interface + new_vision_interface = vision_interface if vision_interface else self.current_network_config.vision_interface + + change_referee_interface = (( + new_referee_interface != self.current_network_config.referee_interface + ) and (new_referee_interface != DISCONNECTED)) + change_vision_interface = (( + new_vision_interface != self.current_network_config.vision_interface + ) and (new_vision_interface != DISCONNECTED)) + + if force_reconnect: + change_referee_interface = True + change_vision_interface = True if change_vision_interface: ( @@ -163,7 +174,7 @@ def setup_for_fullsystem( ) = tbots_cpp.createSSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, - vision_interface, + new_vision_interface, lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), True, ) @@ -172,7 +183,7 @@ def setup_for_fullsystem( logger.error(f"Error setting up vision interface:\n{error}") self.current_network_config.vision_interface = ( - vision_interface if not error else DISCONNECTED + new_vision_interface if not error else DISCONNECTED ) if change_referee_interface: @@ -182,7 +193,7 @@ def setup_for_fullsystem( ) = tbots_cpp.createSSLRefereeProtoListener( SSL_REFEREE_ADDRESS, self.referee_port, - referee_interface, + new_referee_interface, lambda data: self.__forward_to_proto_unix_io(Referee, data), True, ) @@ -191,7 +202,7 @@ def setup_for_fullsystem( logger.error(f"Error setting up referee interface:\n{error}") self.current_network_config.referee_interface = ( - referee_interface if not error else DISCONNECTED + new_referee_interface if not error else DISCONNECTED ) if not self.is_setup_for_fullsystem: @@ -202,18 +213,21 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_communication_interface: str = DISCONNECTED + self, robot_communication_interface: str = DISCONNECTED, force_reconnect: bool = False ) -> None: """ Set up senders and listeners for communicating with the robots :param robot_communication_interface: the interface to listen/send for robot status data. Ignored for sending primitives if using radio + :param force_reconnect: whether to force a reconnection regardless of whether our stored state says we are + already connected """ if ( - robot_communication_interface + (robot_communication_interface == self.current_network_config.robot_communication_interface - or robot_communication_interface == DISCONNECTED + or robot_communication_interface == DISCONNECTED) + and not force_reconnect ): return @@ -392,7 +406,7 @@ def __run_primitive_set(self) -> None: self.__setup_for_robot_communication( robot_communication_interface=network_config.robot_communication_interface ) - self.__print_current_network_config() + self.print_current_network_config() elif network_config is not None: logger.warning( "[RobotCommunication] We received a proto configuration update with a newer network" @@ -400,7 +414,7 @@ def __run_primitive_set(self) -> None: " the next update will be accepted." ) self.accept_next_network_config = True - self.__print_current_network_config() + self.print_current_network_config() # Set up network on the next tick network_config = self.network_config_buffer.get( @@ -510,7 +524,7 @@ def __exit__(self, type, value, traceback) -> None: self.run_primitive_set_thread.join() - def __print_current_network_config(self) -> None: + def print_current_network_config(self) -> None: """ Prints the current network configuration to the console """ @@ -534,8 +548,8 @@ def output_string(comm_name: str, status: str) -> str: ) ) logging.info( - output_string("Vision\t\t", self.current_network_config.vision_interface) + output_string("Vision\t\t", self.current_network_config.vision_interface if self.is_setup_for_fullsystem else DISCONNECTED) ) logging.info( - output_string("Referee\t\t", self.current_network_config.referee_interface) + output_string("Referee\t\t", self.current_network_config.referee_interface if self.is_setup_for_fullsystem else DISCONNECTED) ) diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 0a670bc4f1..aa88116570 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -362,7 +362,8 @@ ) if args.run_blue or args.run_yellow: - robot_communication.setup_for_fullsystem() + robot_communication.setup_for_fullsystem(force_reconnect=True) + robot_communication.print_current_network_config() full_system_runtime_dir = ( args.blue_full_system_runtime_dir if args.run_blue From 4123cf4856e2ac1bd7adcce8ce0d95aa6a5ac883 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 07:47:53 +0000 Subject: [PATCH 36/50] [pre-commit.ci lite] apply automatic fixes --- .../thunderscope/robot_communication.py | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 2e4fe0e18b..1d1ef39e30 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -153,15 +153,23 @@ def setup_for_fullsystem( :param force_reconnect: whether to force a reconnection regardless of whether our stored state says we are already connected """ - new_referee_interface = referee_interface if referee_interface else self.current_network_config.referee_interface - new_vision_interface = vision_interface if vision_interface else self.current_network_config.vision_interface + new_referee_interface = ( + referee_interface + if referee_interface + else self.current_network_config.referee_interface + ) + new_vision_interface = ( + vision_interface + if vision_interface + else self.current_network_config.vision_interface + ) - change_referee_interface = (( + change_referee_interface = ( new_referee_interface != self.current_network_config.referee_interface - ) and (new_referee_interface != DISCONNECTED)) - change_vision_interface = (( + ) and (new_referee_interface != DISCONNECTED) + change_vision_interface = ( new_vision_interface != self.current_network_config.vision_interface - ) and (new_vision_interface != DISCONNECTED)) + ) and (new_vision_interface != DISCONNECTED) if force_reconnect: change_referee_interface = True @@ -213,7 +221,9 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_communication_interface: str = DISCONNECTED, force_reconnect: bool = False + self, + robot_communication_interface: str = DISCONNECTED, + force_reconnect: bool = False, ) -> None: """ Set up senders and listeners for communicating with the robots @@ -224,11 +234,10 @@ def __setup_for_robot_communication( already connected """ if ( - (robot_communication_interface + robot_communication_interface == self.current_network_config.robot_communication_interface - or robot_communication_interface == DISCONNECTED) - and not force_reconnect - ): + or robot_communication_interface == DISCONNECTED + ) and not force_reconnect: return is_listener_setup_successfully = True @@ -548,8 +557,18 @@ def output_string(comm_name: str, status: str) -> str: ) ) logging.info( - output_string("Vision\t\t", self.current_network_config.vision_interface if self.is_setup_for_fullsystem else DISCONNECTED) + output_string( + "Vision\t\t", + self.current_network_config.vision_interface + if self.is_setup_for_fullsystem + else DISCONNECTED, + ) ) logging.info( - output_string("Referee\t\t", self.current_network_config.referee_interface if self.is_setup_for_fullsystem else DISCONNECTED) + output_string( + "Referee\t\t", + self.current_network_config.referee_interface + if self.is_setup_for_fullsystem + else DISCONNECTED, + ) ) From 326fa6317d3f683bf16dbc206349546322e0ff3a Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:48:44 -0700 Subject: [PATCH 37/50] cleanup edge cases --- src/software/thunderscope/robot_communication.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 2e4fe0e18b..bafe3f7a16 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -164,8 +164,8 @@ def setup_for_fullsystem( ) and (new_vision_interface != DISCONNECTED)) if force_reconnect: - change_referee_interface = True - change_vision_interface = True + change_referee_interface = True if new_referee_interface != DISCONNECTED else change_referee_interface + change_vision_interface = True if new_vision_interface != DISCONNECTED else change_vision_interface if change_vision_interface: ( @@ -213,7 +213,7 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_communication_interface: str = DISCONNECTED, force_reconnect: bool = False + self, robot_communication_interface: str, force_reconnect: bool = False ) -> None: """ Set up senders and listeners for communicating with the robots @@ -225,9 +225,8 @@ def __setup_for_robot_communication( """ if ( (robot_communication_interface - == self.current_network_config.robot_communication_interface - or robot_communication_interface == DISCONNECTED) - and not force_reconnect + == self.current_network_config.robot_communication_interface and not force_reconnect) + or (robot_communication_interface == DISCONNECTED) ): return From 8c358a1ac811f2e063a69c1cd3c0ca04606ac6f7 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 00:51:55 -0700 Subject: [PATCH 38/50] formatting --- docs/getting-started.md | 2 +- .../thunderscope/robot_communication.py | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index fac7904924..02a82db236 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -241,7 +241,7 @@ Now that you're setup, if you can run it on the command line, you can run it in - If we are running the AI as "blue": `./tbots.py run thunderscope_main --interface=[interface_here] --run_blue` - If we are running the AI as "yellow": `./tbots.py run thunderscope_main --interface=[interface_here] --run_yellow` - `[interface_here]` corresponds to the `ifconfig` interfaces seen in the previous step - - For instance, a call to run the AI as blue on WiFi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up comunication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. + - For instance, a call to run the AI as blue on WiFi could be: `./tbots.py run thunderscope_main --interface=wlp3s0 --run_blue`. This will start Thunderscope and set up communication with robots over the wifi interface. It will also listen for referee and vision messages on the same interface. - **Note: You do not need to include the `--interface=[interface_here]` argument!** You can run Thunderscope without it and use the dynamic configuration widget to set the interfaces for communication to send and receive robot, vision and referee messages. - If you choose to include `--interface=[interface_here]` argument, Thunderscope will listen for and send robot messages on this port as well as receive vision and referee messages. - Using the dynamic configuration widget is recommended at Robocup. To reduce latencies, it is recommended to connect the robot router to the AI computer via ethernet and use a separate ethernet connection to receive vision and referee messages. In this configuration, Thunderscope will need to bind to two different interfaces, each likely starting with a "e-". diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 061067c9e8..974fe5bf87 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -172,8 +172,16 @@ def setup_for_fullsystem( ) and (new_vision_interface != DISCONNECTED) if force_reconnect: - change_referee_interface = True if new_referee_interface != DISCONNECTED else change_referee_interface - change_vision_interface = True if new_vision_interface != DISCONNECTED else change_vision_interface + change_referee_interface = ( + True + if new_referee_interface != DISCONNECTED + else change_referee_interface + ) + change_vision_interface = ( + True + if new_vision_interface != DISCONNECTED + else change_vision_interface + ) if change_vision_interface: ( @@ -221,9 +229,7 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, - robot_communication_interface: str, - force_reconnect: bool = False, + self, robot_communication_interface: str, force_reconnect: bool = False, ) -> None: """ Set up senders and listeners for communicating with the robots @@ -234,10 +240,10 @@ def __setup_for_robot_communication( already connected """ if ( - (robot_communication_interface - == self.current_network_config.robot_communication_interface and not force_reconnect) - or (robot_communication_interface == DISCONNECTED) - ): + robot_communication_interface + == self.current_network_config.robot_communication_interface + and not force_reconnect + ) or (robot_communication_interface == DISCONNECTED): return is_listener_setup_successfully = True From 5678e3c1d4e7c592444d2db012d8911a834ace8a Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 01:13:31 -0700 Subject: [PATCH 39/50] cleanup logic --- .../thunderscope/robot_communication.py | 69 +++++++------------ .../thunderscope/thunderscope_main.py | 8 ++- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 974fe5bf87..a79fa8977a 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -9,7 +9,7 @@ from software.thunderscope.proto_unix_io import ProtoUnixIO from colorama import Fore, Style import logging -from typing import Any, Callable, Optional, Tuple, Type +from typing import Any, Callable, Tuple, Type import threading import time import os @@ -40,7 +40,7 @@ def __init__( :param current_proto_unix_io: the current proto unix io object :param multicast_channel: The multicast channel to use :param estop_mode: what estop mode we are running right now, of type EstopMode - :param interface: The interface to use + :param interface: The interface to use for communication with the robots :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 @@ -91,19 +91,19 @@ def __init__( PowerControl, self.power_control_diagnostics_buffer ) - # we will be provided a proto configuration from the ProtoConfigurationWidget. If the user provides an - # interface, we will accept it as the first network configuration. If not, we will wait for this configuration + # Whether to accept the next configuration update. We will be provided a proto configuration from the + # ProtoConfigurationWidget. If the user provides an interface, we will accept it as the first network + # configuration and ignore the provided one from the widget. If not, we will wait for this first configuration self.accept_next_network_config = True - if interface: - self.accept_next_network_config = False - else: - interface = DISCONNECTED - self.current_network_config = NetworkConfig( - robot_communication_interface=interface, - vision_interface=interface, - referee_interface=interface, + robot_communication_interface=DISCONNECTED, + referee_interface=DISCONNECTED, + vision_interface=DISCONNECTED, ) + if interface: + self.accept_next_network_config = False + self.__setup_for_robot_communication(interface) + self.current_network_config.robot_communication_interface = interface self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer @@ -140,47 +140,30 @@ def __init__( self.print_current_network_config() def setup_for_fullsystem( - self, - referee_interface: Optional[str] = None, - vision_interface: Optional[str] = None, - force_reconnect: bool = False, + self, referee_interface: str, vision_interface: str, ) -> None: """ Sets up a listener for SSL vision and referee data :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data - :param force_reconnect: whether to force a reconnection regardless of whether our stored state says we are already connected """ - new_referee_interface = ( - referee_interface - if referee_interface - else self.current_network_config.referee_interface - ) - new_vision_interface = ( - vision_interface - if vision_interface - else self.current_network_config.vision_interface - ) - + # Check cache to see if we're already connected change_referee_interface = ( - new_referee_interface != self.current_network_config.referee_interface - ) and (new_referee_interface != DISCONNECTED) + referee_interface != self.current_network_config.referee_interface + ) and (referee_interface != DISCONNECTED) change_vision_interface = ( - new_vision_interface != self.current_network_config.vision_interface - ) and (new_vision_interface != DISCONNECTED) + vision_interface != self.current_network_config.vision_interface + ) and (vision_interface != DISCONNECTED) - if force_reconnect: + # If we haven't been setup for fullsystem yet, we need to connect regardless of cache state + if not self.is_setup_for_fullsystem: change_referee_interface = ( - True - if new_referee_interface != DISCONNECTED - else change_referee_interface + True if referee_interface != DISCONNECTED else change_referee_interface ) change_vision_interface = ( - True - if new_vision_interface != DISCONNECTED - else change_vision_interface + True if vision_interface != DISCONNECTED else change_vision_interface ) if change_vision_interface: @@ -190,7 +173,7 @@ def setup_for_fullsystem( ) = tbots_cpp.createSSLWrapperPacketProtoListener( SSL_VISION_ADDRESS, SSL_VISION_PORT, - new_vision_interface, + vision_interface, lambda data: self.__forward_to_proto_unix_io(SSL_WrapperPacket, data), True, ) @@ -199,7 +182,7 @@ def setup_for_fullsystem( logger.error(f"Error setting up vision interface:\n{error}") self.current_network_config.vision_interface = ( - new_vision_interface if not error else DISCONNECTED + vision_interface if not error else DISCONNECTED ) if change_referee_interface: @@ -209,7 +192,7 @@ def setup_for_fullsystem( ) = tbots_cpp.createSSLRefereeProtoListener( SSL_REFEREE_ADDRESS, self.referee_port, - new_referee_interface, + referee_interface, lambda data: self.__forward_to_proto_unix_io(Referee, data), True, ) @@ -218,7 +201,7 @@ def setup_for_fullsystem( logger.error(f"Error setting up referee interface:\n{error}") self.current_network_config.referee_interface = ( - new_referee_interface if not error else DISCONNECTED + referee_interface if not error else DISCONNECTED ) if not self.is_setup_for_fullsystem: diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index aa88116570..d5b6ddd49a 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -4,6 +4,7 @@ import os import sys import threading +from robot_communication import DISCONNECTED from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.thunderscope.thunderscope import Thunderscope from software.thunderscope.binary_context_managers import * @@ -362,7 +363,12 @@ ) if args.run_blue or args.run_yellow: - robot_communication.setup_for_fullsystem(force_reconnect=True) + robot_communication.setup_for_fullsystem( + referee_interface=args.interface + if args.interface + else DISCONNECTED, + vision_interface=args.interface if args.interface else DISCONNECTED, + ) robot_communication.print_current_network_config() full_system_runtime_dir = ( args.blue_full_system_runtime_dir From 78c2504e4779fd4671336f89a8ad3c5fd5255a1f Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 01:15:45 -0700 Subject: [PATCH 40/50] simplify logic for selecting interface --- src/software/thunderscope/robot_communication.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index a79fa8977a..dd069226a2 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -110,7 +110,7 @@ def __init__( ) # We will ignore the first network configuration update only if the interface is provided if not self.accept_next_network_config: - self.__setup_for_robot_communication(interface, True) + self.__setup_for_robot_communication(interface) self.send_estop_state_thread = threading.Thread( target=self.__send_estop_state, daemon=True @@ -147,7 +147,6 @@ def setup_for_fullsystem( :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data - already connected """ # Check cache to see if we're already connected change_referee_interface = ( @@ -212,20 +211,17 @@ def setup_for_fullsystem( self.is_setup_for_fullsystem = True def __setup_for_robot_communication( - self, robot_communication_interface: str, force_reconnect: bool = False, + self, robot_communication_interface: str ) -> None: """ Set up senders and listeners for communicating with the robots :param robot_communication_interface: the interface to listen/send for robot status data. Ignored for sending primitives if using radio - :param force_reconnect: whether to force a reconnection regardless of whether our stored state says we are - already connected """ if ( robot_communication_interface == self.current_network_config.robot_communication_interface - and not force_reconnect ) or (robot_communication_interface == DISCONNECTED): return From 13db8e6be98816601637214f2d5c0611aea94885 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 01:19:18 -0700 Subject: [PATCH 41/50] further simplify selection logic --- src/software/thunderscope/robot_communication.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index dd069226a2..de72f209ff 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -156,15 +156,6 @@ def setup_for_fullsystem( vision_interface != self.current_network_config.vision_interface ) and (vision_interface != DISCONNECTED) - # If we haven't been setup for fullsystem yet, we need to connect regardless of cache state - if not self.is_setup_for_fullsystem: - change_referee_interface = ( - True if referee_interface != DISCONNECTED else change_referee_interface - ) - change_vision_interface = ( - True if vision_interface != DISCONNECTED else change_vision_interface - ) - if change_vision_interface: ( self.receive_ssl_wrapper, From 78a72037c104505b71dabd65645eeb1d62d76199 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 01:20:15 -0700 Subject: [PATCH 42/50] remove redundant caching --- src/software/thunderscope/robot_communication.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index de72f209ff..c7607698ac 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -103,7 +103,6 @@ def __init__( if interface: self.accept_next_network_config = False self.__setup_for_robot_communication(interface) - self.current_network_config.robot_communication_interface = interface self.network_config_buffer = ThreadSafeBuffer(1, NetworkConfig) self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer From e16126fbe560e65d8a89608d9157b8871ce95817 Mon Sep 17 00:00:00 2001 From: arun Date: Thu, 11 Jul 2024 01:21:07 -0700 Subject: [PATCH 43/50] delete duplicated code --- src/software/thunderscope/robot_communication.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index c7607698ac..7a2188322f 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -107,9 +107,6 @@ def __init__( self.current_proto_unix_io.register_observer( NetworkConfig, self.network_config_buffer ) - # We will ignore the first network configuration update only if the interface is provided - if not self.accept_next_network_config: - self.__setup_for_robot_communication(interface) self.send_estop_state_thread = threading.Thread( target=self.__send_estop_state, daemon=True From 3219b971229e93a4e9ab33eba3503758eb753163 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 20:09:51 +0000 Subject: [PATCH 44/50] [pre-commit.ci lite] apply automatic fixes --- .../common/proto_parameter_tree_util.py | 3 +-- .../thunderscope/robot_communication.py | 23 ++++++++----------- .../thunderscope/thunderscope_main.py | 1 - 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/software/thunderscope/common/proto_parameter_tree_util.py b/src/software/thunderscope/common/proto_parameter_tree_util.py index 1c01d5438b..5f15ceacd0 100644 --- a/src/software/thunderscope/common/proto_parameter_tree_util.py +++ b/src/software/thunderscope/common/proto_parameter_tree_util.py @@ -137,8 +137,7 @@ def __create_parameter_read_only(key, value, descriptor): def __create_network_enum(key, value, _): - """ - Lists all the network interfaces available on the system as enum options for the given parameter field. + """Lists all the network interfaces available on the system as enum options for the given parameter field. :param key: The name of the parameter :param value: The default value diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index 79433c584b..6e51c3be90 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) + class RobotCommunication: """Communicate with the robots""" @@ -134,10 +135,11 @@ def __init__( self.print_current_network_config() def setup_for_fullsystem( - self, referee_interface: str, vision_interface: str, + self, + referee_interface: str, + vision_interface: str, ) -> None: - """ - Sets up a listener for SSL vision and referee data + """Sets up a listener for SSL vision and referee data :param referee_interface: the interface to listen for referee data :param vision_interface: the interface to listen for vision data @@ -198,8 +200,7 @@ def setup_for_fullsystem( def __setup_for_robot_communication( self, robot_communication_interface: str ) -> None: - """ - Set up senders and listeners for communicating with the robots + """Set up senders and listeners for communicating with the robots :param robot_communication_interface: the interface to listen/send for robot status data. Ignored for sending primitives if using radio @@ -213,8 +214,7 @@ def __setup_for_robot_communication( is_listener_setup_successfully = True def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: - """ - Sets up a listener with the given creator function. Logs any errors that occur. + """Sets up a listener with the given creator function. Logs any errors that occur. :param listener_creator: the function to create the listener. It must return a type of (listener object, error) @@ -371,7 +371,7 @@ def __run_primitive_set(self) -> None: ) while self.running: if network_config is not None and self.accept_next_network_config: - logging.info(f"[RobotCommunication] Received new NetworkConfig") + logging.info("[RobotCommunication] Received new NetworkConfig") if self.is_setup_for_fullsystem: self.setup_for_fullsystem( @@ -500,13 +500,10 @@ def __exit__(self, type, value, traceback) -> None: self.run_primitive_set_thread.join() def print_current_network_config(self) -> None: - """ - Prints the current network configuration to the console - """ + """Prints the current network configuration to the console""" def output_string(comm_name: str, status: str) -> str: - """ - Returns a formatted string with the communication name and status + """Returns a formatted string with the communication name and status Any status other than DISCONNECTED will be coloured green, otherwise red diff --git a/src/software/thunderscope/thunderscope_main.py b/src/software/thunderscope/thunderscope_main.py index 0d9c111f38..b0b9287f1c 100644 --- a/src/software/thunderscope/thunderscope_main.py +++ b/src/software/thunderscope/thunderscope_main.py @@ -5,7 +5,6 @@ import sys import threading from robot_communication import DISCONNECTED -from software.thunderscope.thread_safe_buffer import ThreadSafeBuffer from software.thunderscope.thunderscope import Thunderscope from software.thunderscope.binary_context_managers import * from proto.import_all_protos import * From 89fc6b9d9a4588a6473037da8bb5125948a9a27b Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 3 Sep 2024 12:15:40 -0400 Subject: [PATCH 45/50] address PR comments --- src/software/networking/udp/network_utils.cpp | 10 ++++------ src/software/networking/udp/network_utils.h | 6 +++--- src/software/networking/udp/network_utils_test.cpp | 9 ++++----- src/software/networking/udp/proto_udp_listener.hpp | 6 +++--- src/software/networking/udp/udp_sender.cpp | 6 +++--- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/software/networking/udp/network_utils.cpp b/src/software/networking/udp/network_utils.cpp index 88a47e7772..b8f270bc23 100644 --- a/src/software/networking/udp/network_utils.cpp +++ b/src/software/networking/udp/network_utils.cpp @@ -2,7 +2,7 @@ #include -bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4) +std::optional getLocalIp(const std::string& interface, bool ipv4) { struct ifaddrs* ifAddrStruct = nullptr; struct ifaddrs* ifa = nullptr; @@ -19,8 +19,7 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 struct sockaddr_in* sa = (struct sockaddr_in*)ifa->ifa_addr; inet_ntop(AF_INET, &sa->sin_addr, addressBuffer, INET_ADDRSTRLEN); freeifaddrs(ifAddrStruct); - ip_address = addressBuffer; - return true; + return addressBuffer; } else if (!ipv4 && ifa->ifa_addr->sa_family == AF_INET6) { @@ -28,13 +27,12 @@ bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 struct sockaddr_in6* sa = (struct sockaddr_in6*)ifa->ifa_addr; inet_ntop(AF_INET6, &sa->sin6_addr, addressBuffer, INET6_ADDRSTRLEN); freeifaddrs(ifAddrStruct); - ip_address = addressBuffer; - return true; + return addressBuffer; } } } - return false; + return std::nullopt; } bool isIpv6(const std::string& ip_address) diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 2d6744b70a..8ddd7edf68 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -1,7 +1,7 @@ #pragma once #include - +#include #include /** @@ -14,9 +14,9 @@ * found * @param ipv4 If true, get the IPv4 address, otherwise get the IPv6 address * - * @return true if the IP address was found, false otherwise + * @return IP address if it was found, otherwise nullopt */ -bool getLocalIp(const std::string& interface, std::string& ip_address, bool ipv4 = true); +std::optional getLocalIp(const std::string& interface, bool ipv4 = true); /** * Check if the given string follows the IPv6 address format diff --git a/src/software/networking/udp/network_utils_test.cpp b/src/software/networking/udp/network_utils_test.cpp index dff9fc2ad2..4f16ac530c 100644 --- a/src/software/networking/udp/network_utils_test.cpp +++ b/src/software/networking/udp/network_utils_test.cpp @@ -5,16 +5,15 @@ TEST(NetworkUtilsTest, getLocalIpValidInterface) { std::string interface = "lo"; - std::string ip_address; - EXPECT_TRUE(getLocalIp(interface, ip_address, true)); - EXPECT_EQ(ip_address, "127.0.0.1"); + std::optional local_ip = getLocalIp(interface, true); + EXPECT_TRUE(local_ip); + EXPECT_EQ(local_ip.value(), "127.0.0.1"); } TEST(NetworkUtilsTest, getLocalIpInvalidInterface) { std::string interface = "interfaceymcinterfaceface"; - std::string ip_address; - EXPECT_FALSE(getLocalIp(interface, ip_address, true)); + EXPECT_FALSE(getLocalIp(interface, true)); } TEST(NetworkUtilsTest, isIpv6Valid) diff --git a/src/software/networking/udp/proto_udp_listener.hpp b/src/software/networking/udp/proto_udp_listener.hpp index 391d31e7b6..78af7a9b49 100644 --- a/src/software/networking/udp/proto_udp_listener.hpp +++ b/src/software/networking/udp/proto_udp_listener.hpp @@ -247,8 +247,8 @@ void ProtoUdpListener::setupMulticast( { if (ip_address.is_v4()) { - std::string interface_ip; - if (!getLocalIp(listen_interface, interface_ip)) + std::optional interface_ip = getLocalIp(listen_interface); + if (!interface_ip) { std::stringstream ss; ss << "Could not find the local ip address for the given interface: " @@ -258,7 +258,7 @@ void ProtoUdpListener::setupMulticast( } socket_.set_option(boost::asio::ip::multicast::join_group( ip_address.to_v4(), - boost::asio::ip::address::from_string(interface_ip).to_v4())); + boost::asio::ip::address::from_string(interface_ip.value()).to_v4())); return; } socket_.set_option(boost::asio::ip::multicast::join_group(ip_address)); diff --git a/src/software/networking/udp/udp_sender.cpp b/src/software/networking/udp/udp_sender.cpp index f8be80d5cf..271a6ec2d3 100644 --- a/src/software/networking/udp/udp_sender.cpp +++ b/src/software/networking/udp/udp_sender.cpp @@ -37,8 +37,8 @@ void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, { if (ip_address.is_v4()) { - std::string interface_ip; - if (!getLocalIp(interface, interface_ip)) + std::optional interface_ip = getLocalIp(interface); + if (!interface_ip.has_value()) { std::stringstream ss; ss << "UdpSender: Could not get the local IP address for the interface " @@ -51,7 +51,7 @@ void UdpSender::setupMulticast(const boost::asio::ip::address& ip_address, socket_.set_option(boost::asio::ip::multicast::join_group( ip_address.to_v4(), - boost::asio::ip::address::from_string(interface_ip).to_v4())); + boost::asio::ip::address::from_string(interface_ip.value()).to_v4())); return; } From 7fcec12e338db1fdc2f8da338d21b55e7c9758d9 Mon Sep 17 00:00:00 2001 From: arun Date: Tue, 3 Sep 2024 12:16:44 -0400 Subject: [PATCH 46/50] update documentation for getLocalIp --- src/software/networking/udp/network_utils.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 8ddd7edf68..8ae4150839 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -10,8 +10,6 @@ * The modified ip_address is valid only if the function returns true * * @param interface The interface to get the IP address from - * @param ip_address A reference to the std::string that will store the IP address if - * found * @param ipv4 If true, get the IPv4 address, otherwise get the IPv6 address * * @return IP address if it was found, otherwise nullopt From 52a21f04aa4ed72d4bc7bbc9d9981ff986bcdc6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:25:14 +0000 Subject: [PATCH 47/50] [pre-commit.ci lite] apply automatic fixes --- src/software/networking/udp/network_utils.h | 1 + src/software/networking/udp/network_utils_test.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/software/networking/udp/network_utils.h b/src/software/networking/udp/network_utils.h index 8ae4150839..59110988c1 100644 --- a/src/software/networking/udp/network_utils.h +++ b/src/software/networking/udp/network_utils.h @@ -1,6 +1,7 @@ #pragma once #include + #include #include diff --git a/src/software/networking/udp/network_utils_test.cpp b/src/software/networking/udp/network_utils_test.cpp index 4f16ac530c..52cc8b8c32 100644 --- a/src/software/networking/udp/network_utils_test.cpp +++ b/src/software/networking/udp/network_utils_test.cpp @@ -4,7 +4,7 @@ TEST(NetworkUtilsTest, getLocalIpValidInterface) { - std::string interface = "lo"; + std::string interface = "lo"; std::optional local_ip = getLocalIp(interface, true); EXPECT_TRUE(local_ip); EXPECT_EQ(local_ip.value(), "127.0.0.1"); From 80a49ef46392b44392378ab32286b9c146bd84ed Mon Sep 17 00:00:00 2001 From: arun Date: Sat, 19 Oct 2024 13:26:00 -0700 Subject: [PATCH 48/50] maybe fix ci --- src/software/embedded/thunderloop.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/software/embedded/thunderloop.cpp b/src/software/embedded/thunderloop.cpp index 11eead0887..5396ebc334 100644 --- a/src/software/embedded/thunderloop.cpp +++ b/src/software/embedded/thunderloop.cpp @@ -475,9 +475,14 @@ void Thunderloop::updateErrorCodes() void Thunderloop::waitForNetworkUp() { + std::optional error; ThreadedUdpSender network_test( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)) + "%" + network_interface_, - NETWORK_COMM_TEST_PORT, true); + std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), + NETWORK_COMM_TEST_PORT, network_interface_, true, error); + if (error.has_value()) + { + LOG(FATAL) << "Thunderloop cannot connect to the network. Error: " << error.value(); + } // Send an empty packet on the specific network interface to // ensure wifi is connected. Keeps trying until successful From 4cea2d746c906c54dcb49baeef129f0f07e32853 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 19 Oct 2024 20:34:00 +0000 Subject: [PATCH 49/50] [pre-commit.ci lite] apply automatic fixes --- src/software/embedded/thunderloop.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/software/embedded/thunderloop.cpp b/src/software/embedded/thunderloop.cpp index 5396ebc334..19546a1ccd 100644 --- a/src/software/embedded/thunderloop.cpp +++ b/src/software/embedded/thunderloop.cpp @@ -476,12 +476,13 @@ void Thunderloop::updateErrorCodes() void Thunderloop::waitForNetworkUp() { std::optional error; - ThreadedUdpSender network_test( - std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), - NETWORK_COMM_TEST_PORT, network_interface_, true, error); + ThreadedUdpSender network_test(std::string(ROBOT_MULTICAST_CHANNELS.at(channel_id_)), + NETWORK_COMM_TEST_PORT, network_interface_, true, + error); if (error.has_value()) { - LOG(FATAL) << "Thunderloop cannot connect to the network. Error: " << error.value(); + LOG(FATAL) << "Thunderloop cannot connect to the network. Error: " + << error.value(); } // Send an empty packet on the specific network interface to From bfe0f1166a595360851381244d4fb4c1aeaf798e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:24:30 +0000 Subject: [PATCH 50/50] [pre-commit.ci lite] apply automatic fixes --- src/software/thunderscope/robot_communication.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/software/thunderscope/robot_communication.py b/src/software/thunderscope/robot_communication.py index a03d9a7ff0..5ceb81409d 100644 --- a/src/software/thunderscope/robot_communication.py +++ b/src/software/thunderscope/robot_communication.py @@ -8,7 +8,6 @@ import os import software.python_bindings as tbots_cpp -from typing import Type from google.protobuf.message import Message from proto.import_all_protos import * from software.logger.logger import create_logger @@ -308,7 +307,6 @@ def setup_listener(listener_creator: Callable[[], Tuple[Any, str]]) -> Any: for robot_id in self.robot_control_mode_map.keys() ) - def toggle_keyboard_estop(self) -> None: """If keyboard estop is being used, toggles the estop state And sends a message to the console