From fce8b2296defd22bb812c6c4bab3b64bc27d60d8 Mon Sep 17 00:00:00 2001
From: Paul-Winpenny <92634321+Paul-Winpenny@users.noreply.github.com>
Date: Fri, 8 Nov 2024 16:50:09 +0000
Subject: [PATCH] Beginning to handle the loading map data from the robobins
 side.

---
 App/RobobinApp/Networking/WifiManager.cs      |  29 ++-
 .../RPi_Wifi/Layouts/Lab1.json                |  37 +++
 .../RPi_Wifi/WifiControllerRPi.py             | 232 +++++++++++++-----
 Wireless_Communication/RPi_Wifi/config.json   |   3 +
 4 files changed, 223 insertions(+), 78 deletions(-)
 create mode 100644 Wireless_Communication/RPi_Wifi/Layouts/Lab1.json
 create mode 100644 Wireless_Communication/RPi_Wifi/config.json

diff --git a/App/RobobinApp/Networking/WifiManager.cs b/App/RobobinApp/Networking/WifiManager.cs
index b7a43813..fd820a13 100644
--- a/App/RobobinApp/Networking/WifiManager.cs
+++ b/App/RobobinApp/Networking/WifiManager.cs
@@ -15,6 +15,7 @@ namespace RobobinApp.Networking
         private bool _isConnected = false;
         private CancellationTokenSource _cancellationTokenSource;
         private TcpClient _tcpClient;
+        public string Location { get; private set; } = "Unknown";
 
         // Event to notify the UI or other parts of the app of specific messages
         public event Action<string> OnMessageReceived;
