From 010f732dc4c9e72cc76155564bb34d389916d213 Mon Sep 17 00:00:00 2001
From: Paul-Winpenny <92634321+Paul-Winpenny@users.noreply.github.com>
Date: Mon, 4 Nov 2024 16:52:12 +0000
Subject: [PATCH] Began the communication over TCP with the robobin and the
 app. Focusing on just one app instance for now but I will need to do more
 than 1

---
 App/RobobinApp/App.xaml.cs                    |  78 +++++++------
 .../{Interfaces => Networking}/BLEClasses.cs  |   2 +-
 .../BLEInterfaces.cs                          |   2 +-
 App/RobobinApp/Networking/IWifiService.cs     |  14 +++
 App/RobobinApp/Networking/WifiManager.cs      | 108 ++++++++++++++++++
 .../ViewModels/ConnectionPageViewModel.cs     |   2 +-
 .../ViewModels/MainPageViewModel.cs           |  11 +-
 App/RobobinApp/Views/CentreGraph.xaml.cs      |  65 ++++++++---
 App/RobobinApp/Views/MainPage.xaml.cs         |  21 +++-
 Connectivity/SampleServerPi.py                |  65 +++++++++++
 10 files changed, 305 insertions(+), 63 deletions(-)
 rename App/RobobinApp/{Interfaces => Networking}/BLEClasses.cs (93%)
 rename App/RobobinApp/{Interfaces => Networking}/BLEInterfaces.cs (91%)
 create mode 100644 App/RobobinApp/Networking/IWifiService.cs
 create mode 100644 App/RobobinApp/Networking/WifiManager.cs
 create mode 100644 Connectivity/SampleServerPi.py

diff --git a/App/RobobinApp/App.xaml.cs b/App/RobobinApp/App.xaml.cs
index 848550bd..4ae70a3d 100644
--- a/App/RobobinApp/App.xaml.cs
+++ b/App/RobobinApp/App.xaml.cs
@@ -1,52 +1,58 @@
 using Microsoft.Extensions.Logging;
 using Plugin.BLE;
 using Plugin.BLE.Abstractions.Contracts;
+using RobobinApp.Networking; // Make sure to include the namespace for WifiManager
 
-namespace RobobinApp;
-
-public partial class App : Application
+namespace RobobinApp
 {
-    public static IBluetoothLE BluetoothLE { get; private set; }
-    public static IAdapter BluetoothAdapter { get; private set; }
-
-    public App()
+    public partial class App : Application
     {
-        InitializeComponent();
-        ConfigureLogging();
+        public static IBluetoothLE BluetoothLE { get; private set; }
+        public static IAdapter BluetoothAdapter { get; private set; }
+        private WifiManager _wifiManager;
 
-        // Initialize the Bluetooth adapter
-        InitializeBluetoothAdapter();
+        public App()
+        {
+            InitializeComponent();
+            ConfigureLogging();
 
-        MainPage = new AppShell();
-    }
-    protected override Window CreateWindow(IActivationState activationState)
-    {
-        var window = base.CreateWindow(activationState);
+     
+            InitializeBluetoothAdapter();
 
+            _wifiManager = new WifiManager();
+            Task.Run(() => _wifiManager.StartListening());
 
-        window.MinimumWidth = 800; 
-        window.MinimumHeight = 650; 
+            MainPage = new AppShell();
+        }
 
-        return window;
-    }
-    private void ConfigureLogging()
-    {
-        var loggerFactory = LoggerFactory.Create(builder =>
+        protected override Window CreateWindow(IActivationState activationState)
         {
-            builder
-                .AddConsole()
-                .AddDebug();
-        });
+            var window = base.CreateWindow(activationState);
 
-        Logger = loggerFactory.CreateLogger<App>();
-    }
+            window.MinimumWidth = 800;
+            window.MinimumHeight = 650;
 
-    private void InitializeBluetoothAdapter()
-    {
-        
-        BluetoothLE = CrossBluetoothLE.Current;
-        BluetoothAdapter = BluetoothLE.Adapter;
-    }
+            return window;
+        }
+
+        private void ConfigureLogging()
+        {
+            var loggerFactory = LoggerFactory.Create(builder =>
+            {
+                builder
+                    .AddConsole()
+                    .AddDebug();
+            });
 
-    public ILogger<App> Logger { get; private set; }
+            Logger = loggerFactory.CreateLogger<App>();
+        }
+
+        private void InitializeBluetoothAdapter()
+        {
+            BluetoothLE = CrossBluetoothLE.Current;
+            BluetoothAdapter = BluetoothLE.Adapter;
+        }
+
+        public ILogger<App> Logger { get; private set; }
+    }
 }
