From 7680cd6e8fa3f4a75fa284253236a269426fc992 Mon Sep 17 00:00:00 2001
From: Paul-Winpenny <92634321+Paul-Winpenny@users.noreply.github.com>
Date: Thu, 7 Nov 2024 18:19:14 +0000
Subject: [PATCH] Added interface to send messages over tcp fro testing

---
 App/RobobinApp/App.xaml.cs                    |   6 +-
 App/RobobinApp/Networking/WifiManager.cs      | 152 +++++++++++++-----
 App/RobobinApp/Resources/Styles/appstyle.css  |   4 +
 App/RobobinApp/RobobinApp.csproj              |   8 +-
 App/RobobinApp/RobobinApp.csproj.user         |   5 +-
 App/RobobinApp/Views/MainPage.xaml            |   9 +-
 App/RobobinApp/Views/MainPage_Android.xaml    |   9 +-
 App/RobobinApp/Views/Sides/AdminBox.xaml      |  52 ++++++
 App/RobobinApp/Views/Sides/AdminBox.xaml.cs   |  76 +++++++++
 App/RobobinApp/Views/{ => Sides}/SideBox.xaml |   2 +-
 .../Views/{ => Sides}/SideBox.xaml.cs         |   2 +-
 Connectivity/SampleServerPi.py                |  50 ++++--
 12 files changed, 314 insertions(+), 61 deletions(-)
 create mode 100644 App/RobobinApp/Views/Sides/AdminBox.xaml
 create mode 100644 App/RobobinApp/Views/Sides/AdminBox.xaml.cs
 rename App/RobobinApp/Views/{ => Sides}/SideBox.xaml (92%)
 rename App/RobobinApp/Views/{ => Sides}/SideBox.xaml.cs (96%)

diff --git a/App/RobobinApp/App.xaml.cs b/App/RobobinApp/App.xaml.cs
index e1472dcd..4241096d 100644
--- a/App/RobobinApp/App.xaml.cs
+++ b/App/RobobinApp/App.xaml.cs
@@ -9,7 +9,7 @@ namespace RobobinApp
     {
         public static IBluetoothLE BluetoothLE { get; private set; }
         public static IAdapter BluetoothAdapter { get; private set; }
-        private WifiManager _wifiManager;
+        public static WifiManager WifiManager { get; private set; }
 
         public App()
         {
@@ -19,8 +19,8 @@ namespace RobobinApp
      
             InitializeBluetoothAdapter();
 
-            _wifiManager = new WifiManager();
-            Task.Run(() => _wifiManager.StartListening());
+            WifiManager = new WifiManager();
+            Task.Run(() => WifiManager.StartListening());
 
             MainPage = new AppShell();
         }
diff --git a/App/RobobinApp/Networking/WifiManager.cs b/App/RobobinApp/Networking/WifiManager.cs
index b60745e2..b7a43813 100644
--- a/App/RobobinApp/Networking/WifiManager.cs
+++ b/App/RobobinApp/Networking/WifiManager.cs
@@ -12,8 +12,12 @@ namespace RobobinApp.Networking
     {
         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
+        private bool _isConnected = false;
+        private CancellationTokenSource _cancellationTokenSource;
+        private TcpClient _tcpClient;
+
+        // Event to notify the UI or other parts of the app of specific messages
+        public event Action<string> OnMessageReceived;
 
         public WifiManager()
         {
@@ -23,22 +27,42 @@ namespace RobobinApp.Networking
 
         public async Task StartListening()
         {
-            while (!_isConnected) // Continue listening until connected
+            while (true) // Continuous listening loop with reconnection attempts
             {
-                Debug.WriteLine("Waiting for broadcast...");
-                var result = await _udpClient.ReceiveAsync();
-                string message = Encoding.ASCII.GetString(result.Buffer);
+                if (_isConnected)
+                {
+                    Debug.WriteLine("Already connected. Stopping further listening.");
+                    break;
+                }
 
-                if (message == "ROBOBIN_PRESENT")
+                try
+                {
+                    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());
+                    }
+                }
+                catch (ObjectDisposedException)
                 {
-                    Debug.WriteLine("Detected Robobin presence from: " + result.RemoteEndPoint);
-                    SendConnectMessage(result.RemoteEndPoint.Address.ToString());
+                    Debug.WriteLine("UDP client has been closed.");
+                    break;
+                }
+                catch (Exception ex)
+                {
+                    Debug.WriteLine($"Error in UDP listening: {ex.Message}");
                 }
-            }
 
-            // Stop listening if connected
-            Debug.WriteLine("Stopping UDP listener.");
-            _udpClient.Close();
+                // Retry delay if not connected
+                if (!_isConnected)
+                {
+                    await Task.Delay(3000); // Wait 3 seconds before retrying
+                }
+            }
         }
 
         public void SendConnectMessage(string ipAddress)
