Skip to content

Commit

Permalink
✨ Add the sensor service.
Browse files Browse the repository at this point in the history
Only state persistance is implemented for now. (Which is missing in e.g. the lighting service)
  • Loading branch information
hexawyz committed Apr 10, 2024
1 parent 77e80ac commit 1ab3e33
Show file tree
Hide file tree
Showing 9 changed files with 449 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Exo.Core/Configuration/IConfigurationContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public interface IConfigurationContainer : IConfigurationNode
ValueTask<ConfigurationResult<TValue>> ReadValueAsync<TValue>(CancellationToken cancellationToken);
ValueTask WriteValueAsync<TValue>(TValue value, CancellationToken cancellationToken) where TValue : notnull;
ValueTask DeleteValueAsync<TValue>();
ValueTask DeleteAllValuesAsync();
}

public interface IConfigurationContainer<TKey>
Expand All @@ -23,4 +24,5 @@ public interface IConfigurationContainer<TKey>
/// <param name="key"></param>
/// <returns></returns>
IConfigurationContainer GetContainer(TKey key);
ValueTask DeleteAllContainersAsync();
}
13 changes: 13 additions & 0 deletions Exo.Core/Configuration/IConfigurationNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,24 @@ public interface IConfigurationNode
/// <returns></returns>
IConfigurationContainer GetContainer(string containerName);

/// <summary>Tries to get a pre-existing configuration container to store values and child containers.</summary>
/// <param name="containerName"></param>
/// <returns></returns>
IConfigurationContainer? TryGetContainer(string containerName);

/// <summary>Gets a configuration container used to store keyed configuration.</summary>
/// <remarks>If needed, specific keys can be opened as their own containers and define custom child containers.</remarks>
/// <typeparam name="TKey">The type of configuration key.</typeparam>
/// <param name="containerName"></param>
/// <param name="nameSerializer"></param>
/// <returns></returns>
IConfigurationContainer<TKey> GetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer);

/// <summary>Tries to get a configuration container used to store keyed configuration.</summary>
/// <remarks>If needed, specific keys can be opened as their own containers and define custom child containers.</remarks>
/// <typeparam name="TKey">The type of configuration key.</typeparam>
/// <param name="containerName"></param>
/// <param name="nameSerializer"></param>
/// <returns></returns>
IConfigurationContainer<TKey>? TryGetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer);
}
79 changes: 72 additions & 7 deletions Exo.Service.Core/ConfigurationService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using Exo.Configuration;

namespace Exo.Service;
Expand Down Expand Up @@ -60,12 +62,24 @@ public ValueTask WriteValueAsync<TValue>(TValue value, CancellationToken cancell
public ValueTask DeleteValueAsync<TValue>()
=> _configurationService.DeleteValueAsync<TValue>(_directory);

public ValueTask DeleteAllValuesAsync()
=> _configurationService.DeleteValuesAsync(_directory);

public IConfigurationContainer GetContainer(string containerName)
=> new ConfigurationContainer(_configurationService, PrepareChildDirectory(_directory, containerName, false));
=> new ConfigurationContainer(_configurationService, PrepareChildDirectory(_directory, containerName, false, true)!);

public IConfigurationContainer<TKey> GetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer)
=> new ConfigurationContainer<TKey>(_configurationService, PrepareChildDirectory(_directory, containerName, false), nameSerializer);
=> new ConfigurationContainer<TKey>(_configurationService, PrepareChildDirectory(_directory, containerName, false, true)!, nameSerializer);

public IConfigurationContainer? TryGetContainer(string containerName)
=> PrepareChildDirectory(_directory, containerName, false, false) is { } childDirectory ?
new ConfigurationContainer(_configurationService, childDirectory) :
null;

public IConfigurationContainer<TKey>? TryGetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer)
=> PrepareChildDirectory(_directory, containerName, false, false) is { } childDirectory ?
new ConfigurationContainer<TKey>(_configurationService, childDirectory, nameSerializer) :
null;
}

private sealed class ConfigurationContainer<TKey> : IConfigurationContainer<TKey>
Expand Down Expand Up @@ -124,7 +138,9 @@ public ValueTask DeleteValuesAsync(TKey key)
=> _configurationService.DeleteValuesAsync(_directory, _nameSerializer.ToString(key));

public IConfigurationContainer GetContainer(TKey key)
=> new ConfigurationContainer(_configurationService, PrepareChildDirectory(_directory, _nameSerializer.ToString(key), true));
=> new ConfigurationContainer(_configurationService, PrepareChildDirectory(_directory, _nameSerializer.ToString(key), true, true)!);

public ValueTask DeleteAllContainersAsync() => _configurationService.DeleteContainersAsync(_directory, _nameSerializer);
}

private readonly AsyncLock _lock;
Expand All @@ -138,7 +154,7 @@ public ConfigurationService(string directory)
_directory = directory;
}

