Yelling on your Raspberry is not always the best solution, so let’s take a look how to communicate between two C# applications.
When it comes to peer-to-peer communication between UWP and Xamarin apps, we can use TCP Sockets. Unlike UDP, it establishes a long-lasting and reliable connection. However, please note that this implementation is not secured and it’s aimed to use only on private networks.
UWP Server class
First of all, we need to implement server-side listener, which will listen for any incoming requests. Create a new class with TcpListener property, initialize it, and listen in a loop for any incoming connections. When the connection is established, listen for commands, process them and provide a relevant response back to the client. In this scenario, I’m dealing with strings, however, for bigger projects, it is more reasonable to use serializable classes for commands and responses of your own.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
public class TcpSocketServer { private int _port = 6666; private TcpListener _server { get; set; } public async Task StartAsync() { try { _server = new TcpListener(await GetHostPAddressAsync(), _port); _server.Start(); await Task.Factory.StartNew(AcceptClientsAsync); } catch { Stop(); } } private async Task AcceptClientsAsync() { while (true) { var client = await _server.AcceptTcpClientAsync(); OnConnectionRecevied(client); } } private async void OnConnectionRecevied(TcpClient client) { var stream = client.GetStream(); if (!stream.CanRead) return; while (true) { try { var command = await ReadCommandAsync(stream); // Process command here and provide relevant response to the client await WriteResponseAsync(stream, "OK"); } catch(Exception ex) { return; } } } public void Stop() { _server?.Stop(); } private async Task<string> ReadCommandAsync(NetworkStream stream) { var commandLengthBuffer = new byte[sizeof(uint)]; if (await stream.ReadAsync(commandLengthBuffer, 0, commandLengthBuffer.Length) != commandLengthBuffer.Length) throw new Exception(); var commandBuffer = new byte[BitConverter.ToUInt32(commandLengthBuffer, 0)]; if (await stream.ReadAsync(commandBuffer, 0, commandBuffer.Length) != commandBuffer.Length) throw new Exception(); var command = Encoding.UTF8.GetString(commandBuffer); return command; } private async Task WriteResponseAsync(NetworkStream stream, string response) { var responseBuffer = Encoding.UTF8.GetBytes(response); var responseLengthBuffer = BitConverter.GetBytes((uint)responseBuffer.Length); await stream.WriteAsync(responseLengthBuffer, 0, responseLengthBuffer.Length); await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length); } private async Task<IPAddress> GetHostPAddressAsync() { var hostName = Dns.GetHostName(); var adresses = await Dns.GetHostAddressesAsync(hostName); return adresses.First(a => a.AddressFamily == AddressFamily.InterNetwork); } } |
Xamarin Client
Now, we are ready to implement the client in order to reach our server. Because of different Mono Frameworks for mobile platforms, it’s not possible to create a single class client and use it on iOS, Windows, and Android. Fortunately, the code is 99.9% the same, so all we need to do is copy our class to each project and then use DependencyService to access it from PCL (portable class library).
PCL abstract client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public abstract class SocketClient { public string Host { get; private set; } public int Port { get; private set; } public void Init(string host, int port) { Host = host; Port = port; } public abstract Task ConnectAsync(); public abstract Task<string> SendCommandAsync(string command); public abstract void Dispose(); } |
Platform-specific client
The platform-specific class inherits from abstract client implemented in PCL project and is marked with assembly attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
[assembly: Xamarin.Forms.Dependency(typeof(TcpSocketClient))] namespace SmartHome.Droid { public class TcpSocketClient : SocketClient { private const string UNDERLYING_SOCKET_CLOSED_BEFORE_READ = "The underlying socket was closed before read."; private TcpClient _client; private NetworkStream _stream; private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1); public override async Task ConnectAsync() { _client = new TcpClient(); try { await _client.ConnectAsync(Host, Port); } catch { Dispose(); } } public override async Task<string> SendCommandAsync(string command) { // Wait while previous command is proccessed await _semaphoreSlim.WaitAsync(); try { // Check connection if (_client == null) { await ConnectAsync(); } _stream = _client.GetStream(); await WriteCommandAsync(command); var response = await ReadResponseAsync(); return response; } catch { Dispose(); throw; } finally { _semaphoreSlim.Release(); } } public override void Dispose() { _stream?.Dispose(); _client?.Close(); _client = null; } private async Task WriteCommandAsync(string command) { var commandBuffer = Encoding.UTF8.GetBytes(command); var commandBufferLength = BitConverter.GetBytes((uint)commandBuffer.Length); await _stream.WriteAsync(commandBufferLength, 0, commandBufferLength.Length); await _stream.WriteAsync(commandBuffer, 0, commandBuffer.Length); } private async Task<string> ReadResponseAsync() { var responseLengthBuffer = new byte[sizeof(uint)]; if (await _stream.ReadAsync(responseLengthBuffer, 0, responseLengthBuffer.Length) != responseLengthBuffer.Length) throw new Exception(UNDERLYING_SOCKET_CLOSED_BEFORE_READ); var responseBuffer = new byte[BitConverter.ToUInt32(responseLengthBuffer, 0)]; if (await _stream.ReadAsync(responseBuffer, 0, responseBuffer.Length) != responseBuffer.Length) throw new Exception(UNDERLYING_SOCKET_CLOSED_BEFORE_READ); var response = Encoding.UTF8.GetString(responseBuffer); return response; } } } |
Accessing platform-specific client from PCL
1 |
SocketClient client = DependencyService.Get<SocketClient>(); |
Conclusion
And that’s pretty much all from Voice and Wi-Fi controlled Lamp tutorial 🙂
We’ve seen how to control simple electronic device, either with your voice or mobile phone. Hope it helps to you!