@@ -56,53 +80,107 @@ namespace RobobinApp.Networking
 
             Task.Run(() => ConnectToTcpServer(endPoint));
         }
-        public async Task SendPingMessage(TcpClient tcpClient)
+
+        public async Task SendMessageAsync(string message)
         {
-            if (!_isConnected)
+            if (!_isConnected || _tcpClient == null)
             {
-                Debug.WriteLine("Not connected. Cannot send ping message.");
+                Debug.WriteLine("Not connected. Cannot send 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.");
+                NetworkStream stream = _tcpClient.GetStream();
+                byte[] data = Encoding.ASCII.GetBytes(message);
+                await stream.WriteAsync(data, 0, data.Length);
+                Debug.WriteLine($"Sent message: {message}");
             }
             catch (Exception ex)
             {
-                Debug.WriteLine($"Failed to send PING message: {ex.Message}");
+                Debug.WriteLine($"Failed to send message: {ex.Message}");
+                _isConnected = false; // Reset connection status to retry
+                await StartListening(); // Restart listening for reconnection
             }
         }
 
+        public async Task SendPingMessage()
+        {
+            await SendMessageAsync("PING");
+        }
+
         private async Task ConnectToTcpServer(IPEndPoint endPoint)
         {
-            using (TcpClient tcpClient = new TcpClient())
+            _tcpClient = new TcpClient();
+
+            try
             {
-                try
-                {
-                    await tcpClient.ConnectAsync(endPoint.Address, endPoint.Port);
-                    Debug.WriteLine("Connected to Robobin via TCP.");
+                await _tcpClient.ConnectAsync(endPoint.Address, endPoint.Port);
+                Debug.WriteLine("Connected to Robobin via TCP.");
+                _isConnected = true;
 
-                    _isConnected = true;
+ 
+                Task.Run(() => ReceiveMessages());
 
-                    // Keep the connection open to send PING messages periodically
-                    while (_isConnected)
+  
+            }
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"TCP connection failed: {ex.Message}");
+                _isConnected = false; // Reset connection status
+                await StartListening(); // Retry listening for presence broadcast
+            }
+
+            // If TCP connection is lost, attempt to reconnect
+            _cancellationTokenSource.Cancel(); // Stop the current listening task
+        }
+
+        private async Task ReceiveMessages()
+        {
+            if (_tcpClient == null)
+                return;
+
+            NetworkStream stream = _tcpClient.GetStream();
+            byte[] buffer = new byte[1024];
+
+            try
+            {
+                while (_isConnected)
+                {
+                    int byteCount = await stream.ReadAsync(buffer, 0, buffer.Length);
+                    if (byteCount <= 0)
                     {
-                        await SendPingMessage(tcpClient);
-                        await Task.Delay(5000); // Send a ping every 5 seconds
+                        Debug.WriteLine("Disconnected from server.");
+                        _isConnected = false;
+                        break;
                     }
-                }
-                catch (Exception ex)
-                {
-                    Debug.WriteLine($"TCP connection failed: {ex.Message}");
+
+                    string receivedMessage = Encoding.ASCII.GetString(buffer, 0, byteCount);
+                    Debug.WriteLine($"Received message: {receivedMessage}");
+
+                    // Trigger alert or handle specific messages
+                    HandleReceivedMessage(receivedMessage);
                 }
             }
-
-            _cancellationTokenSource.Cancel(); // Cancel the token to stop the UDP listener
+            catch (Exception ex)
+            {
+                Debug.WriteLine($"Error receiving message: {ex.Message}");
+                _isConnected = false;
+                await StartListening(); // Restart listening if disconnected
+            }
         }
 
+        private void HandleReceivedMessage(string message)
+        {
+            if (message == "PONG")
+            {
+                OnMessageReceived?.Invoke("Received PONG from Robobin");
+                Debug.WriteLine("PONG received, alert triggered.");
+            }
+            else
+            {
+                OnMessageReceived?.Invoke($"RM: {message}");
+            }
+        }
     }
 }
diff --git a/App/RobobinApp/Resources/Styles/appstyle.css b/App/RobobinApp/Resources/Styles/appstyle.css
index 3716a8e8..9f88077d 100644
--- a/App/RobobinApp/Resources/Styles/appstyle.css
+++ b/App/RobobinApp/Resources/Styles/appstyle.css
@@ -157,3 +157,7 @@ stacklayout > image {
     background-color: #E8EDF1;
     padding: 10;
 }
