Smdn.TPSmartHomeDevices.Primitives version 1.0.0-rc1
Pre-release
Pre-release
smdn
released this
27 Apr 16:35
·
256 commits
to main
since this release
Released package
Release notes
The full release notes are available at gist.
Change log
Change log in this release:
API changes
API changes in this release:
diff --git a/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-net6.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-net6.0.apilist.cs
new file mode 100644
index 0000000..d39c2be
--- /dev/null
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-net6.0.apilist.cs
@@ -0,0 +1,105 @@
+// Smdn.TPSmartHomeDevices.Primitives.dll (Smdn.TPSmartHomeDevices.Primitives-1.0.0-rc1)
+// Name: Smdn.TPSmartHomeDevices.Primitives
+// AssemblyVersion: 1.0.0.0
+// InformationalVersion: 1.0.0-rc1+7ce7d5ecfff9471ef9ebdce241166784cce4cbbd
+// TargetFramework: .NETCoreApp,Version=v6.0
+// Configuration: Release
+// Referenced assemblies:
+// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+// System.Net.NetworkInformation, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+// System.Net.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+// System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.TPSmartHomeDevices;
+using Smdn.TPSmartHomeDevices.Json;
+
+namespace Smdn.TPSmartHomeDevices {
+ public interface IDeviceEndPoint {
+ ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default);
+ }
+
+ public interface IDeviceEndPointFactory<TAddress> where TAddress : notnull {
+ IDeviceEndPoint Create(TAddress address);
+ }
+
+ public interface IDynamicDeviceEndPoint : IDeviceEndPoint {
+ void Invalidate();
+ }
+
+ public static class DeviceEndPoint {
+ public static IDeviceEndPoint Create(EndPoint endPoint) {}
+ public static IDeviceEndPoint Create(IPAddress ipAddress) {}
+ public static IDeviceEndPoint Create(string host) {}
+ public static IDeviceEndPoint Create<TAddress>(TAddress address, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public static class DeviceEndPointFactoryServiceCollectionExtensions {
+ public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public class DeviceEndPointResolutionException : Exception {
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint) {}
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint, string message, Exception? innerException) {}
+
+ public IDeviceEndPoint DeviceEndPoint { get; }
+ }
+
+ public static class IDeviceEndPointExtensions {
+ public static ValueTask<EndPoint> ResolveOrThrowAsync(this IDeviceEndPoint deviceEndPoint, int defaultPort, CancellationToken cancellationToken = default) {}
+ }
+
+ public sealed class StaticDeviceEndPoint : IDeviceEndPoint {
+ public StaticDeviceEndPoint(EndPoint endPoint) {}
+
+ public ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default) {}
+ public override string? ToString() {}
+ }
+}
+
+namespace Smdn.TPSmartHomeDevices.Json {
+ public sealed class GeolocationInDecimalDegreesJsonConverter : JsonConverter<decimal?> {
+ public GeolocationInDecimalDegreesJsonConverter() {}
+
+ public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ public override void Write(Utf8JsonWriter writer, decimal? @value, JsonSerializerOptions options) {}
+ }
+
+ public sealed class MacAddressJsonConverter : JsonConverter<PhysicalAddress> {
+ public MacAddressJsonConverter() {}
+
+ public override PhysicalAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ public override void Write(Utf8JsonWriter writer, PhysicalAddress @value, JsonSerializerOptions options) {}
+ }
+
+ public sealed class TimeSpanInMinutesJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInMinutesJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public sealed class TimeSpanInSecondsJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInSecondsJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public abstract class TimeSpanJsonConverter : JsonConverter<TimeSpan?> {
+ protected TimeSpanJsonConverter() {}
+
+ public override TimeSpan? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ protected abstract TimeSpan ToTimeSpan(int @value);
+ public override void Write(Utf8JsonWriter writer, TimeSpan? @value, JsonSerializerOptions options) {}
+ }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.0.apilist.cs
new file mode 100644
index 0000000..2086cd7
--- /dev/null
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.0.apilist.cs
@@ -0,0 +1,104 @@
+// Smdn.TPSmartHomeDevices.Primitives.dll (Smdn.TPSmartHomeDevices.Primitives-1.0.0-rc1)
+// Name: Smdn.TPSmartHomeDevices.Primitives
+// AssemblyVersion: 1.0.0.0
+// InformationalVersion: 1.0.0-rc1+7ce7d5ecfff9471ef9ebdce241166784cce4cbbd
+// TargetFramework: .NETStandard,Version=v2.0
+// Configuration: Release
+// Referenced assemblies:
+// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+// System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+// netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.TPSmartHomeDevices;
+using Smdn.TPSmartHomeDevices.Json;
+
+namespace Smdn.TPSmartHomeDevices {
+ public interface IDeviceEndPoint {
+ ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default);
+ }
+
+ public interface IDeviceEndPointFactory<TAddress> where TAddress : notnull {
+ IDeviceEndPoint Create(TAddress address);
+ }
+
+ public interface IDynamicDeviceEndPoint : IDeviceEndPoint {
+ void Invalidate();
+ }
+
+ public static class DeviceEndPoint {
+ public static IDeviceEndPoint Create(EndPoint endPoint) {}
+ public static IDeviceEndPoint Create(IPAddress ipAddress) {}
+ public static IDeviceEndPoint Create(string host) {}
+ public static IDeviceEndPoint Create<TAddress>(TAddress address, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public static class DeviceEndPointFactoryServiceCollectionExtensions {
+ public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public class DeviceEndPointResolutionException : Exception {
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint) {}
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint, string message, Exception? innerException) {}
+
+ public IDeviceEndPoint DeviceEndPoint { get; }
+ }
+
+ public static class IDeviceEndPointExtensions {
+ public static ValueTask<EndPoint> ResolveOrThrowAsync(this IDeviceEndPoint deviceEndPoint, int defaultPort, CancellationToken cancellationToken = default) {}
+ }
+
+ public sealed class StaticDeviceEndPoint : IDeviceEndPoint {
+ public StaticDeviceEndPoint(EndPoint endPoint) {}
+
+ public ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default) {}
+ public override string? ToString() {}
+ }
+}
+
+namespace Smdn.TPSmartHomeDevices.Json {
+ public sealed class GeolocationInDecimalDegreesJsonConverter : JsonConverter<decimal?> {
+ public GeolocationInDecimalDegreesJsonConverter() {}
+
+ public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ public override void Write(Utf8JsonWriter writer, decimal? @value, JsonSerializerOptions options) {}
+ }
+
+ public sealed class MacAddressJsonConverter : JsonConverter<PhysicalAddress> {
+ public MacAddressJsonConverter() {}
+
+ public override PhysicalAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ public override void Write(Utf8JsonWriter writer, PhysicalAddress @value, JsonSerializerOptions options) {}
+ }
+
+ public sealed class TimeSpanInMinutesJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInMinutesJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public sealed class TimeSpanInSecondsJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInSecondsJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public abstract class TimeSpanJsonConverter : JsonConverter<TimeSpan?> {
+ protected TimeSpanJsonConverter() {}
+
+ public override TimeSpan? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ protected abstract TimeSpan ToTimeSpan(int @value);
+ public override void Write(Utf8JsonWriter writer, TimeSpan? @value, JsonSerializerOptions options) {}
+ }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
diff --git a/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.1.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.1.apilist.cs
new file mode 100644
index 0000000..300a348
--- /dev/null
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives-netstandard2.1.apilist.cs
@@ -0,0 +1,103 @@
+// Smdn.TPSmartHomeDevices.Primitives.dll (Smdn.TPSmartHomeDevices.Primitives-1.0.0-rc1)
+// Name: Smdn.TPSmartHomeDevices.Primitives
+// AssemblyVersion: 1.0.0.0
+// InformationalVersion: 1.0.0-rc1+7ce7d5ecfff9471ef9ebdce241166784cce4cbbd
+// TargetFramework: .NETStandard,Version=v2.1
+// Configuration: Release
+// Referenced assemblies:
+// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
+// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+// netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
+#nullable enable annotations
+
+using System;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Smdn.TPSmartHomeDevices;
+using Smdn.TPSmartHomeDevices.Json;
+
+namespace Smdn.TPSmartHomeDevices {
+ public interface IDeviceEndPoint {
+ ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default);
+ }
+
+ public interface IDeviceEndPointFactory<TAddress> where TAddress : notnull {
+ IDeviceEndPoint Create(TAddress address);
+ }
+
+ public interface IDynamicDeviceEndPoint : IDeviceEndPoint {
+ void Invalidate();
+ }
+
+ public static class DeviceEndPoint {
+ public static IDeviceEndPoint Create(EndPoint endPoint) {}
+ public static IDeviceEndPoint Create(IPAddress ipAddress) {}
+ public static IDeviceEndPoint Create(string host) {}
+ public static IDeviceEndPoint Create<TAddress>(TAddress address, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public static class DeviceEndPointFactoryServiceCollectionExtensions {
+ public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) where TAddress : notnull {}
+ }
+
+ public class DeviceEndPointResolutionException : Exception {
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint) {}
+ public DeviceEndPointResolutionException(IDeviceEndPoint deviceEndPoint, string message, Exception? innerException) {}
+
+ public IDeviceEndPoint DeviceEndPoint { get; }
+ }
+
+ public static class IDeviceEndPointExtensions {
+ public static ValueTask<EndPoint> ResolveOrThrowAsync(this IDeviceEndPoint deviceEndPoint, int defaultPort, CancellationToken cancellationToken = default) {}
+ }
+
+ public sealed class StaticDeviceEndPoint : IDeviceEndPoint {
+ public StaticDeviceEndPoint(EndPoint endPoint) {}
+
+ public ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default) {}
+ public override string? ToString() {}
+ }
+}
+
+namespace Smdn.TPSmartHomeDevices.Json {
+ public sealed class GeolocationInDecimalDegreesJsonConverter : JsonConverter<decimal?> {
+ public GeolocationInDecimalDegreesJsonConverter() {}
+
+ public override decimal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ [...] public override <unknown> Write(...) {}
+ }
+
+ public sealed class MacAddressJsonConverter : JsonConverter<PhysicalAddress> {
+ public MacAddressJsonConverter() {}
+
+ public override PhysicalAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ [...] public override <unknown> Write(...) {}
+ }
+
+ public sealed class TimeSpanInMinutesJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInMinutesJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public sealed class TimeSpanInSecondsJsonConverter : TimeSpanJsonConverter {
+ public TimeSpanInSecondsJsonConverter() {}
+
+ protected override TimeSpan ToTimeSpan(int @value) {}
+ }
+
+ public abstract class TimeSpanJsonConverter : JsonConverter<TimeSpan?> {
+ protected TimeSpanJsonConverter() {}
+
+ public override TimeSpan? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {}
+ protected abstract TimeSpan ToTimeSpan(int @value);
+ [...] public override <unknown> Write(...) {}
+ }
+}
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0.
+// Smdn.Reflection.ReverseGenerating.ListApi.Core v1.2.0.0 (https://github.com/smdn/Smdn.Reflection.ReverseGenerating)
Full changes
Full changes in this release:
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs
new file mode 100644
index 0000000..3197ab6
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.TPSmartHomeDevices.Json;
+
+public sealed class GeolocationInDecimalDegreesJsonConverter : JsonConverter<decimal?> {
+ private const decimal Scaler = 10000m;
+
+ public override decimal? Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ => reader.TokenType == JsonTokenType.Number && reader.TryGetDecimal(out var scaledDecimalDegrees)
+ ? scaledDecimalDegrees / Scaler
+ : null;
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ decimal? value,
+ JsonSerializerOptions options
+ )
+ => throw new NotImplementedException();
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs
new file mode 100644
index 0000000..d02af38
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Net.NetworkInformation;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.TPSmartHomeDevices.Json;
+
+public sealed class MacAddressJsonConverter : JsonConverter<PhysicalAddress> {
+ public override PhysicalAddress? Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ {
+ var str = reader.TokenType switch {
+ JsonTokenType.Null => null,
+ JsonTokenType.String => reader.GetString(),
+ _ => null,
+ };
+
+ if (str is null)
+ return null;
+
+#if SYSTEM_NET_NETWORKINFORMATION_PHYSICALADDRESS_TRYPARSE
+ return PhysicalAddress.TryParse(str, out var ret)
+ ? ret
+ : null;
+#else
+ try {
+ return PhysicalAddress.Parse(str);
+ }
+ catch (FormatException) {
+ return null;
+ }
+#endif
+ }
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ PhysicalAddress value,
+ JsonSerializerOptions options
+ )
+ => throw new NotImplementedException();
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs
new file mode 100644
index 0000000..c8748c0
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.TPSmartHomeDevices.Json;
+
+public sealed class TimeSpanInMinutesJsonConverter : TimeSpanJsonConverter {
+ protected override TimeSpan ToTimeSpan(int value) => TimeSpan.FromMinutes(value);
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs
new file mode 100644
index 0000000..f159f0b
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.TPSmartHomeDevices.Json;
+
+public sealed class TimeSpanInSecondsJsonConverter : TimeSpanJsonConverter {
+ protected override TimeSpan ToTimeSpan(int value) => TimeSpan.FromSeconds(value);
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanJsonConverter.cs
new file mode 100644
index 0000000..2282a3d
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Json/TimeSpanJsonConverter.cs
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Smdn.TPSmartHomeDevices.Json;
+
+public abstract class TimeSpanJsonConverter : JsonConverter<TimeSpan?> {
+ protected abstract TimeSpan ToTimeSpan(int value);
+
+ public override TimeSpan? Read(
+ ref Utf8JsonReader reader,
+ Type typeToConvert,
+ JsonSerializerOptions options
+ )
+ => reader.TokenType == JsonTokenType.Number && reader.TryGetInt32(out var value)
+ ? ToTimeSpan(value)
+ : null;
+
+ public override void Write(
+ Utf8JsonWriter writer,
+ TimeSpan? value,
+ JsonSerializerOptions options
+ )
+ => throw new NotImplementedException();
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives.csproj b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives.csproj
new file mode 100644
index 0000000..7b2451f
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices.Primitives.csproj
@@ -0,0 +1,50 @@
+<!--
+SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+SPDX-License-Identifier: MIT
+-->
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks>netstandard2.0;netstandard2.1;net6.0</TargetFrameworks>
+ <VersionPrefix>1.0.0</VersionPrefix>
+ <VersionSuffix>rc1</VersionSuffix>
+ <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> -->
+ <Nullable>enable</Nullable>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <EnableTrimAnalyzer>false</EnableTrimAnalyzer>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <NoWarn>CS1591;$(NoWarn)</NoWarn> <!-- CS1591: Missing XML comment for publicly visible type or member 'Type_or_Member' -->
+ </PropertyGroup>
+
+ <PropertyGroup Label="assembly attributes">
+ <Description>
+<![CDATA[Provides common types for Smdn.TPSmartHomeDevices.Kasa and Smdn.TPSmartHomeDevices.Tapo, including abstraction interfaces, extension methods and custom JsonConverter's.
+This library does not provide any specific implementations to operate Kasa and Tapo devices.
+]]>
+ </Description>
+ <CopyrightYear>2023</CopyrightYear>
+ </PropertyGroup>
+
+ <PropertyGroup Label="package properties">
+ <PackageTags>tplink-kasa,kasa,tplink-tapo,tapo,common,$(PackageCommonTags)</PackageTags>
+ </PropertyGroup>
+
+ <PropertyGroup Label="StyleCop code analysis">
+ <StyleCopAnalyzersConfigurationFile>..\stylecop.json</StyleCopAnalyzersConfigurationFile>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <GlobalAnalyzerConfigFiles Include="..\CodeAnalysis.globalconfig" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="System.Text.Json" Version="6.0.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Include="..\Common\System.Threading.Tasks\ValueTaskShim.cs" />
+ </ItemGroup>
+
+</Project>
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPoint.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPoint.cs
new file mode 100644
index 0000000..f538395
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPoint.cs
@@ -0,0 +1,36 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Net;
+
+namespace Smdn.TPSmartHomeDevices;
+
+public static class DeviceEndPoint {
+ public static IDeviceEndPoint Create(string host)
+ => new StaticDeviceEndPoint(
+ new DnsEndPoint(
+ host: host ?? throw new ArgumentNullException(nameof(host)),
+ port: 0
+ )
+ );
+
+ public static IDeviceEndPoint Create(IPAddress ipAddress)
+ => new StaticDeviceEndPoint(
+ new IPEndPoint(
+ address: ipAddress ?? throw new ArgumentNullException(nameof(ipAddress)),
+ port: 0
+ )
+ );
+
+ public static IDeviceEndPoint Create(EndPoint endPoint)
+ => new StaticDeviceEndPoint(
+ endPoint ?? throw new ArgumentNullException(nameof(endPoint))
+ );
+
+ public static IDeviceEndPoint Create<TAddress>(
+ TAddress address,
+ IDeviceEndPointFactory<TAddress> endPointFactory
+ ) where TAddress : notnull
+ => (endPointFactory ?? throw new ArgumentNullException(nameof(endPointFactory)))
+ .Create(address: address ?? throw new ArgumentNullException(nameof(address)));
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs
new file mode 100644
index 0000000..aa67a3e
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Smdn.TPSmartHomeDevices;
+
+public static class DeviceEndPointFactoryServiceCollectionExtensions {
+ /// <summary>
+ /// Adds <see cref="IDeviceEndPointFactory{TAddress}"/> to create an <see cref="IDeviceEndPoint"/>
+ /// that uses <typeparamref name="TAddress"/> as the address type to represent the device endpoint.
+ /// </summary>
+ /// <typeparam name="TAddress">The type that represents an address of device endpoint.</typeparam>
+ /// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param>
+ /// <param name="endPointFactory">The <see cref="IDeviceEndPointFactory{TAddress}"/> that is added to services.</param>
+ public static IServiceCollection AddDeviceEndPointFactory<TAddress>(
+ this IServiceCollection services,
+ IDeviceEndPointFactory<TAddress> endPointFactory
+ ) where TAddress : notnull
+ {
+ if (services is null)
+ throw new ArgumentNullException(nameof(services));
+ if (endPointFactory is null)
+ throw new ArgumentNullException(nameof(endPointFactory));
+
+ services.TryAdd(ServiceDescriptor.Singleton(typeof(IDeviceEndPointFactory<TAddress>), endPointFactory));
+
+ return services;
+ }
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs
new file mode 100644
index 0000000..865fd56
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+
+namespace Smdn.TPSmartHomeDevices;
+
+public class DeviceEndPointResolutionException : Exception {
+ public IDeviceEndPoint DeviceEndPoint { get; }
+
+ public DeviceEndPointResolutionException(
+ IDeviceEndPoint deviceEndPoint
+ )
+ : this(
+ deviceEndPoint: deviceEndPoint,
+ message: "Could not get or resolve the device endpoint.",
+ innerException: null
+ )
+ {
+ }
+
+ public DeviceEndPointResolutionException(
+ IDeviceEndPoint deviceEndPoint,
+ string message,
+ Exception? innerException
+ )
+ : base(
+ message: message,
+ innerException: innerException
+ )
+ {
+ DeviceEndPoint = deviceEndPoint;
+ }
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPoint.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPoint.cs
new file mode 100644
index 0000000..aed078f
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPoint.cs
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Smdn.TPSmartHomeDevices;
+
+/// <summary>
+/// Provides a mechanism for abstracting device endpoints and resolving to specific <seealso cref="EndPoint" />.
+/// </summary>
+/// <seealso cref="DeviceEndPoint" />
+/// <seealso cref="StaticDeviceEndPoint" />
+/// <seealso cref="IDynamicDeviceEndPoint" />
+public interface IDeviceEndPoint {
+ /// <summary>
+ /// Resolves this endpoint to its corresponding <see cref="EndPoint"/>.
+ /// </summary>
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param>
+ /// <returns>
+ /// A <see cref="ValueTask{EndPoint}"/> representing the result of endpoint resolution.
+ /// If the endpoint is successfully resolved, <see cref="EndPoint"/> representing the resolved endpoint is set. If not, <see langword="null" /> is set.
+ /// </returns>
+ /// <seealso cref="IDynamicDeviceEndPoint.Invalidate" />
+ ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default);
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointExtensions.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointExtensions.cs
new file mode 100644
index 0000000..26c07a3
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointExtensions.cs
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Smdn.TPSmartHomeDevices;
+
+/// <summary>
+/// Provides extension methods for <see cref="IDeviceEndPoint"/>.
+/// </summary>
+/// <seealso cref="IDeviceEndPoint" />
+public static class IDeviceEndPointExtensions {
+ /// <summary>
+ /// Resolves this endpoint to its corresponding <see cref="EndPoint"/>.
+ /// If the endpoint could not resolve to the specific <see cref="EndPoint"/>, throw <see cref="DeviceEndPointResolutionException"/>.
+ /// </summary>
+ /// <remarks>
+ /// This method calls the <see cref="IDynamicDeviceEndPoint.Invalidate" /> if the <paramref name="deviceEndPoint"/> is an <see cref="IDynamicDeviceEndPoint"/> and the endpoint cannot be resolved.
+ /// </remarks>
+ /// <param name="deviceEndPoint">The <see cref="IDeviceEndPoint" />.</param>
+ /// <param name="defaultPort">The default port number. If the resolved <see cref="EndPoint"/> does not specify a specific port, set this port number to the resolved <see cref="EndPoint"/>.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken" /> to monitor for cancellation requests.</param>
+ /// <returns>
+ /// A <see cref="ValueTask{EndPoint}"/> representing the result of endpoint resolution.
+ /// </returns>
+ /// <exception cref="DeviceEndPointResolutionException">The endpoint could not resolve to the specific <see cref="EndPoint"/>.</exception>
+ /// <seealso cref="IDeviceEndPoint.ResolveAsync(CancellationToken)" />
+ /// <seealso cref="IDynamicDeviceEndPoint.Invalidate" />
+ public static ValueTask<EndPoint> ResolveOrThrowAsync(
+ this IDeviceEndPoint deviceEndPoint,
+ int defaultPort,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (deviceEndPoint is null)
+ throw new ArgumentNullException(nameof(deviceEndPoint));
+ if (defaultPort < 0)
+ throw new ArgumentOutOfRangeException(message: "must be positive number", paramName: nameof(defaultPort));
+
+ if (cancellationToken.IsCancellationRequested)
+#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED
+ return ValueTask.FromCanceled<EndPoint>(cancellationToken);
+#else
+ return ValueTaskShim.FromCanceled<EndPoint>(cancellationToken);
+#endif
+
+ return ResolveOrThrowAsyncCore();
+
+ async ValueTask<EndPoint> ResolveOrThrowAsyncCore()
+ {
+ var endPoint = await deviceEndPoint.ResolveAsync(cancellationToken).ConfigureAwait(false);
+
+ if (endPoint is null) {
+ if (deviceEndPoint is IDynamicDeviceEndPoint dynamicDeviceEndPoint)
+ dynamicDeviceEndPoint.Invalidate();
+
+ throw new DeviceEndPointResolutionException(deviceEndPoint);
+ }
+
+ return endPoint switch {
+ IPEndPoint ipEndPoint => ipEndPoint.Port == 0
+ ? new IPEndPoint(ipEndPoint.Address, defaultPort)
+ : ipEndPoint,
+
+ DnsEndPoint dnsEndPoint => dnsEndPoint.Port == 0
+ ? new DnsEndPoint(dnsEndPoint.Host, defaultPort)
+ : dnsEndPoint,
+
+ /* EndPoint */ _ => endPoint,
+ };
+ }
+ }
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs
new file mode 100644
index 0000000..afad3cc
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.TPSmartHomeDevices;
+
+/// <summary>
+/// Provides a mechanism for creating <see cref="IDeviceEndPoint"/> from the address respresented by <typeparamref name="TAddress"/>.
+/// </summary>
+/// <remarks>
+/// <c>MacAddressDeviceEndPointFactory</c> class from the package <c>Smdn.TPSmartHomeDevices.MacAddressEndPoint</c> is one concrete implementation of this interface.
+/// </remarks>
+/// <typeparam name="TAddress">The type that represents an address of device endpoint.</typeparam>
+/// <seealso cref="IDeviceEndPoint" />
+public interface IDeviceEndPointFactory<TAddress> where TAddress : notnull {
+ /// <summary>
+ /// Creates an <see cref="IDeviceEndPoint"/> that is resolved by the address represented by type <typeparamref name="TAddress"/>.
+ /// </summary>
+ /// <param name="address">The address to identify the device endpoint.</param>
+ IDeviceEndPoint Create(TAddress address);
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPoint.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPoint.cs
new file mode 100644
index 0000000..c97256b
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPoint.cs
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+namespace Smdn.TPSmartHomeDevices;
+
+/// <summary>
+/// Provides a mechanism for abstracting device endpoints and resolving to specific <seealso cref="System.Net.EndPoint" />.
+/// This interface represents a 'dynamic' endpoint, such as when the actual IP address is assigned by DHCP.
+/// </summary>
+/// <remarks>
+/// <c>MacAddressDeviceEndPointFactory+MacAddressDeviceEndPoint</c> class from the package <c>Smdn.TPSmartHomeDevices.MacAddressEndPoint</c> is one concrete implementation of this interface.
+/// </remarks>
+public interface IDynamicDeviceEndPoint : IDeviceEndPoint {
+ /// <summary>
+ /// Marks the device endpoint as 'invalidated', for example,
+ /// if the resolved <seealso cref="System.Net.EndPoint" /> is unreachable or expired.
+ /// </summary>
+ void Invalidate();
+}
diff --git a/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/StaticDeviceEndPoint.cs b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/StaticDeviceEndPoint.cs
new file mode 100644
index 0000000..5868549
--- /dev/null
+++ b/src/Smdn.TPSmartHomeDevices.Primitives/Smdn.TPSmartHomeDevices/StaticDeviceEndPoint.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp>
+// SPDX-License-Identifier: MIT
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Smdn.TPSmartHomeDevices;
+
+/// <summary>
+/// A concrete implementation of <see cref="IDeviceEndPoint"/>, which represents a device endpoint by <see cref="EndPoint"/>.
+/// </summary>
+public sealed class StaticDeviceEndPoint : IDeviceEndPoint {
+ private readonly EndPoint endPoint;
+
+ public StaticDeviceEndPoint(EndPoint endPoint)
+ {
+ this.endPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
+ }
+
+ public ValueTask<EndPoint?> ResolveAsync(CancellationToken cancellationToken = default)
+ => cancellationToken.IsCancellationRequested
+ ?
+#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED
+ ValueTask.FromCanceled<EndPoint?>(cancellationToken)
+#else
+ ValueTaskShim.FromCanceled<EndPoint?>(cancellationToken)
+#endif
+ : new(endPoint);
+
+ public override string? ToString()
+ => endPoint.ToString();
+}
Notes
Full Changelog: releases/Smdn.TPSmartHomeDevices-1.0.0-preview3...releases/Smdn.TPSmartHomeDevices.Primitives-1.0.0-rc1