Skip to content

Commit

Permalink
Merge pull request #36 from JMolenkamp/feat-get-specific-device-type-…
Browse files Browse the repository at this point in the history
…by-channel-name

feat: get specific device types by channel name
  • Loading branch information
perkops authored Jun 20, 2024
2 parents 3238727 + 3c02b71 commit 5828858
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
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)));

Check warning on line 584 in src/Atc.Kepware.Configuration/Services/Connectivity/KepwareConfigurationClientConnectivity.cs

View workflow job for this annotation

GitHub Actions / merge-to-stable

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)
}

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

0 comments on commit 5828858

Please sign in to comment.