TCP support in Azure IoT Hub
The default Protocol Gateway sample are indeed somewhat confusing because of all the MQTT code.The protocol gateway works by 'simulating' a IoTHub connection for each custom protocol device you connect to the gateway.
To do this translation from the TCP device to an IoTHub device you first need to have a connection to the IoTHub on behalf of the device. This is the gateway part.Below is the core essentials for this IoTHubConnection.
namespace GatewayTest{ using System; using System.Text; using System.Threading; using System.Threading.Tasks; using DotNetty.Buffers; using Microsoft.Azure.Devices.ProtocolGateway.Identity; using Microsoft.Azure.Devices.ProtocolGateway.IotHubClient; using Microsoft.Azure.Devices.ProtocolGateway.Messaging; public class IoTHubConnection : IMessagingChannel<IMessage> { private readonly string iotHubHostName; private readonly Func<IDeviceIdentity, Task<IMessagingServiceClient>> deviceClientFactory; private readonly Func<string, Task> onMessage; private IMessagingServiceClient deviceClient; private IDeviceIdentity deviceIdentity; public IoTHubConnection( string iotHubHostName, Func<IDeviceIdentity, Task<IMessagingServiceClient>> deviceClientFactory, Func<string, Task> onMessage) { this.iotHubHostName = iotHubHostName; this.deviceClientFactory = deviceClientFactory; this.onMessage = onMessage; } public event EventHandler CapabilitiesChanged; public async Task OpenAsync(string deviceId, string deviceKey) { this.deviceIdentity = this.GetDeviceIdentity(deviceId, deviceKey); if (this.deviceIdentity != UnauthenticatedDeviceIdentity.Instance) { this.deviceClient = await this.deviceClientFactory(this.deviceIdentity); this.deviceClient.BindMessagingChannel(this); } } public async Task CloseAsync() { await this.deviceClient.DisposeAsync(null); this.deviceClient = null; } public void Handle(IMessage message) { var messageBody = message.Payload.ToString(Encoding.UTF8); this.onMessage(messageBody); this.deviceClient.CompleteAsync(message.Id); } public Task SendMessage(string message) { var buffer = Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message)); var deviceMessage = this.deviceClient.CreateMessage($"devices/{this.deviceIdentity.Id}/messages/events", buffer); return this.deviceClient.SendAsync(deviceMessage); } protected virtual void OnCapabilitiesChanged(EventArgs e) { this.CapabilitiesChanged?.Invoke(this, e); } private IDeviceIdentity GetDeviceIdentity(string userName, string deviceKey) { IotHubDeviceIdentity ideviceIdentity; if (!IotHubDeviceIdentity.TryParse($"{this.iotHubHostName}/{userName}", out ideviceIdentity)) { return UnauthenticatedDeviceIdentity.Instance; } ideviceIdentity.WithDeviceKey(deviceKey); return ideviceIdentity; } }}
The deviceClientFactory callback method should be implemented as shown below and in this line in the ProtocolGateway repo in Github.
deviceClientFactory = IotHubClient.PreparePoolFactory( "IotHubConnectionString", 400, TimeSpan.FromMinutes(3), iotHubClientSettings, PooledByteBufferAllocator.Default, new ConfigurableMessageAddressConverter("TopicNameConversion"));
When a Tcp Device connects to the protocol, you should create an instance of this IoTHubConnection and send messages from the Device to the IoTHubConnection and vica versa.The code below shows a very simple version of how this should be done.
private const int BufferSize = 1024;private byte[] buffer = new byte[BufferSize];private IoTHubConnection ioTHubConnection;private NetworkStream stream;private async Task Start(){ listener = new TcpListener(IPAddress.Any, port); listener.Start(); var client = await listener.AcceptTcpClientAsync(); ioTHubConnection = new IoTHubConnection("IoTHubName", deviceClientFactory, OnIoTHubMessage); stream = client.GetStream(); // Read DeviceId and DeviceKey from some sort of StartConnection-message send by the TcpClient. await ioTHubConnection.OpenAsync("DeviceId", "DeviceKey"); stream.BeginRead(buffer, 0, BufferSize, ReadTcpStreamCallback, null);}private void ReadTcpStreamCallback(IAsyncResult ar){ var bytesRead = stream.EndRead(ar); if (bytesRead > 0) { var message = System.Text.Encoding.ASCII.GetString(result); ioTHubConnection.SendMessage(message); // Read again. stream.BeginRead(buffer, 0, BufferSize, ReadTcpStreamCallback, null); }}private async Task OnIoTHubMessage(string message){ // Potentially do some translation on the IoTHub message // and send it to the Device var byteData = Encoding.UTF8.GetBytes(message); stream.BeginWrite(byteData, 0, byteData.Length, SendTcpCallback, null);}private void SendTcpCallback(IAsyncResult ar){ stream.EndWrite(ar);}
I know I am late to this conversation. However I have interesting add on or might be a solution for some.
Azure IoT Gateway is now known as Azure IoT Edge, this is clearly mentioned in the following Azure github repo
https://github.com/Azure/iot-edge-modbus.git
On the other hand, Azure IoT Edge supports TCP for some protocols which can be found in the following links