@@ -29,11 +30,7 @@ namespace RobobinApp.Networking
         {
             while (true) // Continuous listening loop with reconnection attempts
             {
-                if (_isConnected)
-                {
-                    Debug.WriteLine("Already connected. Stopping further listening.");
-                    break;
-                }
+             
 
                 try
                 {
@@ -41,10 +38,19 @@ namespace RobobinApp.Networking
                     var result = await _udpClient.ReceiveAsync();
                     string message = Encoding.ASCII.GetString(result.Buffer);
 
-                    if (message == "ROBOBIN_PRESENT")
+                    if (message.StartsWith("ROBOBIN_PRESENT"))
                     {
-                        Debug.WriteLine("Detected Robobin presence from: " + result.RemoteEndPoint);
-                        SendConnectMessage(result.RemoteEndPoint.Address.ToString());
+                        var parts = message.Split(new[] { '(', ',', ')' }, StringSplitOptions.RemoveEmptyEntries);
+                        if (parts.Length == 3)
+                        {
+                            Location = $"({parts[1].Trim()}, {parts[2].Trim()})";
+                            Debug.WriteLine($"Detected Robobin presence at location: {Location} from: {result.RemoteEndPoint}");
+                            SendConnectMessage(result.RemoteEndPoint.Address.ToString());
+                        }
+                        else
+                        {
+                            Debug.WriteLine("Invalid message format received.");
+                        }
                     }
                 }
                 catch (ObjectDisposedException)
@@ -127,12 +133,11 @@ namespace RobobinApp.Networking
             catch (Exception ex)
             {
                 Debug.WriteLine($"TCP connection failed: {ex.Message}");
-                _isConnected = false; // Reset connection status
-                await StartListening(); // Retry listening for presence broadcast
+                _isConnected = false;
+                await StartListening(); 
             }
 
-            // If TCP connection is lost, attempt to reconnect
-            _cancellationTokenSource.Cancel(); // Stop the current listening task
+            _cancellationTokenSource.Cancel(); 
         }
 
         private async Task ReceiveMessages()
diff --git a/Wireless_Communication/RPi_Wifi/Layouts/Lab1.json b/Wireless_Communication/RPi_Wifi/Layouts/Lab1.json
new file mode 100644
index 00000000..6442655a
--- /dev/null
+++ b/Wireless_Communication/RPi_Wifi/Layouts/Lab1.json
@@ -0,0 +1,37 @@
+{
+    "name": "Lab 1",
+    "nodes": [
+        {"label": "A", "x": 50, "y": 50},
+        {"label": "B", "x": 150, "y": 50},
+        {"label": "C", "x": 250, "y": 50},
+        {"label": "Home", "x": 350, "y": 50},
+        {"label": "E", "x": 50, "y": 150},
+        {"label": "F", "x": 150, "y": 150},
+        {"label": "G", "x": 250, "y": 150},
+        {"label": "H", "x": 350, "y": 150},
+        {"label": "I", "x": 50, "y": 250},
+        {"label": "J", "x": 150, "y": 250},
+        {"label": "K", "x": 250, "y": 250},
+        {"label": "L", "x": 350, "y": 250},
+        {"label": "M", "x": 50, "y": 350},
+        {"label": "N", "x": 150, "y": 350},
+        {"label": "O", "x": 350, "y": 350}
+    ],
+    "adjacency_matrix": [
+        [false, true, false, false, true, false, false, false, false, false, false, false, false, false, false],
+        [true, false, true, false, false, true, false, false, false, false, false, false, false, false, false],
+        [false, true, false, true, false, false, true, false, false, false, false, false, false, false, false],
+        [false, false, true, false, false, false, false, true, false, false, false, false, false, false, true],
+        [true, false, false, false, false, true, false, false, true, false, false, false, false, false, false],
+        [false, true, false, false, true, false, true, false, false, true, false, false, false, false, false],
+        [false, false, true, false, false, true, false, true, false, false, true, false, false, false, false],
+        [false, false, false, true, false, false, true, false, false, false, false, true, false, false, false],
+        [false, false, false, false, true, false, false, false, false, true, false, false, true, false, false],
+        [false, false, false, false, false, true, false, false, true, false, true, false, false, true, false],
+        [false, false, false, false, false, false, true, false, false, true, false, true, false, false, false],
+        [false, false, false, false, false, false, false, true, false, false, true, false, false, false, true],
+        [false, false, false, false, false, false, false, false, true, false, false, false, false, true, false],
+        [false, false, false, false, false, false, false, false, false, true, false, false, true, false, true],
+        [false, false, false, true, false, false, false, false, false, false, false, true, false, true, false]
+    ]
+}
diff --git a/Wireless_Communication/RPi_Wifi/WifiControllerRPi.py b/Wireless_Communication/RPi_Wifi/WifiControllerRPi.py
index 40bc7121..0fd7b315 100644
--- a/Wireless_Communication/RPi_Wifi/WifiControllerRPi.py
+++ b/Wireless_Communication/RPi_Wifi/WifiControllerRPi.py
@@ -1,72 +1,168 @@
-import queue
+import os
+import json
 import socket
 import threading
+import queue
 import time
 
 class RoboBinConnectionHandler:
-    def __init__(self, udp_ip="255.255.255.255", udp_port=5005, listen_port=5006):
-        self.queue = queue.Queue(10)
+    def __init__(self, udp_ip="255.255.255.255", udp_port=5005, listen_port=5006, layouts_folder="Layouts", config_file="config.json"):
+        self.command_queue = queue.Queue(10)
         self.UDP_IP = udp_ip
         self.UDP_PORT = udp_port
         self.LISTEN_PORT = listen_port
-        self.stop_event = threading.Event()  
+        self.stop_event = threading.Event()
+        self.layouts_folder = layouts_folder
+        self.config_file = config_file
+        self.default_layout = "lab1"  
+        self.current_location = (0,0)
+        # Load the configuration on startup
+        config = self.load_config()
+        self.current_layout = config.get("current_layout", self.default_layout) #NAME OF LAYOUT FILE
+        
+        # Load and print nodes from the current layout file
+        self.current_nodes = self.load_layout(self.current_layout)
+
+
+
+
+        # Command handlers
         self.message_handlers = {
             "PING": self.handle_ping,
             "TIME": self.handle_time_request,
-            "CALLOVER" : self.handle_call_over,
-            "CUSTOM": self.handle_custom_message,  
+            "CALLOVER": self.handle_call_over,
+            "CUSTOM": self.handle_custom_message,
+            "REQUEST_MAP": self.handle_request_map,
+            "SET_LAYOUT": self.handle_set_layout
 
         }
-        self.udp_sock = None  
-        self.tcp_socket = None  #
+        
+        self.udp_sock = None
+        self.tcp_socket = None
+        print("RoboBinConnectionHandler initialized.")
+        print("Current layout: {}".format(self.current_layout))
 
-    def broadcast_presence(self):
-        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+    def load_config(self):
+        """
+        Loads the configuration from the config file.
+        If the file does not exist, it creates a default configuration.
+        """
+        if os.path.exists(self.config_file):
+            with open(self.config_file, "r") as file:
+                config = json.load(file)
+            print(f"Loaded configuration: {config}")
+       
+            config_nodes = config.get("nodes", [])
+            for node in config_nodes:
+                print(f"Node: {node}")
+            return config
+        else:
+            config = {"current_layout": self.default_layout}
+            self.save_config(config)
+            print("No config file found. Created default configuration.")
+            return config
 
-        while not self.stop_event.is_set():
-            message = b"ROBOBIN_PRESENT"
-            try:
-                sock.sendto(message, (self.UDP_IP, self.UDP_PORT))
-                print("Broadcasting: {}".format(message.decode()))
-            except OSError as e:
-                print(f"Broadcast error: {e}")
-                break
-            time.sleep(5)
+    def save_config(self, config):
+        """
+        Saves the configuration to the config file.
+        """
+        with open(self.config_file, "w") as file:
+            json.dump(config, file, indent=4)
+        print(f"Configuration saved: {config}")
 
-        sock.close()
-        print("Broadcasting stopped.")
+    def handle_set_layout(self, client_socket, new_layout):
+        """
+        Updates the current layout in the configuration and persists it.
+        """
+        self.current_layout = new_layout
+        # Update config file with the new current_layout
+        config = {"current_layout": self.current_layout}
+        self.save_config(config)
+        response = f"Current layout set to '{self.current_layout}'.".encode()
+        client_socket.sendall(response)
+        print(f"Current layout updated to '{self.current_layout}' and saved to config.")
+
+    def load_layout(self, layout_name):
+        """
+        Loads the specified layout file and prints all nodes in it.
+        """
+        layout_file = os.path.join(self.layouts_folder, f"{layout_name}.json")
+        if os.path.exists(layout_file):
+            with open(layout_file, "r") as file:
+                layout_data = json.load(file)
+            print(f"Loaded layout '{layout_name}':")
+            
+            nodes = layout_data.get("nodes", [])
+            for node in nodes:
+                print(f"Node: {node['label']}, Position: ({node['x']}, {node['y']})")
+            
+            return layout_data
+        else:
+            print(f"Layout file '{layout_file}' not found.")
+            return None
+
+    def load_current_map(self):
+        """
+        Loads the current map JSON data based on the current_layout attribute.
+        If the specified layout is not found, falls back to the default layout.
+        """
+        # Attempt to load the specified layout
+        map_data = self.load_layout(self.current_layout)
+        if map_data:
+            print(f"Loaded map '{self.current_layout}' successfully.")
+            return map_data
 
-    def handle_ping(self, client_socket):
-        print("Received PING from client.")
+        # Attempt to load the default layout if the specified layout is missing
+        map_data = self.load_layout(self.default_layout)
+        if map_data:
+            print(f"Specified layout not found. Loaded default layout '{self.default_layout}'.")
+            return map_data
+
+        # Both the specified and default layouts are missing
+        print("Error: Neither specified nor default layout found.")
+        return None
+
+    def handle_request_map(self, client_socket, _):
+        """
+        Sends the current map layout (nodes and adjacency matrix) to the client.
+        """
+        map_data = self.load_current_map()
+        if map_data:
+            map_json = json.dumps(map_data).encode()
+            client_socket.sendall(map_json)
+            print(f"Sent map data for layout '{self.current_layout}' to client.")
+        else:
+            error_message = b"Map not found."
+            client_socket.sendall(error_message)
+            print("Error: Map data not found, sent error message to client.")
+
+    # Other command handlers
+    def handle_ping(self, client_socket, _):
         response = b"PONG"
-        print("Sending PONG to client.")
         client_socket.sendall(response)
 
-    def handle_time_request(self, client_socket):
+    def handle_time_request(self, client_socket, _):
         current_time = time.ctime().encode()
-        print(f"Sending current time: {current_time.decode()}")
         client_socket.sendall(current_time)
 
     def handle_custom_message(self, client_socket, message):
         response = f"Received custom message: {message}".encode()
-        print(f"Custom handler response: {response.decode()}")
         client_socket.sendall(response)
 
     def handle_call_over(self, client_socket, message):
-        response = f"User has requested node: {message}".encode()
-        print(f"Call over handler response: {response.decode()}")
+        node = message
+        response = f"User has requested node: {node}".encode()
+        #TODO: Node should be added to queue, respond to user with queue position
         client_socket.sendall(response)
 
-    def handle_unknown_message(self, client_socket):
+    def handle_unknown_message(self, client_socket, _):
         response = b"I don't know this message."
-        print("Sending response to unknown message.")
         client_socket.sendall(response)
 
     def handle_client_connection(self, client_socket):
         try:
             while not self.stop_event.is_set():
-                client_socket.settimeout(1) 
+                client_socket.settimeout(1)
                 try:
                     request = client_socket.recv(1024)
                 except socket.timeout:
@@ -82,16 +178,10 @@ class RoboBinConnectionHandler:
                 print("Received from client: {}".format(message))
 
                 parts = message.split(" ", 1)
-                message_type = parts[0]
-                message_data = parts[1] if len(parts) > 1 else None
-
-                if message_type in self.message_handlers:
-                    if message_data:
-                        self.message_handlers[message_type](client_socket, message_data)
-                    else:
-                        self.message_handlers[message_type](client_socket)
-                else:
-                    self.handle_unknown_message(client_socket)
+                command = parts[0]
+                data = parts[1] if len(parts) > 1 else None
+                handler = self.message_handlers.get(command, self.handle_unknown_message)
+                handler(client_socket, data)
 
         except ConnectionResetError:
             print("Client connection was forcibly closed.")
@@ -108,14 +198,13 @@ class RoboBinConnectionHandler:
 
         while not self.stop_event.is_set():
             try:
-                self.udp_sock.settimeout(1)  # Timeout for blocking recvfrom call
+                self.udp_sock.settimeout(1)
                 data, addr = self.udp_sock.recvfrom(1024)
                 if data.decode() == "CONNECT":
                     print("Received connection request from {}".format(addr))
-
                     self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                     self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-                    self.tcp_socket.bind(('', self.LISTEN_PORT)) 
+                    self.tcp_socket.bind(('', self.LISTEN_PORT))
                     self.tcp_socket.listen(1)
                     print("Listening for TCP connection...")
 
@@ -125,18 +214,37 @@ class RoboBinConnectionHandler:
                     threading.Thread(target=self.handle_client_connection, args=(client_socket,)).start()
             except socket.timeout:
                 continue
-            except OSError as e:
-                print(f"UDP socket no longer open (likely due to stop event):" )
+            except OSError:
+                print("UDP socket no longer open (likely due to stop event).")
                 break
             except Exception as e:
                 print(f"An error occurred while listening for connections: {e}")
 
-        # Close sockets if stop event is set
         if self.udp_sock:
             self.udp_sock.close()
         if self.tcp_socket:
             self.tcp_socket.close()
         print("Listening stopped.")
+    def broadcast_presence(self):
+        """
+        Broadcasts RoboBin's presence periodically over UDP.
+        """
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+
+        while not self.stop_event.is_set():
+            #add location to back of message
+            message = b"ROBOBIN_PRESENT " + str(self.current_location).encode()
+            try:
+                sock.sendto(message, (self.UDP_IP, self.UDP_PORT))
+                print("Broadcasting: {}".format(message.decode()))
+            except OSError as e:
+                print(f"Broadcast error: {e}")
+                break
+            time.sleep(5)
+
+        sock.close()
+        print("Broadcasting stopped.")
 
     def start(self):
         self.broadcast_thread = threading.Thread(target=self.broadcast_presence)
@@ -146,29 +254,21 @@ class RoboBinConnectionHandler:
         self.listen_thread.start()
 
     def stop(self):
-        print("Stopping server...")
         self.stop_event.set()
-        # Safely close the sockets to prevent further operations on closed sockets
         if self.udp_sock:
             self.udp_sock.close()
         if self.tcp_socket:
             self.tcp_socket.close()
         self.broadcast_thread.join()
         self.listen_thread.join()
-        print("Server stopped.")
 
-# Instantiate and start the handler
 if __name__ == "__main__":
-    robobin_handler = RoboBinConnectionHandler()
-    robobin_handler.start()
-    print("Server started. Type 'stop' to shut down.")
-
-    # Main loop to accept CLI input
-    while True:
-        command = input("Enter command: ")
-        if command.strip().lower() == "stop":
-            robobin_handler.stop()
-        elif command.strip().lower() == "status":
-            print("Server is running.")
-            #printstatus() # Funciton will be added later
-            break
+    handler = RoboBinConnectionHandler()
+    handler.start()
+    try:
+        while True:
+            time.sleep(1)
+    except KeyboardInterrupt:
+        print("Stopping...")
+        handler.stop()
+        print("Stopped.")
\ No newline at end of file
diff --git a/Wireless_Communication/RPi_Wifi/config.json b/Wireless_Communication/RPi_Wifi/config.json
new file mode 100644
index 00000000..f397d68b
--- /dev/null
+++ b/Wireless_Communication/RPi_Wifi/config.json
@@ -0,0 +1,3 @@
+{
+    "current_layout": "lab1"
+}
-- 
GitLab