Added Mqtt and SignalR
This commit is contained in:
parent
b87d90e5a4
commit
3632e56dc4
33
Birdmap.API/Extensions/ServiceCollectionExtensions.cs
Normal file
33
Birdmap.API/Extensions/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Birdmap.API.Options;
|
||||
using Birdmap.API.Services.Mqtt;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
|
||||
namespace Birdmap.API.Extensions
|
||||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddMqttClientServiceWithConfig(this IServiceCollection services, Action<AspCoreMqttClientOptions> configureOptions)
|
||||
{
|
||||
services.AddSingleton(serviceProvider =>
|
||||
{
|
||||
var optionBuilder = new AspCoreMqttClientOptions(serviceProvider);
|
||||
configureOptions(optionBuilder);
|
||||
return optionBuilder.Build();
|
||||
});
|
||||
services.AddSingleton<MqttClientService>();
|
||||
services.AddSingleton<IHostedService>(serviceProvider =>
|
||||
{
|
||||
return serviceProvider.GetService<MqttClientService>();
|
||||
});
|
||||
services.AddSingleton(serviceProvider =>
|
||||
{
|
||||
var mqttClientService = serviceProvider.GetService<MqttClientService>();
|
||||
var mqttClientServiceProvider = new MqttClientServiceProvider(mqttClientService);
|
||||
return mqttClientServiceProvider;
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
22
Birdmap.API/Options/AspCoreMqttClientOptions.cs
Normal file
22
Birdmap.API/Options/AspCoreMqttClientOptions.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using MQTTnet.Client.Options;
|
||||
using System;
|
||||
|
||||
namespace Birdmap.API.Options
|
||||
{
|
||||
public class AspCoreMqttClientOptions : MqttClientOptionsBuilder
|
||||
{
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
public AspCoreMqttClientOptions(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public AspCoreMqttClientOptions WithTopic(string topic)
|
||||
{
|
||||
WithUserProperty("Topic", topic);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
36
Birdmap.API/Services/Hubs/DevicesHub.cs
Normal file
36
Birdmap.API/Services/Hubs/DevicesHub.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Birdmap.API.Services.Hubs
|
||||
{
|
||||
public class DevicesHub : Hub<IDevicesHubClient>
|
||||
{
|
||||
private readonly ILogger<DevicesHub> _logger;
|
||||
|
||||
public DevicesHub(ILogger<DevicesHub> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override Task OnConnectedAsync()
|
||||
{
|
||||
_logger.LogInformation("Client connected.");
|
||||
|
||||
return base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
_logger.LogInformation("Client disconnected.");
|
||||
|
||||
return base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public Task UserJoinedAsync(Guid deviceId, DateTime date, double probability)
|
||||
{
|
||||
return Clients.All.NotifyDeviceAsync(deviceId, date, probability);
|
||||
}
|
||||
}
|
||||
}
|
10
Birdmap.API/Services/Hubs/IDevicesHubClient.cs
Normal file
10
Birdmap.API/Services/Hubs/IDevicesHubClient.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Birdmap.API.Services.Hubs
|
||||
{
|
||||
public interface IDevicesHubClient
|
||||
{
|
||||
Task NotifyDeviceAsync(Guid deviceId, DateTime date, double probability);
|
||||
}
|
||||
}
|
15
Birdmap.API/Services/IMqttClientService.cs
Normal file
15
Birdmap.API/Services/IMqttClientService.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MQTTnet.Client.Connecting;
|
||||
using MQTTnet.Client.Disconnecting;
|
||||
using MQTTnet.Client.Receiving;
|
||||
|
||||
namespace Birdmap.API.Services
|
||||
{
|
||||
public interface IMqttClientService : IHostedService,
|
||||
IMqttClientConnectedHandler,
|
||||
IMqttClientDisconnectedHandler,
|
||||
IMqttApplicationMessageReceivedHandler
|
||||
{
|
||||
|
||||
}
|
||||
}
|
134
Birdmap.API/Services/Mqtt/MqttClientService.cs
Normal file
134
Birdmap.API/Services/Mqtt/MqttClientService.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using Birdmap.API.Services.Hubs;
|
||||
using Birdmap.BLL.Interfaces;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Client.Connecting;
|
||||
using MQTTnet.Client.Disconnecting;
|
||||
using MQTTnet.Client.Options;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Birdmap.API.Services.Mqtt
|
||||
{
|
||||
public class MqttClientService : IMqttClientService
|
||||
{
|
||||
private readonly IMqttClient _mqttClient;
|
||||
private readonly IMqttClientOptions _options;
|
||||
private readonly ILogger<MqttClientService> _logger;
|
||||
private readonly IInputService _inputService;
|
||||
private readonly IHubContext<DevicesHub, IDevicesHubClient> _hubContext;
|
||||
|
||||
public MqttClientService(IMqttClientOptions options, ILogger<MqttClientService> logger, IInputService inputService, IHubContext<DevicesHub, IDevicesHubClient> hubContext)
|
||||
{
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
_inputService = inputService;
|
||||
_hubContext = hubContext;
|
||||
_mqttClient = new MqttFactory().CreateMqttClient();
|
||||
ConfigureMqttClient();
|
||||
}
|
||||
|
||||
private void ConfigureMqttClient()
|
||||
{
|
||||
_mqttClient.ConnectedHandler = this;
|
||||
_mqttClient.DisconnectedHandler = this;
|
||||
_mqttClient.ApplicationMessageReceivedHandler = this;
|
||||
}
|
||||
|
||||
private class Payload
|
||||
{
|
||||
[JsonProperty("tag")]
|
||||
public Guid TagID { get; set; }
|
||||
|
||||
[JsonProperty("probability")]
|
||||
public double Probability { get; set; }
|
||||
}
|
||||
|
||||
public async Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||
{
|
||||
var message = eventArgs.ApplicationMessage.ConvertPayloadToString();
|
||||
|
||||
_logger.LogInformation($"Recieved [{eventArgs.ClientId}] " +
|
||||
$"Topic: {eventArgs.ApplicationMessage.Topic} | Payload: {message} | QoS: {eventArgs.ApplicationMessage.QualityOfServiceLevel} | Retain: {eventArgs.ApplicationMessage.Retain}");
|
||||
|
||||
var payload = JsonConvert.DeserializeObject<Payload>(message);
|
||||
var inputResponse = await _inputService.GetInputAsync(payload.TagID);
|
||||
|
||||
await _hubContext.Clients.All.NotifyDeviceAsync(inputResponse.Message.Device_id, inputResponse.Message.Date.UtcDateTime, payload.Probability);
|
||||
}
|
||||
|
||||
public async Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
var topic = _options.UserProperties.SingleOrDefault(up => up.Name == "Topic")?.Value;
|
||||
_logger.LogInformation($"Connected. Auth result: {eventArgs.AuthenticateResult}. Subscribing to topic: {topic}");
|
||||
|
||||
await _mqttClient.SubscribeAsync(topic);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Cannot subscribe...");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_logger.LogWarning(eventArgs.Exception, $"Disconnected. Reason {eventArgs.ReasonCode}. Auth result: {eventArgs.AuthenticateResult}. Reconnecting...");
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
await _mqttClient.ConnectAsync(_options, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Reconnect failed...");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _mqttClient.ConnectAsync(_options);
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
await _mqttClient.ReconnectAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Cannot connect...");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var disconnectOption = new MqttClientDisconnectOptions
|
||||
{
|
||||
ReasonCode = MqttClientDisconnectReason.NormalDisconnection,
|
||||
ReasonString = "NormalDiconnection"
|
||||
};
|
||||
await _mqttClient.DisconnectAsync(disconnectOption, cancellationToken);
|
||||
}
|
||||
await _mqttClient.DisconnectAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Cannot disconnect...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Birdmap.API/Services/Mqtt/MqttClientServiceProvider.cs
Normal file
12
Birdmap.API/Services/Mqtt/MqttClientServiceProvider.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Birdmap.API.Services.Mqtt
|
||||
{
|
||||
public class MqttClientServiceProvider
|
||||
{
|
||||
public IMqttClientService MqttClientService { get; }
|
||||
|
||||
public MqttClientServiceProvider(IMqttClientService mqttClientService)
|
||||
{
|
||||
MqttClientService = mqttClientService;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
using AutoMapper;
|
||||
using Birdmap.API.Extensions;
|
||||
using Birdmap.API.Middlewares;
|
||||
using Birdmap.API.Services.Hubs;
|
||||
using Birdmap.BLL;
|
||||
using Birdmap.DAL;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
@ -39,6 +41,8 @@ namespace Birdmap.API
|
||||
|
||||
services.AddAutoMapper(typeof(Startup));
|
||||
|
||||
services.AddSignalR();
|
||||
|
||||
var key = Encoding.ASCII.GetBytes(Configuration["Secret"]);
|
||||
services.AddAuthentication(opt =>
|
||||
{
|
||||
@ -59,6 +63,33 @@ namespace Birdmap.API
|
||||
};
|
||||
});
|
||||
|
||||
services.AddMqttClientServiceWithConfig(opt =>
|
||||
{
|
||||
var mqtt = Configuration.GetSection("Mqtt");
|
||||
|
||||
var mqttClient = mqtt.GetSection("ClientSettings");
|
||||
var clientSettings = new
|
||||
{
|
||||
Id = mqttClient.GetValue<string>("Id"),
|
||||
Username = mqttClient.GetValue<string>("Username"),
|
||||
Password = mqttClient.GetValue<string>("Password"),
|
||||
Topic = mqttClient.GetValue<string>("Topic"),
|
||||
};
|
||||
|
||||
var mqttBrokerHost = mqtt.GetSection("BrokerHostSettings");
|
||||
var brokerHostSettings = new
|
||||
{
|
||||
Host = mqttBrokerHost.GetValue<string>("Host"),
|
||||
Port = mqttBrokerHost.GetValue<int>("Port"),
|
||||
};
|
||||
|
||||
opt
|
||||
.WithTopic(clientSettings.Topic)
|
||||
.WithCredentials(clientSettings.Username, clientSettings.Password)
|
||||
.WithClientId(clientSettings.Id)
|
||||
.WithTcpServer(brokerHostSettings.Host, brokerHostSettings.Port);
|
||||
});
|
||||
|
||||
// In production, the React files will be served from this directory
|
||||
services.AddSpaStaticFiles(configuration =>
|
||||
{
|
||||
@ -91,6 +122,7 @@ namespace Birdmap.API
|
||||
{
|
||||
endpoints.MapHealthChecks("/health");
|
||||
endpoints.MapControllers();
|
||||
endpoints.MapHub<DevicesHub>("/hubs/devices");
|
||||
});
|
||||
|
||||
app.UseSpa(spa =>
|
||||
|
@ -28,5 +28,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"UseDummyServices": true
|
||||
"UseDummyServices": true,
|
||||
"Mqtt": {
|
||||
"BrokerHostSettings": {
|
||||
"Host": "localhost",
|
||||
"Port": 1883
|
||||
},
|
||||
|
||||
"ClientSettings": {
|
||||
"Id": "ASP.NET Core client",
|
||||
"Username": "username",
|
||||
"Password": "password",
|
||||
"Topic": "devices/output"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,14 @@
|
||||
<targets async="true">
|
||||
<default-target-parameters xsi:type="File" keepFileOpen="false" maxArchiveFiles="10" archiveAboveSize="1048576"/>
|
||||
<target xsi:type="File" name="allFile" fileName="${basedir}Log/birdmap-all-${shortdate}.log"
|
||||
layout="${longdate} [${event-properties:item=EventId_Id}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
|
||||
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
|
||||
|
||||
<target xsi:type="File" name="mqttFile" fileName="${basedir}Log/birdmap-mqtt-${shortdate}.log"
|
||||
layout="${longdate} [${event-properties:item=EventId_Id}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
|
||||
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${logger} - ${message} ${exception:format=tostring}" />
|
||||
|
||||
<!-- another file log, only own logs. Uses some ASP.NET core renderers -->
|
||||
<target xsi:type="File" name="ownFile" fileName="${basedir}Log/birdmap-own-${shortdate}.log"
|
||||
layout="${longdate} [${event-properties:item=EventId_Id}] ${uppercase:${level}} ${callsite} - ${message} ${exception:format=tostring} (url: ${aspnet-request-url})(action: ${aspnet-mvc-action})" />
|
||||
layout="${longdate} [${threadname:whenEmpty=${threadid}}] ${uppercase:${level}} ${callsite} - ${message} ${exception:format=tostring} (url: ${aspnet-request-url})(action: ${aspnet-mvc-action})" />
|
||||
</targets>
|
||||
|
||||
<!-- rules to map from logger name to target +-->
|
||||
@ -31,7 +31,7 @@
|
||||
<logger name="*" minlevel="Trace" writeTo="allFile" />
|
||||
|
||||
<!--Skip non-critical Mqtt logs-->
|
||||
<logger name="*.MqttDevicesController.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile"/>
|
||||
<logger name="*.*Mqtt*.*" minlevel="Trace" maxlevel="Warning" writeTo="mqttFile" final="true"/>
|
||||
|
||||
<!--Skip non-critical Microsoft logs-->
|
||||
<logger name="Microsoft.*" maxlevel="Info" final="true" />
|
||||
|
Loading…
Reference in New Issue
Block a user