diff --git a/App/RobobinApp/Interfaces/BLEClasses.cs b/App/RobobinApp/Networking/BLEClasses.cs
similarity index 93%
rename from App/RobobinApp/Interfaces/BLEClasses.cs
rename to App/RobobinApp/Networking/BLEClasses.cs
index d0823784..3718c695 100644
--- a/App/RobobinApp/Interfaces/BLEClasses.cs
+++ b/App/RobobinApp/Networking/BLEClasses.cs
@@ -6,7 +6,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace Robobin.Interfaces
+namespace RobobinApp.Networking
 {
     public class BluetoothLEService : IBluetoothLEService
     {
diff --git a/App/RobobinApp/Interfaces/BLEInterfaces.cs b/App/RobobinApp/Networking/BLEInterfaces.cs
similarity index 91%
rename from App/RobobinApp/Interfaces/BLEInterfaces.cs
rename to App/RobobinApp/Networking/BLEInterfaces.cs
index 181124d9..d61f586f 100644
--- a/App/RobobinApp/Interfaces/BLEInterfaces.cs
+++ b/App/RobobinApp/Networking/BLEInterfaces.cs
@@ -5,7 +5,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace Robobin.Interfaces
+namespace RobobinApp.Networking
 {
     public interface IBluetoothLEService
     {
diff --git a/App/RobobinApp/Networking/IWifiService.cs b/App/RobobinApp/Networking/IWifiService.cs
new file mode 100644
index 00000000..99b621ee
--- /dev/null
+++ b/App/RobobinApp/Networking/IWifiService.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace RobobinApp.Networking
+{
+    public interface IWifiService
+    {
+        Task StartListening();
+        void SendConnectMessage(string ipAddress);
+    }
+}
diff --git a/App/RobobinApp/Networking/WifiManager.cs b/App/RobobinApp/Networking/WifiManager.cs
new file mode 100644
index 00000000..b60745e2
--- /dev/null
+++ b/App/RobobinApp/Networking/WifiManager.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Diagnostics;
+
+namespace RobobinApp.Networking
+{
+    public class WifiManager : IWifiService
+    {
+        private UdpClient _udpClient;
+        private const int BroadcastPort = 5005;
+        private bool _isConnected = false; // Flag to indicate connection status
+        private CancellationTokenSource _cancellationTokenSource; // For stopping the UDP listener
+
+        public WifiManager()
+        {
+            _udpClient = new UdpClient(BroadcastPort);
+            _cancellationTokenSource = new CancellationTokenSource();
+        }
+
+        public async Task StartListening()
+        {
+            while (!_isConnected) // Continue listening until connected
+            {
+                Debug.WriteLine("Waiting for broadcast...");
+                var result = await _udpClient.ReceiveAsync();
+                string message = Encoding.ASCII.GetString(result.Buffer);
+
+                if (message == "ROBOBIN_PRESENT")
+                {
+                    Debug.WriteLine("Detected Robobin presence from: " + result.RemoteEndPoint);
+                    SendConnectMessage(result.RemoteEndPoint.Address.ToString());
+                }
+            }
+
+            // Stop listening if connected
+            Debug.WriteLine("Stopping UDP listener.");
+            _udpClient.Close();
+        }
+
+        public void SendConnectMessage(string ipAddress)
+        {
+            if (_isConnected)
+            {
+                Debug.WriteLine("Already connected. No need to send another connect message.");
+                return;
+            }
+
+            var endPoint = new IPEndPoint(IPAddress.Parse(ipAddress), 5006);
+            byte[] message = Encoding.ASCII.GetBytes("CONNECT");
+            _udpClient.Send(message, message.Length, endPoint);
+            Debug.WriteLine($"Sent connect message to: {ipAddress}");
+
+            Task.Run(() => ConnectToTcpServer(endPoint));
+        }
+        public async Task SendPingMessage(TcpClient tcpClient)
+        {
+            if (!_isConnected)
+            {
+                Debug.WriteLine("Not connected. Cannot send ping message.");
+                return;
+            }
+
+            try
+            {
+                NetworkStream stream = tcpClient.GetStream();
+                byte[] pingMessage = Encoding.ASCII.GetBytes("PING");
+                await stream.WriteAsync(pingMessage, 0, pingMessage.Length);
+                Debug.WriteLine("Sent PING message to Robobin.");
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"Failed to send PING message: {ex.Message}");
+            }
+        }
+
+        private async Task ConnectToTcpServer(IPEndPoint endPoint)
+        {
+            using (TcpClient tcpClient = new TcpClient())
+            {
+                try
+                {
+                    await tcpClient.ConnectAsync(endPoint.Address, endPoint.Port);
+                    Debug.WriteLine("Connected to Robobin via TCP.");
+
+                    _isConnected = true;
+
+                    // Keep the connection open to send PING messages periodically
+                    while (_isConnected)
+                    {
+                        await SendPingMessage(tcpClient);
+                        await Task.Delay(5000); // Send a ping every 5 seconds
+                    }
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine($"TCP connection failed: {ex.Message}");
+                }
+            }
+
+            _cancellationTokenSource.Cancel(); // Cancel the token to stop the UDP listener
+        }
+
+    }
+}
diff --git a/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
index 57804e7a..74427aa2 100644
--- a/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
+++ b/App/RobobinApp/ViewModels/ConnectionPageViewModel.cs
@@ -19,7 +19,7 @@ using System.Reflection.PortableExecutable;
 using Plugin.BLE.Windows;
 using System.Text;
 using System.Windows.Documents;
-using Robobin.Interfaces;
+using RobobinApp.Networking;
 
 namespace RobobinApp.ViewModels
 {
diff --git a/App/RobobinApp/ViewModels/MainPageViewModel.cs b/App/RobobinApp/ViewModels/MainPageViewModel.cs
index 26c62ad1..da7f2073 100644
--- a/App/RobobinApp/ViewModels/MainPageViewModel.cs
+++ b/App/RobobinApp/ViewModels/MainPageViewModel.cs
@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using System.Windows.Input;
+using Microsoft.Extensions.Logging;
 using Microsoft.Maui.Controls;
 using Robobin.Models;
 using RobobinApp.Views;
@@ -16,6 +17,10 @@ namespace RobobinApp.ViewModels
         private bool _isBusy;
         private string _statusMessage;
 
+
+        
+        
+      
         public ICommand ConnectToRobobinCommand { get; }
 
         private Graph _selectedGraph;
@@ -36,7 +41,6 @@ namespace RobobinApp.ViewModels
         {
             ConnectToRobobinCommand = new Command(async () => await OnConnectToRobobin());
             SelectGraphCommand = new Command<int>(OnSelectGraph);
-
             var graphNodes1 = new List<Node>
 {
     new Node("A", 50, 50),
@@ -77,10 +81,7 @@ namespace RobobinApp.ViewModels
 
             var graph1 = new Graph("Lab 1", graphNodes1, graphMatrix1);
 
-
-
-
-
+        
 
 
 
diff --git a/App/RobobinApp/Views/CentreGraph.xaml.cs b/App/RobobinApp/Views/CentreGraph.xaml.cs
index 7836b1d3..61c3cd36 100644
--- a/App/RobobinApp/Views/CentreGraph.xaml.cs
+++ b/App/RobobinApp/Views/CentreGraph.xaml.cs
@@ -2,6 +2,7 @@ using Microsoft.Maui.Graphics;
 using Robobin.Models;
 using RobobinApp.Models;
 using System;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace RobobinApp.Views
@@ -15,16 +16,19 @@ namespace RobobinApp.Views
         private static readonly Color BackgroundColor = Color.FromHex("#2C2F33");
         private static readonly Color ConnectionColor = Colors.Grey;
         private static readonly Color HighlightedNodeColor = Colors.Yellow; // Color for the highlighted node
+        private static readonly Color PathColor = Colors.Blue; // Color for the path
 
         // Properties to keep track of scale and offset
         public float CurrentScale { get; private set; } = 1.0f; // Initialize to default scale
         public (float X, float Y) CurrentOffset { get; private set; } = (0, 0); // Initialize to default offset
         private Node _highlightedNode; // Field to keep track of the highlighted node
+        private List<Node> _path; // Field to store the path
 
         public void SetGraph(Graph graph)
         {
             _graph = graph ?? throw new ArgumentNullException(nameof(graph));
             _highlightedNode = null; // Reset highlighted node when setting a new graph
+            _path = null; // Reset the path when setting a new graph
         }
 
         public void Draw(ICanvas canvas, RectF dirtyRect)
@@ -40,6 +44,8 @@ namespace RobobinApp.Views
             float nodeRadius = BaseNodeRadius * CurrentScale;
 
             DrawConnections(canvas, CurrentScale, offsetX, offsetY);
+            DrawPath(canvas, CurrentScale, offsetX, offsetY); // Draw the path
+
             DrawNodes(canvas, CurrentScale, offsetX, offsetY, nodeRadius, _highlightedNode);
         }
 
@@ -64,7 +70,7 @@ namespace RobobinApp.Views
             float graphHeight = maxY - minY;
             float scaleX = dirtyRect.Width / graphWidth;
             float scaleY = dirtyRect.Height / graphHeight;
-            return Math.Min(scaleX, scaleY) * 0.8f; // Scale with some margin
+            return Math.Min(scaleX, scaleY) * 0.8f; 
         }
 
         private (float offsetX, float offsetY) CalculateOffsets(RectF dirtyRect, float minX, float minY, float maxX, float maxY, float scale)
@@ -135,26 +141,27 @@ namespace RobobinApp.Views
 
         public void HighlightNode(Node node)
         {
-            _highlightedNode = node; // Store the node to highlight
+            _highlightedNode = node; 
         }
+
         public List<Node> AStarPathfinding(Node startNode, Node targetNode)
         {
             var openSet = new List<Node> { startNode };
             var cameFrom = new Dictionary<Node, Node>();
-            var gScore = new Dictionary<Node, float>(); // Cost from start to node
-            var fScore = new Dictionary<Node, float>(); // Estimated cost from start to target through node
+            var gScore = new Dictionary<Node, float>(); 
+            var fScore = new Dictionary<Node, float>(); 
 
             foreach (var node in _graph.Nodes)
             {
-                gScore[node] = float.MaxValue; // Initial cost is infinite
-                fScore[node] = float.MaxValue; // Initial cost is infinite
+                gScore[node] = float.MaxValue; 
+                fScore[node] = float.MaxValue;
             }
             gScore[startNode] = 0;
-            fScore[startNode] = Heuristic(startNode, targetNode); // Initial heuristic
+            fScore[startNode] = Heuristic(startNode, targetNode);
 
             while (openSet.Count > 0)
             {
-                // Find node with the lowest fScore
+
                 Node currentNode = openSet.OrderBy(node => fScore[node]).First();
 
                 if (currentNode == targetNode)
@@ -164,13 +171,13 @@ namespace RobobinApp.Views
 
                 openSet.Remove(currentNode);
 
-                // Check neighboring nodes
+
                 foreach (var edge in currentNode.Edges)
                 {
-                    Node neighbor = edge.TargetNode; // Get the neighboring node
-                    float tentativeGScore = gScore[currentNode] + edge.Cost; // Calculate tentative gScore
+                    Node neighbor = edge.TargetNode;
+                    float tentativeGScore = gScore[currentNode] + edge.Cost;
 
-                    if (tentativeGScore < gScore[neighbor]) // A better path has been found
+                    if (tentativeGScore < gScore[neighbor]) 
                     {
                         cameFrom[neighbor] = currentNode;
                         gScore[neighbor] = tentativeGScore;
@@ -184,12 +191,12 @@ namespace RobobinApp.Views
                 }
             }
 
-            return new List<Node>(); // Return empty path if there is no path
+            return new List<Node>(); 
         }
 
         private float Heuristic(Node a, Node b)
         {
-            // Euclidean distance
+
             return MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2));
         }
 
@@ -203,9 +210,37 @@ namespace RobobinApp.Views
                 totalPath.Add(currentNode);
             }
 
-            totalPath.Reverse(); // Reverse the path to start from the initial node
+            totalPath.Reverse(); 
             return totalPath;
         }
+        public Node GetHomeNode()
+        {
+            return _graph.Nodes.FirstOrDefault(n => n.Name == "Home");
+        }
+        public List<Node> Path
+        {
+            get => _path;
+            set => _path = value; 
+        }
 
+
+        private void DrawPath(ICanvas canvas, float scale, float offsetX, float offsetY)
+        {
+            if (_path != null && _path.Count > 0)
+            {
+                canvas.StrokeColor = PathColor; // Color for the path
+                canvas.StrokeSize = 3;
+
+                for (int i = 0; i < _path.Count - 1; i++)
+                {
+                    float startX = _path[i].X * scale + offsetX;
+                    float startY = _path[i].Y * scale + offsetY;
+                    float endX = _path[i + 1].X * scale + offsetX;
+                    float endY = _path[i + 1].Y * scale + offsetY;
+
+                    canvas.DrawLine(startX, startY, endX, endY);
+                }
+            }
+        }
     }
 }
