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 publisher set up to send mood to flespi upon change #7

Merged
merged 1 commit into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Core/Options/MqttOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
45 changes: 29 additions & 16 deletions Core/Services/ConditionsLogsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down
42 changes: 42 additions & 0 deletions Core/Services/MqttPublisherService.cs
Original file line number Diff line number Diff line change
@@ -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<MqttOptions> _options;

public MqttPublisherService(IOptions<MqttOptions> 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);
}
}
9 changes: 3 additions & 6 deletions Core/Services/MqttSubscriberService.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<CreateConditionsLogDto>(payload, options:
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
Expand All @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions Infrastructure/Repositories/ConditionsLogsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,24 @@ public async Task CreateConditionsLogAsync(ConditionsLog conditionsLog)
await context.ConditionsLogs.AddAsync(conditionsLog);
await context.SaveChangesAsync();
}

public async Task<int> 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;
}
}
}
5 changes: 2 additions & 3 deletions Shared/Dtos/CreateConditionsLogDto.cs
Original file line number Diff line number Diff line change
@@ -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; }
Expand Down
6 changes: 3 additions & 3 deletions Shared/Dtos/FromClient/Requirements/CreateRequirementsDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
6 changes: 3 additions & 3 deletions Shared/Dtos/FromClient/Requirements/UpdateRequirementDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
6 changes: 6 additions & 0 deletions Shared/Dtos/MoodDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Shared.Dtos;

public class MoodDto
{
public int Mood { get; set; }
}
6 changes: 3 additions & 3 deletions Shared/Models/Information/Conditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
8 changes: 7 additions & 1 deletion Shared/Models/Information/ConditionsLog.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 6 additions & 6 deletions Shared/Models/Information/Requirements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
1 change: 1 addition & 0 deletions api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public static async Task<WebApplication> StartApi(string[] args)
builder.Services.AddSingleton<RequirementService>();
builder.Services.AddSingleton<ConditionsLogsService>();
builder.Services.AddSingleton<MqttSubscriberService>();
builder.Services.AddSingleton<MqttPublisherService>();
// TODO: add repositories

builder.Services.AddAsyncApiSchemaGeneration(o =>
Expand Down
Loading