Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mqtt business #21

Merged
merged 12 commits into from
May 27, 2024
Merged
6 changes: 0 additions & 6 deletions BotaniQue-Fullstack.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
docker-compose.yml = docker-compose.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{3719CB65-7BBB-44FE-8666-FB37D144A2AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{242DADC6-D834-4F15-BCA4-E373D490F705}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Shared.csproj", "{799F730A-C9F6-441D-9578-178238799393}"
Expand All @@ -25,10 +23,6 @@ Global
{C0A8D4C5-7198-4EDE-8622-8FDC43A03C99}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0A8D4C5-7198-4EDE-8622-8FDC43A03C99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0A8D4C5-7198-4EDE-8622-8FDC43A03C99}.Release|Any CPU.Build.0 = Release|Any CPU
{3719CB65-7BBB-44FE-8666-FB37D144A2AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3719CB65-7BBB-44FE-8666-FB37D144A2AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3719CB65-7BBB-44FE-8666-FB37D144A2AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3719CB65-7BBB-44FE-8666-FB37D144A2AA}.Release|Any CPU.Build.0 = Release|Any CPU
{242DADC6-D834-4F15-BCA4-E373D490F705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{242DADC6-D834-4F15-BCA4-E373D490F705}.Debug|Any CPU.Build.0 = Debug|Any CPU
{242DADC6-D834-4F15-BCA4-E373D490F705}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
23 changes: 0 additions & 23 deletions Core/Core.csproj

This file was deleted.

5 changes: 3 additions & 2 deletions Infrastructure/Repositories/ConditionsLogsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ namespace Infrastructure.Repositories;

public class ConditionsLogsRepository (IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
public async Task CreateConditionsLogAsync(ConditionsLog conditionsLog)
public async Task<ConditionsLog> CreateConditionsLogAsync(ConditionsLog conditionsLog)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
await context.ConditionsLogs.AddAsync(conditionsLog);
var entityEntry = await context.ConditionsLogs.AddAsync(conditionsLog);
await context.SaveChangesAsync();
return entityEntry.Entity;
}

public async Task<int> GetRecentMoodAsync(Guid plantId)
Expand Down
4 changes: 2 additions & 2 deletions Infrastructure/Repositories/RequirementsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace Infrastructure.Repositories;

public class RequirementsRepository(IDbContextFactory<ApplicationDbContext> dbContextFactory)
{
public async Task<Requirements?> GetRequirements(Guid requirementsId)
public async Task<Requirements?> GetRequirementsForPlant(Guid plantId)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
return await context.Requirements.FirstOrDefaultAsync(r => r.RequirementsId == requirementsId);
return await context.Requirements.FirstOrDefaultAsync(r => r.PlantId == plantId);
}

public async Task<Requirements> CreateRequirements(Requirements requirements)
Expand Down
9 changes: 9 additions & 0 deletions Infrastructure/Repositories/UserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,13 @@ public async Task UpdatePassword(User userToUpdate, string password)
context.Users.Update(userToUpdate);
await context.SaveChangesAsync();
}

public async Task<string?> GetUserByDeviceId(string deviceId)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
return await context.Users
.Where(u => u.Plants.Any(p => p.DeviceId == deviceId))
.Select(u => u.UserEmail)
.FirstOrDefaultAsync();
}
}
9 changes: 9 additions & 0 deletions Shared/Wrappers/ClientConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Fleck;

namespace Shared.Wrappers;

public class ClientConnection
{
public required IWebSocketConnection Connection { get; set; }
public string? Email { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core.Options;
namespace api.Core.Options;

public class AzureBlobStorageOptions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core.Options;
namespace api.Core.Options;

public class AzureVisionOptions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core.Options;
namespace api.Core.Options;

public class JwtOptions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
namespace Core.Options;
namespace api.Core.Options;

public class MqttOptions
{
public string Server { get; set; }

Check warning on line 5 in api/Core/Options/MqttOptions.cs

View workflow job for this annotation

GitHub Actions / tests

Non-nullable property 'Server' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public int Port { get; set; }
public string Username { get; set; }

Check warning on line 7 in api/Core/Options/MqttOptions.cs

View workflow job for this annotation

GitHub Actions / tests

Non-nullable property 'Username' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string ClientId { get; set; }

Check warning on line 8 in api/Core/Options/MqttOptions.cs

View workflow job for this annotation

GitHub Actions / tests

Non-nullable property 'ClientId' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string SubscribeTopic { get; set; }

Check warning on line 9 in api/Core/Options/MqttOptions.cs

View workflow job for this annotation

GitHub Actions / tests

Non-nullable property 'SubscribeTopic' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public string PublishTopic { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using Infrastructure.Repositories;
using Serilog;
using Shared.Dtos;
using Shared.Dtos.FromClient.Collections;
using Shared.Exceptions;
using Shared.Models;

namespace Core.Services;
namespace api.Core.Services;

public class CollectionsService(CollectionsRepository collectionsRepository, PlantService plantService)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Infrastructure.Repositories;
using api.Events.Conditions.Server;
using api.Extensions;
using Infrastructure.Repositories;
using Shared.Dtos;
using Shared.Exceptions;
using Shared.Models.Information;

namespace Core.Services;
namespace api.Core.Services;

public class ConditionsLogsService (ConditionsLogsRepository conditionsLogsRepository, PlantService plantService, RequirementService requirementService ,MqttPublisherService mqttPublisherService)
public class ConditionsLogsService (WebSocketConnectionService webSocketConnectionService, UserService userService, ConditionsLogsRepository conditionsLogsRepository, PlantService plantService, RequirementService requirementService ,MqttPublisherService mqttPublisherService)
{
private const int TemperatureTolerance = 1;

Expand All @@ -18,7 +20,7 @@ public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditio
throw new RegisterDeviceException();
}

var recentMood = conditionsLogsRepository.GetRecentMoodAsync(plantId).Result;
var recentMood = await conditionsLogsRepository.GetRecentMoodAsync(plantId);

var conditionsLog = new ConditionsLog
{
Expand All @@ -35,17 +37,22 @@ public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditio
var newMood = await CalculateMood(conditionsLog);
conditionsLog.Mood = newMood;

await conditionsLogsRepository.CreateConditionsLogAsync(conditionsLog);
var addedLog = await conditionsLogsRepository.CreateConditionsLogAsync(conditionsLog);

var email = await userService.GetEmailFromDeviceId(createConditionsLogDto.DeviceId.ToString());
var connection = webSocketConnectionService.GetConnectionByEmail(email);
connection?.SendDto(new ServerSendsLatestConditionsForPlant
{
ConditionsLog = addedLog
});

if (newMood != recentMood)
{
//TODO send Event to mobile app
var moodDto = new MoodDto
{
Mood = newMood,
Mood = newMood
};
var deviceId = createConditionsLogDto.DeviceId;
await mqttPublisherService.PublishAsync(moodDto, deviceId);
await mqttPublisherService.PublishAsync(moodDto, createConditionsLogDto.DeviceId);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core.Services.External.BackgroundRemoval;
namespace api.Core.Services.External.BackgroundRemoval;

public interface IImageBackgroundRemoverService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Net;
using System.Net.Http.Headers;
using Core.Options;
using api.Core.Options;
using Microsoft.Extensions.Options;
using Shared.Exceptions;

namespace Core.Services.External.BackgroundRemoval;
namespace api.Core.Services.External.BackgroundRemoval;

public class ImageBackgroundRemoverService(IOptions<AzureVisionOptions> options) : IImageBackgroundRemoverService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Core.Services.External.BackgroundRemoval;

namespace Core.Services.External;
namespace api.Core.Services.External.BackgroundRemoval;

public class MockImageBackgroundRemoverService : IImageBackgroundRemoverService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Net;
using api.Core.Options;
using Azure.Storage.Blobs;
using Azure.Storage.Sas;
using Core.Options;
using Microsoft.Extensions.Options;
using Shared.Exceptions;

namespace Core.Services.External.BlobStorage;
namespace api.Core.Services.External.BlobStorage;

public class BlobStorageService(IOptions<AzureBlobStorageOptions> azureBlobStorageOptions) : IBlobStorageService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Azure.Storage.Blobs;

namespace Core.Services.External.BlobStorage;
namespace api.Core.Services.External.BlobStorage;

public interface IBlobStorageService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Core.Services.External.BlobStorage;
namespace api.Core.Services.External.BlobStorage;

public class MockBlobStorageService : IBlobStorageService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Security.Authentication;
using Core.Options;
using api.Core.Options;
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
Expand All @@ -8,7 +8,7 @@
using Serilog;
using Shared.Models.Identity;

namespace Core.Services;
namespace api.Core.Services;

public class JwtService(IOptions<JwtOptions> jwtOptions)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
using System.Text.Json;
using Core.Options;
using api.Core.Options;
using Microsoft.Extensions.Options;
using MQTTnet;
using MQTTnet.Client;
using Shared.Dtos;

namespace Core.Services;
namespace api.Core.Services;

public class MqttPublisherService
{
private readonly IOptions<MqttOptions> _options;
private readonly JsonSerializerOptions _jsonSerializerOptions;


public MqttPublisherService(IOptions<MqttOptions> options)
{
_options = options;
_jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };


if (string.IsNullOrEmpty(_options.Value.Username) || _options.Value.Username == "FILL_ME_IN")
throw new Exception("MQTT username not set in appsettings.json");
}
public async Task PublishAsync(MoodDto mood, long deviceId)
{
var mqttFactory = new MqttFactory();


using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
Expand All @@ -32,7 +37,7 @@ public async Task PublishAsync(MoodDto mood, long deviceId)

var mqttPublishOptions = new MqttApplicationMessageBuilder()
.WithTopic($"{_options.Value.PublishTopic}/{deviceId}")
.WithPayload(JsonSerializer.Serialize(mood, options: new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase}))
.WithPayload(JsonSerializer.Serialize(mood, options: _jsonSerializerOptions))
.WithRetainFlag()
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
.Build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
using System.Text;
using System.Text.Json;
using Core.Options;
using api.Core.Options;
using Microsoft.Extensions.Options;
using MQTTnet;
using MQTTnet.Client;
using Shared.Dtos;

namespace Core.Services;
namespace api.Core.Services;

public class MqttSubscriberService
{
private readonly IOptions<MqttOptions> _options;
private readonly ConditionsLogsService _conditionsLogService;
private readonly JsonSerializerOptions _jsonSerializerOptions;

public MqttSubscriberService(IOptions<MqttOptions> options, ConditionsLogsService conditionsLogService)
{
_options = options;
_conditionsLogService = conditionsLogService;
_jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };

if (string.IsNullOrEmpty(_options.Value.Username) || _options.Value.Username == "FILL_ME_IN")
throw new Exception("MQTT username not set in appsettings.json");
Expand All @@ -34,21 +36,20 @@ public async Task SubscribeAsync()

mqttClient.ApplicationMessageReceivedAsync += async e =>
{
var payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment);
var payload = Encoding.UTF8.GetString((ReadOnlySpan<byte>)e.ApplicationMessage.PayloadSegment);
var conditions = JsonSerializer.Deserialize<CreateConditionsLogDto>(payload, options:
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (conditions is null) return;

await _conditionsLogService.CreateConditionsLogAsync(conditions);
_jsonSerializerOptions);

if (conditions is not null)
{
await _conditionsLogService.CreateConditionsLogAsync(conditions);
}
};

await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f => { f.WithTopic(_options.Value.SubscribeTopic); })
.Build();
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter(
f => { f.WithTopic(_options.Value.SubscribeTopic); }).Build();

await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using Core.Options;
using Core.Services.External.BlobStorage;
using api.Core.Options;
using api.Core.Services.External.BlobStorage;
using Infrastructure.Repositories;
using Microsoft.Extensions.Options;
using Shared.Dtos.FromClient.Plant;
using Shared.Exceptions;
using Shared.Models;
using Shared.Models.Information;

namespace Core.Services;
namespace api.Core.Services;

public class PlantService(
PlantRepository plantRepository,
Expand Down
Loading
Loading