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

feat: get specific device types by channel name #36

Merged
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: 3 additions & 0 deletions src/Atc.Kepware.Configuration/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
global using System.Collections.Concurrent;
global using System.ComponentModel.DataAnnotations;
global using System.Diagnostics.CodeAnalysis;
global using System.Net;
global using System.Net.Http.Headers;
global using System.Net.Mime;
global using System.Reflection;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Nodes;
global using System.Text.Json.Serialization;
global using Atc.Data.Models;
global using Atc.Helpers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Atc.Kepware.Configuration.Services;
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK")]
public sealed partial class KepwareConfigurationClient
{
private static readonly ConcurrentDictionary<Type, Type?> DeviceTypeJsonMappingTypeLookup = [];

public async Task<HttpClientRequestResult<bool>> IsChannelDefined(
string channelName,
CancellationToken cancellationToken)
Expand Down Expand Up @@ -206,6 +208,30 @@ public async Task<HttpClientRequestResult<bool>> IsTagGroupDefined(
return response.Adapt<HttpClientRequestResult<IList<DeviceBase>?>>();
}

public async Task<HttpClientRequestResult<IList<TDevice>?>> GetDevicesByChannelName<TDevice>(
string channelName,
CancellationToken cancellationToken)
where TDevice : DeviceBase
{
ArgumentNullException.ThrowIfNull(channelName);

if (!IsValidConnectivityName(
channelName,
deviceName: null,
tagGroupNameOrTagName: null,
tagGroupStructure: null,
out var errorMessage))
{
return await Task.FromResult(HttpClientRequestResultFactory<IList<TDevice>?>.CreateBadRequest(errorMessage!));
}

HttpClientRequestResult<IList<JsonObject>?> response = await Get<IList<JsonObject>>(
$"{EndpointPathTemplateConstants.Channels}/{channelName}/{EndpointPathTemplateConstants.Devices}",
cancellationToken);

return ProcessGetDevicesByChannelNameResponse<TDevice>(response);
}

public async Task<HttpClientRequestResult<TagRoot>> GetTags(
string channelName,
string deviceName,
Expand Down Expand Up @@ -472,6 +498,92 @@ public Task<HttpClientRequestResult<bool>> DeleteTagGroup(
cancellationToken);
}

private HttpClientRequestResult<IList<TDevice>?> ProcessGetDevicesByChannelNameResponse<TDevice>(HttpClientRequestResult<IList<JsonObject>?> response)
where TDevice : DeviceBase
{
// No data, return early, nothing to adapt
if (!response.HasData)
{
return new HttpClientRequestResult<IList<TDevice>?>()
{
CommunicationSucceeded = response.CommunicationSucceeded,
StatusCode = response.StatusCode,
Message = response.Message,
Exception = response.Exception,
};
}

// No actual devices, return early, nothing to adapt
if (response.Data!.Count == 0)
{
return new HttpClientRequestResult<IList<TDevice>?>([])
{
CommunicationSucceeded = response.CommunicationSucceeded,
StatusCode = response.StatusCode,
Message = response.Message,
Exception = response.Exception,
};
}

if (!TryGetDeviceTypeJsonMappingType<TDevice>(out Type? jsonMappingType))
{
return new HttpClientRequestResult<IList<TDevice>?>()
{
CommunicationSucceeded = response.CommunicationSucceeded,
StatusCode = response.StatusCode,
Message = response.Message,
Exception = new NotSupportedException($"Could not find a JSON mapping type for {typeof(TDevice).Name}"),
};
}

// Deserialize from JSON and adapt to the desired type
IList<TDevice> deviceTypes = response
.Data
.Select(x => JsonSerializer.Deserialize(x.ToString(), jsonMappingType, jsonSerializerOptions))
.Select(x => x.Adapt<TDevice>())
.ToList();

return new HttpClientRequestResult<IList<TDevice>?>(deviceTypes)
{
CommunicationSucceeded = response.CommunicationSucceeded,
StatusCode = response.StatusCode,
Message = response.Message,
};
}

private static bool TryGetDeviceTypeJsonMappingType<TDevice>([NotNullWhen(true)] out Type? jsonMappingType)
where TDevice : DeviceBase
{
jsonMappingType = DeviceTypeJsonMappingTypeLookup.GetOrAdd(typeof(TDevice), GetDeviceTypeJsonMappingType);
return jsonMappingType != null;
}

/// <summary>
/// Each driver device type has a corresponding type in this assembly for mapping from JSON. Try finding
/// that type by looking for a shared interface with the device type, that derives from IDeviceBase.
/// </summary>
/// <returns>
/// The type that maps to the device type, or null if not found.
/// </returns>
private static Type? GetDeviceTypeJsonMappingType(Type deviceType)
{
// Get the implemented interfaces that derive from IDeviceBase, but are not IDeviceBase
ISet<Type> implementedInterfaces = deviceType
.GetInterfaces()
.Where(x
=> typeof(IDeviceBase).IsAssignableFrom(x)
&& x != typeof(IDeviceBase))
.ToHashSet();

// Find the first type in this assembly that shares an interface
return typeof(KepwareConfigurationClient)
.Assembly
.GetTypes()
.FirstOrDefault(x => x
.GetInterfaces()
.Any(y => implementedInterfaces.Contains(y)));
}

private Task<HttpClientRequestResult<IList<KepwareContracts.Connectivity.Tag>?>> GetTagsResultForPathTemplate(
string pathTemplate,
CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,17 @@ Task<HttpClientRequestResult<bool>> IsTagGroupDefined(
string channelName,
CancellationToken cancellationToken);

/// <summary>
/// Returns a list of all devices under the specified channel.
/// </summary>
/// <param name="channelName">The Channel Name.</param>
/// <param name="cancellationToken">The CancellationToken.</param>
/// <typeparam name="TDevice">A driver specific <see cref="DeviceBase"/> implementation.</typeparam>
Task<HttpClientRequestResult<IList<TDevice>?>> GetDevicesByChannelName<TDevice>(
string channelName,
CancellationToken cancellationToken)
where TDevice : DeviceBase;

/// <summary>
/// Returns the properties of the specified EuroMap63 device.
/// </summary>
Expand Down