+.sideBox button.button-primary {
+    background-color: #647687;
+    color: #FFFFFF;
+}
diff --git a/App/RobobinApp/RobobinApp.csproj b/App/RobobinApp/RobobinApp.csproj
index 29a2b946..33d435f7 100644
--- a/App/RobobinApp/RobobinApp.csproj
+++ b/App/RobobinApp/RobobinApp.csproj
@@ -71,6 +71,9 @@
         <Compile Update="Views\MainPage_Android.xaml.cs">
           <DependentUpon>MainPage_Android.xaml</DependentUpon>
         </Compile>
+        <Compile Update="Views\Sides\AdminBox.xaml.cs">
+          <DependentUpon>AdminBox.xaml</DependentUpon>
+        </Compile>
     </ItemGroup>
 
     <ItemGroup>
@@ -86,7 +89,10 @@
         <MauiXaml Update="Views\MainPage_Android.xaml">
           <Generator>MSBuild:Compile</Generator>
         </MauiXaml>
-        <MauiXaml Update="Views\SideBox.xaml">
+        <MauiXaml Update="Views\Sides\AdminBox.xaml">
+          <Generator>MSBuild:Compile</Generator>
+        </MauiXaml>
+        <MauiXaml Update="Views\Sides\SideBox.xaml">
           <Generator>MSBuild:Compile</Generator>
         </MauiXaml>
     </ItemGroup>
diff --git a/App/RobobinApp/RobobinApp.csproj.user b/App/RobobinApp/RobobinApp.csproj.user
index 6bc1775f..0e19fbac 100644
--- a/App/RobobinApp/RobobinApp.csproj.user
+++ b/App/RobobinApp/RobobinApp.csproj.user
@@ -32,7 +32,10 @@
     <MauiXaml Update="Views\MainPage_Android.xaml">
       <SubType>Designer</SubType>
     </MauiXaml>
-    <MauiXaml Update="Views\SideBox.xaml">
+    <MauiXaml Update="Views\Sides\AdminBox.xaml">
+      <SubType>Designer</SubType>
+    </MauiXaml>
+    <MauiXaml Update="Views\Sides\SideBox.xaml">
       <SubType>Designer</SubType>
     </MauiXaml>
   </ItemGroup>
diff --git a/App/RobobinApp/Views/MainPage.xaml b/App/RobobinApp/Views/MainPage.xaml
index 7f8086d5..79e226df 100644
--- a/App/RobobinApp/Views/MainPage.xaml
+++ b/App/RobobinApp/Views/MainPage.xaml
@@ -2,6 +2,7 @@
 <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:local="clr-namespace:RobobinApp.Views"  
+             xmlns:sides ="clr-namespace:RobobinApp.Views.Sides"
              xmlns:viewModels="clr-namespace:RobobinApp.ViewModels"
              xmlns:drawable="clr-namespace:RobobinApp.Views"
     x:Class="RobobinApp.Views.MainPage"
@@ -36,8 +37,8 @@
                 <Button Text="Setup" Command="{Binding ConnectToRobobinCommand}"/>
             </HorizontalStackLayout>
 
-            <local:SideBox HeaderTitle="Queue:" />
-            <local:SideBox HeaderTitle="Status:" />
+            <sides:SideBox HeaderTitle="Queue:" />
+            <sides:SideBox HeaderTitle="Status:" />
         </VerticalStackLayout>
 
         <Frame StyleClass="mainFrame" 
@@ -67,8 +68,8 @@
 
 
             </HorizontalStackLayout>
-            <local:SideBox HeaderTitle="Mode:" />
-            <local:SideBox HeaderTitle="Admin:" />
+            <sides:SideBox HeaderTitle="Mode:" />
+            <sides:AdminBox HeaderTitle="Admin:" />
 
         </VerticalStackLayout>
     </Grid>
diff --git a/App/RobobinApp/Views/MainPage_Android.xaml b/App/RobobinApp/Views/MainPage_Android.xaml
index 153fb312..2e149064 100644
--- a/App/RobobinApp/Views/MainPage_Android.xaml
+++ b/App/RobobinApp/Views/MainPage_Android.xaml
@@ -3,6 +3,7 @@
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
              xmlns:local="clr-namespace:RobobinApp.Views"  
              xmlns:viewModels="clr-namespace:RobobinApp.ViewModels"
