Skip to content
Snippets Groups Projects
Commit 010f732d authored by Paul-Winpenny's avatar Paul-Winpenny
Browse files

Began the communication over TCP with the robobin and the app. Focusing on...

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
parent e95811d6
No related branches found
No related tags found
No related merge requests found
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Plugin.BLE; using Plugin.BLE;
using Plugin.BLE.Abstractions.Contracts; using Plugin.BLE.Abstractions.Contracts;
using RobobinApp.Networking; // Make sure to include the namespace for WifiManager
namespace RobobinApp; namespace RobobinApp
{
public partial class App : Application public partial class App : Application
{ {
public static IBluetoothLE BluetoothLE { get; private set; } public static IBluetoothLE BluetoothLE { get; private set; }
public static IAdapter BluetoothAdapter { get; private set; } public static IAdapter BluetoothAdapter { get; private set; }
private WifiManager _wifiManager;
public App() public App()
{ {
InitializeComponent(); InitializeComponent();
ConfigureLogging(); ConfigureLogging();
// Initialize the Bluetooth adapter
InitializeBluetoothAdapter(); InitializeBluetoothAdapter();
_wifiManager = new WifiManager();
Task.Run(() => _wifiManager.StartListening());
MainPage = new AppShell(); MainPage = new AppShell();
} }
protected override Window CreateWindow(IActivationState activationState) protected override Window CreateWindow(IActivationState activationState)
{ {
var window = base.CreateWindow(activationState); var window = base.CreateWindow(activationState);
window.MinimumWidth = 800; window.MinimumWidth = 800;
window.MinimumHeight = 650; window.MinimumHeight = 650;
return window; return window;
} }
private void ConfigureLogging() private void ConfigureLogging()
{ {
var loggerFactory = LoggerFactory.Create(builder => var loggerFactory = LoggerFactory.Create(builder =>
...@@ -43,10 +49,10 @@ public partial class App : Application ...@@ -43,10 +49,10 @@ public partial class App : Application
private void InitializeBluetoothAdapter() private void InitializeBluetoothAdapter()
{ {
BluetoothLE = CrossBluetoothLE.Current; BluetoothLE = CrossBluetoothLE.Current;
BluetoothAdapter = BluetoothLE.Adapter; BluetoothAdapter = BluetoothLE.Adapter;
} }
public ILogger<App> Logger { get; private set; } public ILogger<App> Logger { get; private set; }
} }
}
...@@ -6,7 +6,7 @@ using System.Linq; ...@@ -6,7 +6,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Robobin.Interfaces namespace RobobinApp.Networking
{ {
public class BluetoothLEService : IBluetoothLEService public class BluetoothLEService : IBluetoothLEService
{ {
......
...@@ -5,7 +5,7 @@ using System.Linq; ...@@ -5,7 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Robobin.Interfaces namespace RobobinApp.Networking
{ {
public interface IBluetoothLEService public interface IBluetoothLEService
{ {
......
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);
}
}
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
}
}
}
...@@ -19,7 +19,7 @@ using System.Reflection.PortableExecutable; ...@@ -19,7 +19,7 @@ using System.Reflection.PortableExecutable;
using Plugin.BLE.Windows; using Plugin.BLE.Windows;
using System.Text; using System.Text;
using System.Windows.Documents; using System.Windows.Documents;
using Robobin.Interfaces; using RobobinApp.Networking;
namespace RobobinApp.ViewModels namespace RobobinApp.ViewModels
{ {
......
...@@ -2,6 +2,7 @@ using System.Collections.ObjectModel; ...@@ -2,6 +2,7 @@ using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows.Input; using System.Windows.Input;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls; using Microsoft.Maui.Controls;
using Robobin.Models; using Robobin.Models;
using RobobinApp.Views; using RobobinApp.Views;
...@@ -16,6 +17,10 @@ namespace RobobinApp.ViewModels ...@@ -16,6 +17,10 @@ namespace RobobinApp.ViewModels
private bool _isBusy; private bool _isBusy;
private string _statusMessage; private string _statusMessage;
public ICommand ConnectToRobobinCommand { get; } public ICommand ConnectToRobobinCommand { get; }
private Graph _selectedGraph; private Graph _selectedGraph;
...@@ -36,7 +41,6 @@ namespace RobobinApp.ViewModels ...@@ -36,7 +41,6 @@ namespace RobobinApp.ViewModels
{ {
ConnectToRobobinCommand = new Command(async () => await OnConnectToRobobin()); ConnectToRobobinCommand = new Command(async () => await OnConnectToRobobin());
SelectGraphCommand = new Command<int>(OnSelectGraph); SelectGraphCommand = new Command<int>(OnSelectGraph);
var graphNodes1 = new List<Node> var graphNodes1 = new List<Node>
{ {
new Node("A", 50, 50), new Node("A", 50, 50),
...@@ -82,9 +86,6 @@ namespace RobobinApp.ViewModels ...@@ -82,9 +86,6 @@ namespace RobobinApp.ViewModels
var graph2Nodes = new List<Node> var graph2Nodes = new List<Node>
{ {
new Node("Home", 50, 50), new Node("Home", 50, 50),
......
...@@ -2,6 +2,7 @@ using Microsoft.Maui.Graphics; ...@@ -2,6 +2,7 @@ using Microsoft.Maui.Graphics;
using Robobin.Models; using Robobin.Models;
using RobobinApp.Models; using RobobinApp.Models;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace RobobinApp.Views namespace RobobinApp.Views
...@@ -15,16 +16,19 @@ namespace RobobinApp.Views ...@@ -15,16 +16,19 @@ namespace RobobinApp.Views
private static readonly Color BackgroundColor = Color.FromHex("#2C2F33"); private static readonly Color BackgroundColor = Color.FromHex("#2C2F33");
private static readonly Color ConnectionColor = Colors.Grey; private static readonly Color ConnectionColor = Colors.Grey;
private static readonly Color HighlightedNodeColor = Colors.Yellow; // Color for the highlighted node 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 // Properties to keep track of scale and offset
public float CurrentScale { get; private set; } = 1.0f; // Initialize to default scale 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 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 Node _highlightedNode; // Field to keep track of the highlighted node
private List<Node> _path; // Field to store the path
public void SetGraph(Graph graph) public void SetGraph(Graph graph)
{ {
_graph = graph ?? throw new ArgumentNullException(nameof(graph)); _graph = graph ?? throw new ArgumentNullException(nameof(graph));
_highlightedNode = null; // Reset highlighted node when setting a new 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) public void Draw(ICanvas canvas, RectF dirtyRect)
...@@ -40,6 +44,8 @@ namespace RobobinApp.Views ...@@ -40,6 +44,8 @@ namespace RobobinApp.Views
float nodeRadius = BaseNodeRadius * CurrentScale; float nodeRadius = BaseNodeRadius * CurrentScale;
DrawConnections(canvas, CurrentScale, offsetX, offsetY); DrawConnections(canvas, CurrentScale, offsetX, offsetY);
DrawPath(canvas, CurrentScale, offsetX, offsetY); // Draw the path
DrawNodes(canvas, CurrentScale, offsetX, offsetY, nodeRadius, _highlightedNode); DrawNodes(canvas, CurrentScale, offsetX, offsetY, nodeRadius, _highlightedNode);
} }
...@@ -64,7 +70,7 @@ namespace RobobinApp.Views ...@@ -64,7 +70,7 @@ namespace RobobinApp.Views
float graphHeight = maxY - minY; float graphHeight = maxY - minY;
float scaleX = dirtyRect.Width / graphWidth; float scaleX = dirtyRect.Width / graphWidth;
float scaleY = dirtyRect.Height / graphHeight; 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) 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 ...@@ -135,26 +141,27 @@ namespace RobobinApp.Views
public void HighlightNode(Node node) public void HighlightNode(Node node)
{ {
_highlightedNode = node; // Store the node to highlight _highlightedNode = node;
} }
public List<Node> AStarPathfinding(Node startNode, Node targetNode) public List<Node> AStarPathfinding(Node startNode, Node targetNode)
{ {
var openSet = new List<Node> { startNode }; var openSet = new List<Node> { startNode };
var cameFrom = new Dictionary<Node, Node>(); var cameFrom = new Dictionary<Node, Node>();
var gScore = new Dictionary<Node, float>(); // Cost from start to node var gScore = new Dictionary<Node, float>();
var fScore = new Dictionary<Node, float>(); // Estimated cost from start to target through node var fScore = new Dictionary<Node, float>();
foreach (var node in _graph.Nodes) foreach (var node in _graph.Nodes)
{ {
gScore[node] = float.MaxValue; // Initial cost is infinite gScore[node] = float.MaxValue;
fScore[node] = float.MaxValue; // Initial cost is infinite fScore[node] = float.MaxValue;
} }
gScore[startNode] = 0; gScore[startNode] = 0;
fScore[startNode] = Heuristic(startNode, targetNode); // Initial heuristic fScore[startNode] = Heuristic(startNode, targetNode);
while (openSet.Count > 0) while (openSet.Count > 0)
{ {
// Find node with the lowest fScore
Node currentNode = openSet.OrderBy(node => fScore[node]).First(); Node currentNode = openSet.OrderBy(node => fScore[node]).First();
if (currentNode == targetNode) if (currentNode == targetNode)
...@@ -164,13 +171,13 @@ namespace RobobinApp.Views ...@@ -164,13 +171,13 @@ namespace RobobinApp.Views
openSet.Remove(currentNode); openSet.Remove(currentNode);
// Check neighboring nodes
foreach (var edge in currentNode.Edges) foreach (var edge in currentNode.Edges)
{ {
Node neighbor = edge.TargetNode; // Get the neighboring node Node neighbor = edge.TargetNode;
float tentativeGScore = gScore[currentNode] + edge.Cost; // Calculate tentative gScore float tentativeGScore = gScore[currentNode] + edge.Cost;
if (tentativeGScore < gScore[neighbor]) // A better path has been found if (tentativeGScore < gScore[neighbor])
{ {
cameFrom[neighbor] = currentNode; cameFrom[neighbor] = currentNode;
gScore[neighbor] = tentativeGScore; gScore[neighbor] = tentativeGScore;
...@@ -184,12 +191,12 @@ namespace RobobinApp.Views ...@@ -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) 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)); return MathF.Sqrt(MathF.Pow(a.X - b.X, 2) + MathF.Pow(a.Y - b.Y, 2));
} }
...@@ -203,9 +210,37 @@ namespace RobobinApp.Views ...@@ -203,9 +210,37 @@ namespace RobobinApp.Views
totalPath.Add(currentNode); totalPath.Add(currentNode);
} }
totalPath.Reverse(); // Reverse the path to start from the initial node totalPath.Reverse();
return totalPath; 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);
}
}
}
} }
} }
...@@ -3,6 +3,7 @@ using RobobinApp.ViewModels; ...@@ -3,6 +3,7 @@ using RobobinApp.ViewModels;
using RobobinApp.Models; using RobobinApp.Models;
using System.Diagnostics; using System.Diagnostics;
using RobobinApp.Views; using RobobinApp.Views;
using Robobin.Models;
namespace RobobinApp.Views namespace RobobinApp.Views
{ {
...@@ -58,12 +59,22 @@ namespace RobobinApp.Views ...@@ -58,12 +59,22 @@ namespace RobobinApp.Views
var tapPointY = (float)tapPoint.Value.Y; var tapPointY = (float)tapPoint.Value.Y;
var nearestNode = drawable.FindNearestNode(tapPointX, tapPointY, scale, offset.X, offset.Y); var nearestNode = drawable.FindNearestNode(tapPointX, tapPointY, scale, offset.X, offset.Y);
// Highlight the nearest node
drawable.HighlightNode(nearestNode); drawable.HighlightNode(nearestNode);
if (nearestNode != null) 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"); DisplayAlert("Node Clicked", $"You clicked on node: {nearestNode.Name}", "OK");
} }
else else
...@@ -71,8 +82,10 @@ namespace RobobinApp.Views ...@@ -71,8 +82,10 @@ namespace RobobinApp.Views
DisplayAlert("No Node", "No node was clicked.", "OK"); DisplayAlert("No Node", "No node was clicked.", "OK");
} }
// Invalidate to trigger a redraw and highlight the node
GraphicsView.Invalidate(); GraphicsView.Invalidate();
} }
} }
} }
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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment