diff --git a/Core/Options/MqttOptions.cs b/Core/Options/MqttOptions.cs index 44f5199..c4fcbc5 100644 --- a/Core/Options/MqttOptions.cs +++ b/Core/Options/MqttOptions.cs @@ -6,5 +6,6 @@ public class MqttOptions public int Port { get; set; } public string Username { get; set; } public string ClientId { get; set; } - public string Topic { get; set; } + public string SubscribeTopic { get; set; } + public string PublishTopic { get; set; } } \ No newline at end of file diff --git a/Core/Services/ConditionsLogsService.cs b/Core/Services/ConditionsLogsService.cs index 55ef189..289e1ee 100644 --- a/Core/Services/ConditionsLogsService.cs +++ b/Core/Services/ConditionsLogsService.cs @@ -5,7 +5,7 @@ namespace Core.Services; -public class ConditionsLogsService (ConditionsLogsRepository conditionsLogsRepository, PlantRepository plantRepository) +public class ConditionsLogsService (ConditionsLogsRepository conditionsLogsRepository, PlantRepository plantRepository, MqttPublisherService mqttPublisherService) { public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditionsLogDto) { @@ -15,23 +15,36 @@ public async Task CreateConditionsLogAsync(CreateConditionsLogDto createConditio { throw new RegisterDeviceException(); } + + var recentMood = conditionsLogsRepository.GetRecentMoodAsync(plantId).Result; + var conditionsLog = new ConditionsLog { ConditionsId = new Guid(), - TimeStamp = DateTime.UtcNow, //TODO get this from the right place - SoilMoisture = CalculateSoilMoistureLevel(createConditionsLogDto.SoilMoisturePercentage), - LightLevel = CalculateLightLevel(createConditionsLogDto.LightLevel), - Temperature = CalculateTemperatureLevel(createConditionsLogDto.Temperature), - Humidity = CalculateHumidityLevel(createConditionsLogDto.Humidity), + TimeStamp = DateTime.UtcNow, + SoilMoisture = createConditionsLogDto.SoilMoisturePercentage, + Light = createConditionsLogDto.Light, + Temperature = createConditionsLogDto.Temperature, + Humidity = createConditionsLogDto.Humidity, PlantId = plantId }; - conditionsLog.Mood = CalculateMood(conditionsLog); - - Console.WriteLine("Conditions log created"); - Console.WriteLine(conditionsLog); + var newMood = CalculateMood(conditionsLog); + conditionsLog.Mood = newMood; await conditionsLogsRepository.CreateConditionsLogAsync(conditionsLog); + + + if (newMood != recentMood) + { + //TODO send Event to mobile app + var moodDto = new MoodDto + { + Mood = newMood, + }; + var deviceId = createConditionsLogDto.DeviceId; + await mqttPublisherService.PublishAsync(moodDto, deviceId); + } } private RequirementLevel CalculateTemperatureLevel (double value) @@ -78,18 +91,18 @@ private RequirementLevel CalculateHumidityLevel (double value) }; } - private int CalculateMood (Conditions conditions) + private int CalculateMood (ConditionsLog conditionsLog) { // Compare ideal requirements for humidity, temperature, soil moisture and light level with actual conditions and calculate mood from 0-4 // get ideal requirements from plant - var requirementsForPlant = plantRepository.GetRequirementsForPlant(conditions.PlantId); + var requirementsForPlant = plantRepository.GetRequirementsForPlant(conditionsLog.PlantId); // compare with actual conditions var mood = 0; // calculate mood - mood += CalculateScore((int)requirementsForPlant.Result.Humidity, (int)conditions.Humidity); - mood += CalculateScore((int)requirementsForPlant.Result.Temperature, (int)conditions.Temperature); - mood += CalculateScore((int)requirementsForPlant.Result.SoilMoisture, (int)conditions.SoilMoisture); - mood += CalculateScore((int)requirementsForPlant.Result.LightLevel, (int)conditions.LightLevel); + mood += CalculateScore((int)requirementsForPlant.Result.HumidityLevel, (int)CalculateHumidityLevel(conditionsLog.Humidity)); + mood += CalculateScore((int)requirementsForPlant.Result.TemperatureLevel, (int)CalculateTemperatureLevel(conditionsLog.Temperature)); + mood += CalculateScore((int)requirementsForPlant.Result.SoilMoistureLevel, (int)CalculateSoilMoistureLevel(conditionsLog.SoilMoisture)); + mood += CalculateScore((int)requirementsForPlant.Result.LightLevel, (int)CalculateLightLevel(conditionsLog.Light)); if (mood == 0) { diff --git a/Core/Services/MqttPublisherService.cs b/Core/Services/MqttPublisherService.cs new file mode 100644 index 0000000..39251ff --- /dev/null +++ b/Core/Services/MqttPublisherService.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using Core.Options; +using Microsoft.Extensions.Options; +using MQTTnet; +using MQTTnet.Client; +using Shared.Dtos; + +namespace Core.Services; + +public class MqttPublisherService +{ + private readonly IOptions _options; + + public MqttPublisherService(IOptions options) + { + _options = options; + + 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() + .WithTcpServer(_options.Value.Server, _options.Value.Port) + .WithCredentials(_options.Value.Username) + .Build(); + + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); + + var mqttPublishOptions = new MqttApplicationMessageBuilder() + .WithTopic($"{_options.Value.PublishTopic}/{deviceId}") + .WithPayload(JsonSerializer.Serialize(mood, options: new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase})) + .WithRetainFlag() + .WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce) + .Build(); + + await mqttClient.PublishAsync(mqttPublishOptions, CancellationToken.None); + } +} \ No newline at end of file diff --git a/Core/Services/MqttSubscriberService.cs b/Core/Services/MqttSubscriberService.cs index 98fb991..4ec0ba1 100644 --- a/Core/Services/MqttSubscriberService.cs +++ b/Core/Services/MqttSubscriberService.cs @@ -1,6 +1,7 @@ using System.Text; using System.Text.Json; using Core.Options; +using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Options; using MQTTnet; using MQTTnet.Client; @@ -31,13 +32,9 @@ public async Task SubscribeAsync() .WithTcpServer(_options.Value.Server, _options.Value.Port) .WithCredentials(_options.Value.Username) .Build(); - - // Setup message handling before connecting so that queued messages - // are also handled properly. When there is no event handler attached all - // received messages get lost. + mqttClient.ApplicationMessageReceivedAsync += async e => { - //var payload = e.ApplicationMessage.ConvertPayloadToString(); var payload = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment); var conditions = JsonSerializer.Deserialize(payload, options: new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); @@ -51,7 +48,7 @@ public async Task SubscribeAsync() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() .WithTopicFilter( - f => { f.WithTopic(_options.Value.Topic); }) + f => { f.WithTopic(_options.Value.SubscribeTopic); }) .Build(); await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); diff --git a/Infrastructure/Repositories/ConditionsLogsRepository.cs b/Infrastructure/Repositories/ConditionsLogsRepository.cs index 757dfc2..cfcd8d1 100644 --- a/Infrastructure/Repositories/ConditionsLogsRepository.cs +++ b/Infrastructure/Repositories/ConditionsLogsRepository.cs @@ -11,4 +11,24 @@ public async Task CreateConditionsLogAsync(ConditionsLog conditionsLog) await context.ConditionsLogs.AddAsync(conditionsLog); await context.SaveChangesAsync(); } + + public async Task GetRecentMoodAsync(Guid plantId) + { + await using var context = await dbContextFactory.CreateDbContextAsync(); + try + { + var recentMood = await context.ConditionsLogs + .Where(log => log.PlantId == plantId) + .OrderByDescending(log => log.TimeStamp) + .Select(log => log.Mood) + .FirstAsync(); + + return recentMood; + } + catch (InvalidOperationException) + { + // Return -1 if no entries are found + return -1; + } + } } \ No newline at end of file diff --git a/Shared/Dtos/CreateConditionsLogDto.cs b/Shared/Dtos/CreateConditionsLogDto.cs index ac9cc64..e34a17c 100644 --- a/Shared/Dtos/CreateConditionsLogDto.cs +++ b/Shared/Dtos/CreateConditionsLogDto.cs @@ -1,10 +1,9 @@ namespace Shared.Dtos; public class CreateConditionsLogDto -{ - public DateTime TimeStamp { get; set; } +{ public double SoilMoisturePercentage { get; set; } - public double LightLevel { get; set; } + public double Light { get; set; } public double Temperature { get; set; } public double Humidity { get; set; } public long DeviceId { get; set; } diff --git a/Shared/Dtos/FromClient/Requirements/CreateRequirementsDto.cs b/Shared/Dtos/FromClient/Requirements/CreateRequirementsDto.cs index 4826303..63af082 100644 --- a/Shared/Dtos/FromClient/Requirements/CreateRequirementsDto.cs +++ b/Shared/Dtos/FromClient/Requirements/CreateRequirementsDto.cs @@ -5,8 +5,8 @@ namespace Shared.Dtos.FromClient.Requirements; public class CreateRequirementsDto { public Guid? PlantId { get; set; } // is not sent with the request, but assigned when creating a plant - public RequirementLevel SoilMoisture { get; set; } + public RequirementLevel SoilMoistureLevel { get; set; } public RequirementLevel LightLevel { get; set; } - public RequirementLevel Temperature { get; set; } - public RequirementLevel Humidity { get; set; } + public RequirementLevel TemperatureLevel { get; set; } + public RequirementLevel HumidityLevel { get; set; } } \ No newline at end of file diff --git a/Shared/Dtos/FromClient/Requirements/UpdateRequirementDto.cs b/Shared/Dtos/FromClient/Requirements/UpdateRequirementDto.cs index 1710224..0f11954 100644 --- a/Shared/Dtos/FromClient/Requirements/UpdateRequirementDto.cs +++ b/Shared/Dtos/FromClient/Requirements/UpdateRequirementDto.cs @@ -5,8 +5,8 @@ namespace Shared.Dtos.FromClient.Requirements; public class UpdateRequirementDto { public Guid ConditionsId { get; set; } - public RequirementLevel SoilMoisture { get; set; } + public RequirementLevel SoilMoistureLevel { get; set; } public RequirementLevel LightLevel { get; set; } - public RequirementLevel Temperature { get; set; } - public RequirementLevel Humidity { get; set; } + public RequirementLevel TemperatureLevel { get; set; } + public RequirementLevel HumidityLevel { get; set; } } \ No newline at end of file diff --git a/Shared/Dtos/MoodDto.cs b/Shared/Dtos/MoodDto.cs new file mode 100644 index 0000000..b52b2c5 --- /dev/null +++ b/Shared/Dtos/MoodDto.cs @@ -0,0 +1,6 @@ +namespace Shared.Dtos; + +public class MoodDto +{ + public int Mood { get; set; } +} \ No newline at end of file diff --git a/Shared/Models/Information/Conditions.cs b/Shared/Models/Information/Conditions.cs index f75e550..38e5848 100644 --- a/Shared/Models/Information/Conditions.cs +++ b/Shared/Models/Information/Conditions.cs @@ -4,8 +4,8 @@ public abstract class Conditions { public Guid ConditionsId { get; set; } public Guid PlantId { get; set; } - public RequirementLevel SoilMoisture { get; set; } + public RequirementLevel SoilMoistureLevel { get; set; } public RequirementLevel LightLevel { get; set; } - public RequirementLevel Temperature { get; set; } - public RequirementLevel Humidity { get; set; } + public RequirementLevel TemperatureLevel { get; set; } + public RequirementLevel HumidityLevel { get; set; } } \ No newline at end of file diff --git a/Shared/Models/Information/ConditionsLog.cs b/Shared/Models/Information/ConditionsLog.cs index c7a8eaf..d35a31c 100644 --- a/Shared/Models/Information/ConditionsLog.cs +++ b/Shared/Models/Information/ConditionsLog.cs @@ -1,7 +1,13 @@ namespace Shared.Models.Information; -public class ConditionsLog : Conditions +public class ConditionsLog { + public Guid ConditionsId { get; set; } + public Guid PlantId { get; set; } public DateTime TimeStamp { get; set; } public int Mood { get; set; } + public double SoilMoisture { get; set; } + public double Light { get; set; } + public double Temperature { get; set; } + public double Humidity { get; set; } } \ No newline at end of file diff --git a/Shared/Models/Information/Requirements.cs b/Shared/Models/Information/Requirements.cs index 9620b21..45d90cb 100644 --- a/Shared/Models/Information/Requirements.cs +++ b/Shared/Models/Information/Requirements.cs @@ -12,19 +12,19 @@ public Requirements(CreateRequirementsDto createRequirementsDto) { ConditionsId = Guid.NewGuid(); PlantId = createRequirementsDto.PlantId!.Value; // plantID should be assigned to the dto before using this constructor - SoilMoisture = createRequirementsDto.SoilMoisture; + SoilMoistureLevel = createRequirementsDto.SoilMoistureLevel; LightLevel = createRequirementsDto.LightLevel; - Temperature = createRequirementsDto.Temperature; - Humidity = createRequirementsDto.Humidity; + TemperatureLevel = createRequirementsDto.TemperatureLevel; + HumidityLevel = createRequirementsDto.HumidityLevel; } public Requirements(UpdateRequirementDto updateRequirementDto, Guid plantId) { ConditionsId = updateRequirementDto.ConditionsId; PlantId = plantId; - SoilMoisture = updateRequirementDto.SoilMoisture; + SoilMoistureLevel = updateRequirementDto.SoilMoistureLevel; LightLevel = updateRequirementDto.LightLevel; - Temperature = updateRequirementDto.Temperature; - Humidity = updateRequirementDto.Humidity; + TemperatureLevel = updateRequirementDto.TemperatureLevel; + HumidityLevel = updateRequirementDto.HumidityLevel; } } \ No newline at end of file diff --git a/api/Program.cs b/api/Program.cs index 691004e..146de78 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -53,6 +53,7 @@ public static async Task StartApi(string[] args) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // TODO: add repositories builder.Services.AddAsyncApiSchemaGeneration(o =>