+             xmlns:sides ="clr-namespace:RobobinApp.Views.Sides"
              xmlns:drawable="clr-namespace:RobobinApp.Views"
              x:Class="RobobinApp.Views.MainPage_Android"
              Title="">
@@ -36,8 +37,8 @@
                       HorizontalOptions="Center" />
                 </HorizontalStackLayout>
 
-                <local:SideBox HeaderTitle="Queue:" />
-                <local:SideBox HeaderTitle="Status:" />
+                <sides:SideBox HeaderTitle="Queue:" />
+                <sides:SideBox HeaderTitle="Status:" />
             </VerticalStackLayout>
 
             <!-- Main Drawable Area with specific HeightRequest to ensure visibility -->
@@ -60,8 +61,8 @@
                                  VerticalOptions="End">
          
 
-                <local:SideBox HeaderTitle="Mode:" />
-                <local:SideBox HeaderTitle="Admin:" />
+                <sides:SideBox HeaderTitle="Mode:" />
+                <sides:AdminBox HeaderTitle="Admin:" />
             </VerticalStackLayout>
 
         </VerticalStackLayout>
diff --git a/App/RobobinApp/Views/Sides/AdminBox.xaml b/App/RobobinApp/Views/Sides/AdminBox.xaml
new file mode 100644
index 00000000..6cde48a5
--- /dev/null
+++ b/App/RobobinApp/Views/Sides/AdminBox.xaml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+             x:Class="RobobinApp.Views.Sides.AdminBox"
+             class="sideBox">
+    <Frame class="side-box-frame">
+        <VerticalStackLayout>
+
+            <Grid class="side-box-header">
+                <Label x:Name="HeaderText"
+                       Text="Admin:"
+                       class="side-box-header-text"/>
+            </Grid>
+
+            <ScrollView class="side-box-content">
+                <VerticalStackLayout>
+
+               
+                        
+                        <HorizontalStackLayout>
+                        <Label Text="Send TCP Message:"/>
+                        <Entry x:Name="TcpMessageEntry"
+                                Placeholder="Type message here"
+                                    BackgroundColor="#FFFFFF"
+                                    Grid.Column="0"
+                HorizontalOptions="FillAndExpand"
+              Margin="0"/>
+
+                            <Button Text="Send"
+               Clicked="OnSendMessageClicked"
+               class="button-primary"
+               Grid.Column="1"
+               HorizontalOptions="End"
+               Margin="0"/>
+                        </HorizontalStackLayout>
+                    <HorizontalStackLayout>
+
+                        <Label Text="Latest Message:"  Margin="0,10,0,5"/>
+                        <Frame class="primaryFrame">
+                            <Label x:Name="LatestMessageLabel" TextColor="White"
+                               Text="No messages yet."
+                               />
+                        </Frame>
+                    </HorizontalStackLayout>
+
+
+
+                </VerticalStackLayout>
+            </ScrollView>
+        </VerticalStackLayout>
+    </Frame>
+</ContentView>
diff --git a/App/RobobinApp/Views/Sides/AdminBox.xaml.cs b/App/RobobinApp/Views/Sides/AdminBox.xaml.cs
new file mode 100644
index 00000000..95f404d7
--- /dev/null
+++ b/App/RobobinApp/Views/Sides/AdminBox.xaml.cs
@@ -0,0 +1,76 @@
+using System;
+using Microsoft.Maui.Controls;
+
+namespace RobobinApp.Views.Sides
+{
+    public partial class AdminBox : ContentView
+    {
+        public static readonly BindableProperty HeaderTitleProperty =
+            BindableProperty.Create(nameof(HeaderTitle),
+                                  typeof(string),
+                                  typeof(AdminBox),
+                                  defaultValue: "Admin:",
+                                  propertyChanged: OnHeaderTitleChanged);
+
+        public string HeaderTitle
+        {
+            get => (string)GetValue(HeaderTitleProperty);
+            set => SetValue(HeaderTitleProperty, value);
+        }
+
+        public AdminBox()
+        {
+            InitializeComponent();
+
+ 
+            App.WifiManager.OnMessageReceived += UpdateLatestMessage;
+        }
+
+        private void UpdateLatestMessage(string message)
+        {
+
+            MainThread.BeginInvokeOnMainThread(() =>
+            {
+                LatestMessageLabel.Text = message;
+            });
+        }
+
+        private async void OnSendMessageClicked(object sender, EventArgs e)
+        {
+ 
+            string messageToSend = TcpMessageEntry.Text;
+
+            if (!string.IsNullOrWhiteSpace(messageToSend))
+            {
+
+                await App.WifiManager.SendMessageAsync(messageToSend);
+
+
+                TcpMessageEntry.Text = string.Empty;
+            }
+            else
+            {
+                await Application.Current.MainPage.DisplayAlert("Error", "Please enter a message to send.", "OK");
+            }
+        }
+
+        protected static void OnHeaderTitleChanged(BindableObject bindable, object oldValue, object newValue)
+        {
+            var control = (AdminBox)bindable;
+            if (control.HeaderText != null)
+            {
+                control.HeaderText.Text = newValue?.ToString();
+            }
+        }
+
+        protected override void OnParentChanged()
+        {
+            base.OnParentChanged();
+
+            if (Parent == null)
+            {
+                App.WifiManager.OnMessageReceived -= UpdateLatestMessage;
+            }
+        }
+    }
+}
diff --git a/App/RobobinApp/Views/SideBox.xaml b/App/RobobinApp/Views/Sides/SideBox.xaml
similarity index 92%
rename from App/RobobinApp/Views/SideBox.xaml
rename to App/RobobinApp/Views/Sides/SideBox.xaml
index 799f3e5c..e4fd9c09 100644
--- a/App/RobobinApp/Views/SideBox.xaml
+++ b/App/RobobinApp/Views/Sides/SideBox.xaml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
-             x:Class="RobobinApp.Views.SideBox"
+             x:Class="RobobinApp.Views.Sides.SideBox"
              StyleClass="SideBox">
     <Frame StyleClass="side-box-frame">
         <VerticalStackLayout>