diff --git a/App/RobobinApp/Views/MainPage.xaml.cs b/App/RobobinApp/Views/MainPage.xaml.cs
index 5b06ec41..a8c12e68 100644
--- a/App/RobobinApp/Views/MainPage.xaml.cs
+++ b/App/RobobinApp/Views/MainPage.xaml.cs
@@ -3,6 +3,7 @@ using RobobinApp.ViewModels;
 using RobobinApp.Models;
 using System.Diagnostics;
 using RobobinApp.Views;
+using Robobin.Models;
 
 namespace RobobinApp.Views
 {
@@ -12,7 +13,7 @@ namespace RobobinApp.Views
         {
             InitializeComponent();
 
-      
+           
             var drawable = (GraphicsDrawable)Resources["drawable"];
             if (BindingContext is MainPageViewModel viewModel)
             {
@@ -58,12 +59,22 @@ namespace RobobinApp.Views
             var tapPointY = (float)tapPoint.Value.Y;
             var nearestNode = drawable.FindNearestNode(tapPointX, tapPointY, scale, offset.X, offset.Y);
 
-
+            // Highlight the nearest node
             drawable.HighlightNode(nearestNode);
 
-   
             if (nearestNode != null)
             {
+                // Find the home node
+                Node homeNode = drawable.GetHomeNode(); 
+
+                if (homeNode != null)
+                {
+         
+                    List<Node> path = drawable.AStarPathfinding(nearestNode, homeNode);
+                    drawable.Path = path;
+                    GraphicsView.Invalidate();
+                }
+
                 DisplayAlert("Node Clicked", $"You clicked on node: {nearestNode.Name}", "OK");
             }
             else
@@ -71,8 +82,10 @@ namespace RobobinApp.Views
                 DisplayAlert("No Node", "No node was clicked.", "OK");
             }
 
+            // Invalidate to trigger a redraw and highlight the node
             GraphicsView.Invalidate();
         }
+
     }