private static string PrepareChildDirectory(string baseDirectory, string directoryName, bool allowGuid)
private static string? PrepareChildDirectory(string baseDirectory, string directoryName, bool allowGuid, bool createIfNotExists)
{
ArgumentNullException.ThrowIfNull(directoryName);
// This first check is a relatively quick way to validate that the directory doesn't contain any directory separators.
Expand All @@ -149,18 +165,35 @@ private static string PrepareChildDirectory(string baseDirectory, string directo
string childDirectory = Path.GetFullPath(Path.Combine(baseDirectory, directoryName));
if (childDirectory.Length > baseDirectory.Length && childDirectory.StartsWith(baseDirectory))
{
Directory.CreateDirectory(childDirectory);
if (createIfNotExists)
{
Directory.CreateDirectory(childDirectory);
}
else if (!Directory.Exists(childDirectory))
{
return null;
}
return childDirectory;
}
}
throw new InvalidOperationException($"Invalid directory name: {directoryName}.");
}

public IConfigurationContainer GetContainer(string containerName)
=> new ConfigurationContainer(this, PrepareChildDirectory(_directory, containerName, true));
=> new ConfigurationContainer(this, PrepareChildDirectory(_directory, containerName, true, true)!);

public IConfigurationContainer<TKey> GetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer)
=> new ConfigurationContainer<TKey>(this, PrepareChildDirectory(_directory, containerName, true), nameSerializer);
=> new ConfigurationContainer<TKey>(this, PrepareChildDirectory(_directory, containerName, true, true)!, nameSerializer);

public IConfigurationContainer? TryGetContainer(string containerName)
=> PrepareChildDirectory(_directory, containerName, true, false) is { } childDirectory ?
new ConfigurationContainer(this, childDirectory) :
null;

public IConfigurationContainer<TKey>? TryGetContainer<TKey>(string containerName, INameSerializer<TKey> nameSerializer)
=> PrepareChildDirectory(_directory, containerName, true, false) is { } childDirectory ?
new ConfigurationContainer<TKey>(this, childDirectory, nameSerializer) :
null;

private async ValueTask<ConfigurationResult<T>> ReadValueAsync<T>(string directory, string? key, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -243,6 +276,38 @@ private async ValueTask DeleteValueAsync<T>(string directory)
}
}

private async ValueTask DeleteValuesAsync(string directory)
{
using (await _lock.WaitAsync(default).ConfigureAwait(false))
{
string[] fileNames = Directory.GetFiles(directory, "????????-????-????-????-????????????.json", SearchOption.TopDirectoryOnly);

foreach (string fileName in fileNames)
{
if (GuidNameSerializer.Instance.TryParse(Path.GetFileNameWithoutExtension(fileName), out _))
{
File.Delete(fileName);
}
}
}
}

private async ValueTask DeleteContainersAsync<TKey>(string directory, INameSerializer<TKey> nameSerializer)
{
using (await _lock.WaitAsync(default).ConfigureAwait(false))
{
string[] directoryNames = Directory.GetDirectories(directory, nameSerializer.FileNamePattern, SearchOption.TopDirectoryOnly);

foreach (string directoryName in directoryNames)
{
if (GuidNameSerializer.Instance.TryParse(Path.GetFileName(directoryName), out _))
{
Directory.Delete(directoryName, true);
}
}
}
}

public async ValueTask<string[]> GetDirectoryNamesAsync(string directory, string pattern, CancellationToken cancellationToken)
{
using (await _lock.WaitAsync(cancellationToken).ConfigureAwait(false))
Expand Down
18 changes: 18 additions & 0 deletions Exo.Service.Core/SensorDataType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Exo.Service;

public enum SensorDataType : byte
{
UInt8,
UInt16,
UInt32,
UInt64,
UInt128,
SInt8,
SInt16,
SInt32,
SInt64,
SInt128,
Float16,
Float32,
Float64,
}
22 changes: 22 additions & 0 deletions Exo.Service.Core/SensorDeviceInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Immutable;

namespace Exo.Service;

public readonly struct SensorDeviceInformation : IEquatable<SensorDeviceInformation>
{
public SensorDeviceInformation(Guid deviceId, ImmutableArray<SensorInformation> sensors)
{
DeviceId = deviceId;
Sensors = sensors;
}

public Guid DeviceId { get; }
public ImmutableArray<SensorInformation> Sensors { get; }

public override bool Equals(object? obj) => obj is SensorDeviceInformation information && Equals(information);
public bool Equals(SensorDeviceInformation other) => DeviceId.Equals(other.DeviceId) && Sensors.SequenceEqual(other.Sensors);
public override int GetHashCode() => HashCode.Combine(DeviceId, Sensors.Length);

public static bool operator ==(SensorDeviceInformation left, SensorDeviceInformation right) => left.Equals(right);
public static bool operator !=(SensorDeviceInformation left, SensorDeviceInformation right) => !(left == right);
}
15 changes: 15 additions & 0 deletions Exo.Service.Core/SensorInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Exo.Service;

public record struct SensorInformation
{
public SensorInformation(Guid sensorId, SensorDataType dataType, bool isPolled)
{
SensorId = sensorId;
DataType = dataType;
IsPolled = isPolled;
}

public Guid SensorId { get; }
public SensorDataType DataType { get; }
public bool IsPolled { get; }
}
Loading

0 comments on commit 1ab3e33

Please sign in to comment.