diff --git a/App/RobobinApp/Views/SideBox.xaml.cs b/App/RobobinApp/Views/Sides/SideBox.xaml.cs
similarity index 96%
rename from App/RobobinApp/Views/SideBox.xaml.cs
rename to App/RobobinApp/Views/Sides/SideBox.xaml.cs
index e115f034..a2d96900 100644
--- a/App/RobobinApp/Views/SideBox.xaml.cs
+++ b/App/RobobinApp/Views/Sides/SideBox.xaml.cs
@@ -1,4 +1,4 @@
-namespace RobobinApp.Views
+namespace RobobinApp.Views.Sides
 {
     public partial class SideBox : ContentView
     {
diff --git a/Connectivity/SampleServerPi.py b/Connectivity/SampleServerPi.py
index 17591102..9868fc29 100644
--- a/Connectivity/SampleServerPi.py
+++ b/Connectivity/SampleServerPi.py
@@ -16,20 +16,54 @@ def broadcast_presence():
         print("Broadcasting: {}".format(message.decode()))
         time.sleep(5)
 
+def handle_ping(client_socket):
+    print("Received PING from client.")
+    response = b"PONG"
+    print("Sending PONG to client.")
+    client_socket.sendall(response)
+
+def handle_time_request(client_socket):
+    current_time = time.ctime().encode()
+    print(f"Sending current time: {current_time.decode()}")
+    client_socket.sendall(current_time)
+
+def handle_custom_message(client_socket, message):
+    response = f"Received custom message: {message}".encode()
+    print(f"Custom handler response: {response.decode()}")
+    client_socket.sendall(response)
+def handle_unknown_message(client_socket):
+    response = b"I don't know this message."
+    print("Sending response to unknown message.")
+    client_socket.sendall(response)
+
+message_handlers = {
+    "PING": handle_ping,
+    "TIME": handle_time_request,
+    "CUSTOM": handle_custom_message,  
+}
+
 def handle_client_connection(client_socket):
     try:
         while True:
             request = client_socket.recv(1024)
             if not request:
                 print("No request received, closing connection.")
-                break  # Connection closed by the client
+                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)
+  
+            parts = message.split(" ", 1)
+            message_type = parts[0]
+            message_data = parts[1] if len(parts) > 1 else None
+
+            if message_type in message_handlers:
+                if message_type == "CUSTOM" and message_data:
+                    message_handlers[message_type](client_socket, message_data)
+                else:
+                    message_handlers[message_type](client_socket)
+            else:
+                handle_unknown_message(client_socket)
 
     except ConnectionResetError:
         print("Client connection was forcibly closed.")
@@ -49,21 +83,19 @@ def listen_for_connections():
             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.bind(('', 5006)) 
                     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()
         except Exception as e:
             print(f"An error occurred while listening for connections: {e}")
 
-# Start the broadcasting and listening in separate threads
+# Start broadcasting and listening threads
 broadcast_thread = threading.Thread(target=broadcast_presence)
 listen_thread = threading.Thread(target=listen_for_connections)
 
-- 
GitLab