diff --git a/PixelServer.sln b/PixelHub.sln
similarity index 79%
rename from PixelServer.sln
rename to PixelHub.sln
index fde9661..4937c61 100644
--- a/PixelServer.sln
+++ b/PixelHub.sln
@@ -1,7 +1,7 @@
 ﻿
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelServer", "PixelServer\PixelServer.csproj", "{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixelHub", "PixelHub\PixelHub.csproj", "{B051D556-1D7A-48B0-8D4D-ABD2FBEBB6EF}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/PixelHub/DiscoveryBeacon.cs b/PixelHub/DiscoveryBeacon.cs
new file mode 100644
index 0000000..7432c95
--- /dev/null
+++ b/PixelHub/DiscoveryBeacon.cs
@@ -0,0 +1,211 @@
+﻿using System;
+using System.Net.Sockets;
+using System.Net;
+using System.IO;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+
+using SBRL.Utilities;
+using System.Text;
+using System.Runtime.CompilerServices;
+
+namespace PixelHub.Net
+{
+	/// <summary>
+	/// Provides a mechanism to broadcast a program's presence on a multicast address and port.
+	/// </summary>
+	public class DiscoveryBeacon : IDisposable
+	{
+		TextWriter logger;
+		UdpClient beacon;
+		int emitFrequency = 2000;
+		string role = "server";
+
+		/// <summary>
+		/// Whether the beacon is currently active.
+		/// If true then the beacon needs disposing of properly by calling the Dispose() method before the program ends.
+		/// </summary>
+		public bool Active { get; private set; } = true;
+		/// <summary>
+		/// Whether the logging output should be verbose.
+		/// </summary>
+		public bool Verbose { get; private set; } = false;
+
+
+		/// <summary>
+		/// The current IP address of the local machine.
+		/// </summary>
+		/// <remarks>
+		/// Setting this value affects the IP address that is broadcast by the beacon.
+		/// </remarks>
+		public IPAddress LocalIP { get; set; }
+		/// <summary>
+		/// The (multicast) IP address that the beacon should broadcast to.
+		/// </summary>
+		public IPAddress DestinationAddress { get; private set; }
+		/// <summary>
+		/// The multicast endpoint that the beacon will broadcast to.
+		/// This value is dynamically generated from the DestinationAddress and Port properties.
+		/// </summary>
+		public IPEndPoint MulticastChannel { get; private set; }
+		/// <summary>
+		/// The port that the beacon should broadcast on.
+		/// </summary>
+		public int Port { get; set; } = 5050;
+		/// <summary>
+		/// The role that the program hosted at this beacon plays. Default: server.
+		/// </summary>
+		public string Role {
+			get{
+				return role;
+			}
+			private set {
+				if (value.Contains(" "))
+					throw new InvalidDataException($"Error: Invalid role '{value}'.");
+				role = value;
+			}
+		}
+
+		/// <summary>
+		/// The time, in milliseconds, that the beacon should wait between broadcasts.
+		/// </summary>
+		public int EmitFrequency {
+			get {
+				return emitFrequency;
+			}
+			set {
+				emitFrequency = value;
+				logger.WriteLine("Beacon broadcast frequency changed to {0}ms.", EmitFrequency);
+			}
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelHub.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		/// <param name="inPort">The port to broadcast on.</param>
+		/// <param name="inLogger">The place to send log messages to defaults to Console.Out.</param>
+		public DiscoveryBeacon(IPAddress inDestAddress, int inPort, TextWriter inLogger)
+		{
+			logger = inLogger;
+
+			Port = inPort;
+			DestinationAddress = inDestAddress;
+			MulticastChannel = new IPEndPoint(DestinationAddress, Port);
+
+			LocalIP = findLocalIP();
+			logger.WriteLine("Using {0} as the local IP Address.", LocalIP);
+		}
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelHub.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		/// <param name="inPort">The port to broadcast on.</param>
+		public DiscoveryBeacon(IPAddress inDestAddress, int inPort) : this(inDestAddress, inPort, Console.Out)
+		{
+		}
+		/// <summary>
+		/// Initializes a new instance of the <see cref="PixelHub.Net.DiscoveryBeacon"/> class.
+		/// </summary>
+		/// <param name="inDestAddress">The destination address to broadcast to.</param>
+		public DiscoveryBeacon(IPAddress inDestAddress) : this(inDestAddress, 5050)
+		{
+		}
+
+		/// <summary>
+		/// Start the beacon up and broadcast the program's presence on a regular (See <see cref="EmitFrequency"/> basis.
+		/// </summary>
+		public async Task Emit()
+		{
+			connect();
+			logger.WriteLine("Beacon activated with broadcast interval of {0}ms.", EmitFrequency);
+			while(true)
+			{
+				await emitOnce();
+				await Task.Delay(EmitFrequency);
+			}
+		}
+
+		/// <summary>
+		/// Finds the IP of the machine the beacon is running on.
+		/// </summary>
+		/// <returns>The first IP found that points to this machine.</returns>
+		private IPAddress findLocalIP()
+		{
+			List<IPAddress> localIPS = Utilities.GetLocalIps().ToList();
+			return localIPS[0];
+		}
+
+		/// <summary>
+		/// Connects the underlying UdpClient client.
+		/// </summary>
+		private void connect()
+		{
+			beacon = new UdpClient(Port);
+			Active = true;
+			beacon.JoinMulticastGroup(DestinationAddress);
+			logger.WriteLine("One way beacon set up pointing at {0}.", MulticastChannel);
+		}
+		
+		/// <summary>
+		/// Emits a single presence broadcast from the beacon.
+		/// </summary>
+		private async Task emitOnce()
+		{
+			await Send($"{Role}@{LocalIP}:{Port}\n");
+		}
+
+		/// <summary>
+		/// Sends a message from the underlying UdpClient to the attached multicast address.
+		/// </summary>
+		/// <param name="data">The message to send.</param>
+		private async Task Send(string data)
+		{
+			await Send(MulticastChannel, data);
+		}
+		/// <summary>
+		/// Sends a message to the given endpoint using the underlying UdpClient.
+		/// </summary>
+		/// <param name="destination">The place to send the message to.</param>
+		/// <param name="data">The message payload.</param>
+		private async Task Send(IPEndPoint destination, string data)
+		{
+			if(Verbose) logger.WriteLine("Sending '{0}' to {1}.", Utilities.EscapeString(data), destination);
+			byte[] payload = Encoding.UTF8.GetBytes(data);
+			await Send(destination, payload);
+		}
+		/// <summary>
+		/// Sends a raw byte array message to the given endpoint.
+		/// This is the lowest level Send() method before the message is passed to the UcpClient itself.
+		/// </summary>
+		/// <param name="destination">THe destination endpoint to send the message to.</param>
+		/// <param name="payload">The byte array to use as the message body.</param>
+		private async Task Send(IPEndPoint destination, byte[] payload)
+		{
+			if (beacon == null)
+				throw new InvalidOperationException("Error: Can't send data until you join a channel.");
+			await beacon.SendAsync(payload, payload.Length, MulticastChannel);
+		}
+
+		/// <summary>
+		/// Releases all resource used by the <see cref="PixelHub.Net.DiscoveryBeacon"/>.
+		/// </summary>
+		/// <remarks>
+		/// Call <see cref="Dispose"/> when you are finished using the <see cref="PixelHub.Net.DiscoveryBeacon"/>. The
+		/// <see cref="Dispose"/> method leaves the <see cref="PixelHub.Net.DiscoveryBeacon"/> in an unusable state. After
+		/// calling <see cref="Dispose"/>, you must release all references to the
+		/// <see cref="PixelHub.Net.DiscoveryBeacon"/> so the garbage collector can reclaim the memory that the
+		/// <see cref="PixelHub.Net.DiscoveryBeacon"/> was occupying.</remarks>
+		public void Dispose()
+		{
+			if(Active)
+			{
+				beacon.Close();
+				Active = false;
+			}
+
+		}
+	}
+}
+
diff --git a/PixelServer/DiscoveryBeacon.cs b/PixelHub/DiscoveryBeacon.cs.orig
similarity index 100%
rename from PixelServer/DiscoveryBeacon.cs
rename to PixelHub/DiscoveryBeacon.cs.orig
diff --git a/PixelServer/ForgetTask.cs b/PixelHub/ForgetTask.cs
similarity index 100%
rename from PixelServer/ForgetTask.cs
rename to PixelHub/ForgetTask.cs
diff --git a/PixelHub/PixelHub.cs b/PixelHub/PixelHub.cs
new file mode 100644
index 0000000..166bc3d
--- /dev/null
+++ b/PixelHub/PixelHub.cs
@@ -0,0 +1,94 @@
+﻿using System;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Net;
+using SBRL.Utilities;
+using System.IO;
+
+namespace PixelHub
+{
+	/// <summary>
+	/// Provides the server component of the Hull Pixelbot server that can be used to control any number of pixel bots at once.
+	/// </summary>
+	public class PixelHub
+	{
+		TextWriter logger;
+		IPAddress bindAddress = IPAddress.Any;
+		int port;
+
+		TcpListener server;
+		/// <summary>
+		/// Whether the Hull Pixelbot serve is currently active and listening.
+		/// </summary>
+		public bool Active { get; private set; } = false;
+		public IPAddress BindAddress
+		{
+			get {
+				return bindAddress;
+			}
+			set {
+				if (Active == true)
+					throw new InvalidOperationException("Error: The bind address can't be changed once the server has started listening!");
+				bindAddress = value;
+			}
+		}
+		public int Port
+		{
+			get {
+				return port;
+			}
+			set {
+				if (Active == true)
+					throw new InvalidOperationException("Error: The port can't be changed once the server has started listening!");
+				port = value;
+			}
+		}
+		public IPEndPoint Endpoint
+		{
+			get {
+				return new IPEndPoint(BindAddress, Port);
+			}
+		}
+
+		public PixelHub(int inPort, TextWriter inLogger)
+		{
+			Port = inPort;
+			logger = inLogger;
+		}
+		public PixelHub(int inPort) : this(inPort, Console.Out)
+		{
+		}
+
+		public async Task Listen()
+		{
+			server = new TcpListener(Endpoint);
+			logger.WriteLine("TCP Listener set up on {0}.", Endpoint);
+			Active = true;
+			while(true)
+			{
+				TcpClient nextClient = await server.AcceptTcpClientAsync();
+				AsyncTools.ForgetTask(Handle(nextClient));
+			}
+			// TODO: Add an shutdown CancellationToken thingy here
+			//Active = false;
+		}
+
+		private async Task Handle(TcpClient client)
+		{
+			logger.WriteLine("Accepted connection from {0}.", client.Client.RemoteEndPoint);
+
+			using (StreamReader incoming = new StreamReader(client.GetStream()))
+			using (StreamWriter outgoing = new StreamWriter(client.GetStream()) { AutoFlush = true })
+			{
+				string nextLine;
+				while((nextLine = await incoming.ReadLineAsync()) != null)
+				{
+					Console.WriteLine("Got message from client: '{0}'", nextLine.Trim());
+				}
+			}
+
+			Console.WriteLine("Lost connection from {0}.", client.Client.RemoteEndPoint);
+		}
+	}
+}
+
diff --git a/PixelServer/PixelServer.cs b/PixelHub/PixelHub.cs.orig
similarity index 100%
rename from PixelServer/PixelServer.cs
rename to PixelHub/PixelHub.cs.orig
diff --git a/PixelServer/PixelServer.csproj b/PixelHub/PixelHub.csproj
similarity index 97%
rename from PixelServer/PixelServer.csproj
rename to PixelHub/PixelHub.csproj
index 9052bb6..0e9814e 100644
--- a/PixelServer/PixelServer.csproj
+++ b/PixelHub/PixelHub.csproj
@@ -38,8 +38,8 @@
     <Compile Include="DiscoveryBeacon.cs" />
     <Compile Include="Utilities.cs" />
     <Compile Include="ForgetTask.cs" />
-    <Compile Include="PixelServer.cs" />
+    <Compile Include="PixelHub.cs" />
     <Compile Include="PrefixedWriter.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/PixelServer/PrefixedWriter.cs b/PixelHub/PrefixedWriter.cs
similarity index 100%
rename from PixelServer/PrefixedWriter.cs
rename to PixelHub/PrefixedWriter.cs
diff --git a/PixelHub/Program.cs b/PixelHub/Program.cs
new file mode 100644
index 0000000..cf42520
--- /dev/null
+++ b/PixelHub/Program.cs
@@ -0,0 +1,34 @@
+﻿using System;
+
+using System.Net;
+using System.Threading.Tasks;
+
+using SBRL.Utilities;
+using PixelHub.Net;
+
+namespace PixelHub
+{
+	static class Program
+	{
+		private static int port = 5050;
+		private static PrefixedWriter systemWriter = new PrefixedWriter(Console.Out) { Prefix = "[System] " };
+		private static PrefixedWriter beaconWriter = new PrefixedWriter(Console.Out) { Prefix = "[Beacon] " };
+		private static PrefixedWriter serverWriter = new PrefixedWriter(Console.Out) { Prefix = "[Server] " };
+
+		public static void Main(string[] args)
+		{
+			systemWriter.WriteLine("Booting server.");
+			DiscoveryBeacon beacon = new DiscoveryBeacon(
+				IPAddress.Parse("239.62.148.30"), port,
+				beaconWriter
+			);
+			PixelHub server = new PixelHub(port, serverWriter);
+			systemWriter.WriteLine("Server booting complete. Beginning async loop.");
+			//AsyncTools.ForgetTask(beacon.Emit());
+			Task.WaitAll(new Task[] {
+				beacon.Emit(),
+				server.Listen()
+			});
+		}
+	}
+}
diff --git a/PixelServer/Program.cs b/PixelHub/Program.cs.orig
similarity index 100%
rename from PixelServer/Program.cs
rename to PixelHub/Program.cs.orig
diff --git a/PixelServer/Properties/AssemblyInfo.cs b/PixelHub/Properties/AssemblyInfo.cs
similarity index 100%
rename from PixelServer/Properties/AssemblyInfo.cs
rename to PixelHub/Properties/AssemblyInfo.cs
diff --git a/PixelServer/Utilities.cs b/PixelHub/Utilities.cs
similarity index 100%
rename from PixelServer/Utilities.cs
rename to PixelHub/Utilities.cs
