Skip to content

Commit

Permalink
Feature: Initialise Command Controller (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
willsawyerrrr authored Oct 13, 2024
2 parents 871e345 + a1c3eeb commit b672298
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 776 deletions.
12 changes: 0 additions & 12 deletions Mortein.IntegrationTests/IndexTests.cs

This file was deleted.

28 changes: 0 additions & 28 deletions Mortein.IntegrationTests/Mortein.IntegrationTests.csproj

This file was deleted.

729 changes: 0 additions & 729 deletions Mortein.IntegrationTests/packages.lock.json

This file was deleted.

52 changes: 52 additions & 0 deletions Mortein.Types/Command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Text.Json.Serialization;
using NodaTime;

namespace Mortein.Types;


public enum CommandType
{
ToggleVibration,
VibrateForDuration,
}


/// <summary>
/// Command for a device.
/// </summary>
[JsonDerivedType(typeof(ToggleVibrationCommand))]
[JsonDerivedType(typeof(VibrateForDurationCommand))]
public abstract class Command
{
/// <summary>
/// The unique identifier of a device.
/// </summary>
public required Guid DeviceId { get; set; }

/// <summary>
/// The timestamp for this command.
/// </summary>
public Instant Timestamp { get; set; } = SystemClock.Instance.GetCurrentInstant();

/// <summary>
/// The type of command.
/// </summary>
public abstract CommandType Type { get; }
}


public class ToggleVibrationCommand : Command
{
public override CommandType Type { get => CommandType.ToggleVibration; }
}


public class VibrateForDurationCommand : Command
{
public override CommandType Type { get => CommandType.VibrateForDuration; }

/// <summary>
/// The duration for which to vibrate the device.
/// </summary>
public required int Seconds { get; set; }
}
99 changes: 99 additions & 0 deletions Mortein/Controllers/CommandController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using Microsoft.AspNetCore.Mvc;
using Mortein.Mqtt.Services;
using Mortein.Types;
using MQTTnet.Client;
using NodaTime.Serialization.SystemTextJson;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Mortein.Controllers;


/// <summary>
/// API controller for device commands.
/// </summary>
///
/// <param name="context">The context which enables interaction with the database.</param>
/// <param name="clientService">The service exposing the client which enables publishing to the MQTT client.</param>
[ApiController]
[Route("Device/{deviceId}/[controller]")]
public class CommandController(DatabaseContext context, MqttClientService clientService) : ControllerBase
{
/// The context which enables interaction with the database.
private readonly DatabaseContext _context = context;

/// The client which enables publishing to the MQTT client.
private readonly IMqttClient _client = clientService.MqttClient;

private static readonly JsonSerializerOptions jsonSerializerOptions = new()
{
Converters =
{
NodaConverters.InstantConverter,
new JsonStringEnumConverter()
}
};

/// <summary>
/// Publish a command to a device.
/// </summary>
///
/// <param name="deviceId">The device to which to publish a command.</param>
/// <param name="command">The command to publish.</param>
private async void PublishCommand(Guid deviceId, Command command)
{
await _client.PublishStringAsync(ConstructTopicName(deviceId), JsonSerializer.Serialize(command, jsonSerializerOptions));
}

/// <summary>
/// Construct a topic name for a device
/// </summary>
/// <param name="deviceId">The device for which to construct a topic name.</param>
/// <returns></returns>
private static string ConstructTopicName(Guid deviceId)
{
return deviceId.ToString();
}

/// <summary>
/// Toggle Device Vibration
/// </summary>
///
/// <remarks>
/// Toggles the vibration of a device by ID.
/// </remarks>
///
/// <param name="deviceId">The device to vibrate.</param>
[HttpPost("Toggle")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult ToggleDeviceVibration(Guid deviceId)
{
PublishCommand(deviceId, new ToggleVibrationCommand()
{
DeviceId = deviceId,
});
return NoContent();
}

/// <summary>
/// Vibrate Device for Duration
/// </summary>
///
/// <remarks>
/// Vibrate a device by ID for a specified duration.
/// </remarks>
///
/// <param name="deviceId">The device to vibrate.</param>
/// <param name="seconds">The duration of the vibration.</param>
[HttpPost("VibrateForDuration")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public IActionResult VibrateDeviceForDuration(Guid deviceId, int seconds)
{
PublishCommand(deviceId, new VibrateForDurationCommand()
{
DeviceId = deviceId,
Seconds = seconds,
});
return NoContent();
}
}
7 changes: 6 additions & 1 deletion Mortein/Mqtt/Services/MqttClientService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public class MqttClientService(MqttClientOptions options, ILogger<MqttClientServ
{
private readonly IMqttClient mqttClient = new MqttFactory().CreateMqttClient();

/// <summary>
/// Underlying MQTT Client connection.
/// </summary>
public IMqttClient MqttClient => mqttClient;

private readonly MqttClientOptions options = options;

private readonly ILogger<MqttClientService> _logger = logger;
Expand Down Expand Up @@ -62,7 +67,7 @@ public async Task StopAsync(CancellationToken cancellationToken)
var disconnectOption = new MqttClientDisconnectOptions
{
Reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
ReasonString = "NormalDiconnection"
ReasonString = "NormalDisconnection"
};
await mqttClient.DisconnectAsync(disconnectOption, cancellationToken);
}
Expand Down
6 changes: 0 additions & 6 deletions api.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mortein", "Mortein\Mortein.csproj", "{D5707188-8017-4864-A816-7EC324FCF5A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mortein.IntegrationTests", "Mortein.IntegrationTests\Mortein.IntegrationTests.csproj", "{A8595760-A1F4-4AF4-B727-352717B0CAC1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mortein.Types", "Mortein.Types\Mortein.Types.csproj", "{89DAD340-71B6-482D-B937-534CAD16361B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mortein.UnitTests", "Mortein.UnitTests\Mortein.UnitTests.csproj", "{755DE551-B295-48B8-A75C-720EC60FF5F3}"
Expand All @@ -21,10 +19,6 @@ Global
{D5707188-8017-4864-A816-7EC324FCF5A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5707188-8017-4864-A816-7EC324FCF5A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5707188-8017-4864-A816-7EC324FCF5A6}.Release|Any CPU.Build.0 = Release|Any CPU
{A8595760-A1F4-4AF4-B727-352717B0CAC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8595760-A1F4-4AF4-B727-352717B0CAC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8595760-A1F4-4AF4-B727-352717B0CAC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8595760-A1F4-4AF4-B727-352717B0CAC1}.Release|Any CPU.Build.0 = Release|Any CPU
{89DAD340-71B6-482D-B937-534CAD16361B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89DAD340-71B6-482D-B937-534CAD16361B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89DAD340-71B6-482D-B937-534CAD16361B}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down

0 comments on commit b672298

Please sign in to comment.