-    
+
 }
diff --git a/Connectivity/SampleServerPi.py b/Connectivity/SampleServerPi.py
new file mode 100644
index 00000000..30465635
--- /dev/null
+++ b/Connectivity/SampleServerPi.py
@@ -0,0 +1,65 @@
+import socket
+import threading
+import time
+
+UDP_IP = "255.255.255.255"
+UDP_PORT = 5005
+LISTEN_PORT = 5006
+
+def broadcast_presence():
+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+
+    while True:
+        message = b"ROBOBIN_PRESENT"
+        sock.sendto(message, (UDP_IP, UDP_PORT))
+        time.sleep(5)
+
+def handle_client_connection(client_socket):
+    while True:
+        request = client_socket.recv(1024)
+        if not request:
+            break 
+        message = request.decode()
+        print("Received from client: {}".format(message))
+
+        if message == "PING":
+            print("Received PING from client.")  
+            response = b"PONG"  
+            client_socket.sendall(response)
+
+    client_socket.close()
+    print("Client disconnected.")
+
+
+def listen_for_connections():
+    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+    sock.bind(('', LISTEN_PORT))
+
+    while True:
+        data, addr = sock.recvfrom(1024)
+        if data.decode() == "CONNECT":
+            print("Received connection request from {}".format(addr))
+
+            # Create a TCP socket to accept connections
+            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_socket:
+                tcp_socket.bind(('', 5006))  # Listen on the fixed TCP port
+                tcp_socket.listen(1)
+                print("Listening for TCP connection...")
+
+                client_socket, client_addr = tcp_socket.accept()
+                print("Client connected from {}".format(client_addr))
+
+                # Spawn a new thread for handling the client connection
+                threading.Thread(target=handle_client_connection, args=(client_socket,)).start()
+
+
+# Start the broadcasting and listening in separate threads
+broadcast_thread = threading.Thread(target=broadcast_presence)
+listen_thread = threading.Thread(target=listen_for_connections)
+
+broadcast_thread.start()
+listen_thread.start()
+
+broadcast_thread.join()
+listen_thread.join()
-- 
GitLab