main/Smdn.TPSmartHomeDevices-1.0.0-rc1
Created
April 29, 2023 01:49
-
-
Save smdn/1e06022628513a0345cb26a9a5527567 to your computer and use it in GitHub Desktop.
Smdn.TPSmartHomeDevices 1.0.0-rc1 Release Notes
- 2023-04-29 update package version
- 2023-04-29 improve metapackage configurations
- 2023-04-28 split assembly Smdn.TPSmartHomeDevices into the assemblies -.Primitives, -.Tapo, -.Kasa, -.MacAddressEndPoint
- 2023-04-27 rename to (Tapo|Kasa)DeviceExceptionHandler and move them to their root namespace
- 2023-04-27 add TimeSpanJsonConverter abstract base class
- 2023-04-27 add 'notnull' constraints to the type parameter that defines type of pass through request
- 2023-04-27 fix link in credit
- 2023-04-27 add SPDX license headers
- 2023-04-27 add XML comment docs
- 2023-04-26 move declaration to appropriate position
- 2023-04-26 parse ID-like properties as byte[] instead of string
- 2023-04-26 fix type parameter name
- 2023-04-25 merge TapoCredentialUtils and TapoCredentialProviderFactory into TapoCredentials
- 2023-04-25 add credit and third party notices
- 2023-04-25 add TapoDevice.Create<TAddress>()
- 2023-04-25 add KasaDevice.Create<TAddress>()
- 2023-04-24 throw ArgumentNullException to keep parameter name
- 2023-04-24 expose DeviceEndPoint and merge the implementation of KasaDeviceEndPoint and TapoDeviceEndPoint
- 2023-04-24 lose the semantics of port numbers from IDeviceEndPoint
- 2023-04-24 expose IDeviceEndPointExtensions.ResolveOrThrowAsync
- 2023-04-24 expose StaticDeviceEndPoint
- 2023-04-24 change endpoint info to output as logger scope instead of category name
- 2023-04-24 reduce overloads of DeviceEndPoint.Create
- 2023-04-24 add Create<TAddress> to support creating devices from any address type
- 2023-04-24 add notnull constraints
- 2023-04-22 enable and fix CA2007
- 2023-04-22 clean up NoWarn values
- 2023-04-22 fix warning CS1573
- 2023-04-22 disable warning CA1848
- 2023-04-22 improve comment
- 2023-04-22 fix warning CA2254
- 2023-04-22 seal the externally invisible types
- 2023-04-22 disable unnecessary using directives
- 2023-04-22 fix nullability warnings
- 2023-04-22 throw InvalidOperationException if the pass through response was decrypted as 'null' JSON object
- 2023-04-22 throw TapoProtocolException if the HTTP response was parsed as 'null' JSON object
- 2023-04-22 expose TapoClient.DefaultHttpClientFactory
- 2023-04-22 propagate CancellationToken
- 2023-04-22 bump Smdn.MSBuild.DefineConstants.NETSdkApi up to 1.3.12
- 2023-04-22 suppress warning SA1114 for switching use of utf-8 string literals
- 2023-04-22 delete unnecessary codes
- 2023-04-22 propagate CancellationToken
- 2023-04-22 improved annotations of fields that may represent disposed states
- 2023-04-22 suppress warning SA1313 and CA1822 for private type
- 2023-04-22 suppress warning CA5350
- 2023-04-22 suppress warning SA1305
- 2023-04-22 bump Smdn.MSBuild.ProjectAssets.Library up to 1.5.0
- 2023-04-22 improve handling of error code -1501
- 2023-04-22 add overload of TapoDevice.GetDeviceInfoAsync() which returns TapoDeviceInfo
- 2023-04-22 expose JsonConverter types
- 2023-04-22 validate token type before getting value
- 2023-04-21 bump Smdn.Net.AddressResolution up to 1.0.0
- 2023-04-21 add TapoDevice.GetOnOffStateAsync
- 2023-04-21 make GetDeviceInfoResponse.Result generic type to make it extensible and reduce dependency to TapoDeviceInfo
- 2023-04-21 rename IDynamicDeviceEndPoint.Invalidate() from InvalidateEndPoint()
- 2023-04-21 rename IDeviceEndPoint.ResolveAsync() from GetEndPointAsync()
- 2023-04-21 rename files
- 2023-04-21 rename IDeviceEndPoint from IDeviceEndPointProvider
- 2023-04-21 reduce usage of record struct
- 2023-04-21 clarify interface method specifications
- 2023-04-21 improve message of TapoErrorResponseException
- 2023-04-21 remove ErrorCode enum types
- 2023-04-16 add log point
- 2023-04-16 lower the log level
- 2023-04-16 use Microsoft.Extensions.DependencyInjection.Abstractions instead of Microsoft.Extensions.DependencyInjection
- 2023-04-15 bump Smdn.Net.AddressResolution up to 1.0.0-rc2
- 2023-04-15 bump Smdn.MSBuild.DefineConstants.NETSdkApi up to 1.3.11
- 2023-04-15 methodize the receive operations
- 2023-04-15 attempt receiving rest of body with a timeout period when a part of the expected body is received
- 2023-04-15 increase the receive block size
- 2023-04-15 add log point
- 2023-04-15 implement auto connection refresh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-net6.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-net6.0.apilist.cs | |
deleted file mode 100644 | |
index 57cc447..0000000 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-net6.0.apilist.cs | |
+++ /dev/null | |
@@ -1,807 +0,0 @@ | |
-// Smdn.TPSmartHomeDevices.dll (Smdn.TPSmartHomeDevices-1.0.0-preview3) | |
-// Name: Smdn.TPSmartHomeDevices | |
-// AssemblyVersion: 1.0.0.0 | |
-// InformationalVersion: 1.0.0-preview3+ae327055c2651d84fffc372b481a72257af2a9d8 | |
-// TargetFramework: .NETCoreApp,Version=v6.0 | |
-// Configuration: Release | |
-// Referenced assemblies: | |
-// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Win32.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.1.0, Culture=neutral | |
-// Smdn.Net.AddressResolution, Version=1.0.0.0, Culture=neutral | |
-// System.Collections, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.ComponentModel, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Linq, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Memory, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Net.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Net.Http.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// 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.Net.Sockets, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Runtime, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Security.Cryptography.Algorithms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Security.Cryptography.Encoding, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Security.Cryptography.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Text.Encodings.Web, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-#nullable enable annotations | |
- | |
-using System; | |
-using System.Buffers; | |
-using System.Collections.Generic; | |
-using System.Diagnostics.CodeAnalysis; | |
-using System.Net; | |
-using System.Net.Http; | |
-using System.Net.NetworkInformation; | |
-using System.Security.Cryptography; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.Net.AddressResolution; | |
-using Smdn.TPSmartHomeDevices; | |
-using Smdn.TPSmartHomeDevices.Kasa; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
-using Smdn.TPSmartHomeDevices.Tapo; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices { | |
- public interface IDeviceEndPointFactory<TAddress> { | |
- IDeviceEndPointProvider Create(TAddress address, int port = 0); | |
- } | |
- | |
- public interface IDeviceEndPointProvider { | |
- ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken = default); | |
- } | |
- | |
- public interface IDynamicDeviceEndPointProvider : IDeviceEndPointProvider { | |
- void InvalidateEndPoint(); | |
- } | |
- | |
- public static class DeviceEndPointFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) {} | |
- } | |
- | |
- public class DeviceEndPointResolutionException : Exception { | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider) {} | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider, string message, Exception? innerException) {} | |
- | |
- public IDeviceEndPointProvider EndPointProvider { get; } | |
- } | |
- | |
- public class MacAddressDeviceEndPointFactory : | |
- IDeviceEndPointFactory<PhysicalAddress>, | |
- IDisposable | |
- { | |
- protected class MacAddressDeviceEndPointProvider : IDynamicDeviceEndPointProvider { | |
- public MacAddressDeviceEndPointProvider(IAddressResolver<PhysicalAddress, IPAddress> resolver, PhysicalAddress address, int port) {} | |
- | |
- public async ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken) {} | |
- public void InvalidateEndPoint() {} | |
- public override string ToString() {} | |
- } | |
- | |
- protected MacAddressDeviceEndPointFactory(IAddressResolver<PhysicalAddress, IPAddress> resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolver resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {} | |
- | |
- public virtual IDeviceEndPointProvider Create(PhysicalAddress address, int port = 0) {} | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected void ThrowIfDisposed() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa { | |
- public class HS105 : KasaDevice { | |
- public HS105(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public HS105(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public HS105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public HS105(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KL130 : KasaDevice { | |
- public KL130(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public KL130(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public KL130(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public KL130(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<KL130LightState> GetLightStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KasaDevice : IDisposable { | |
- protected readonly struct NullParameter { | |
- } | |
- | |
- protected static readonly JsonEncodedText MethodTextGetSysInfo; // = "get_sysinfo" | |
- protected static readonly JsonEncodedText ModuleTextSystem; // = "system" | |
- | |
- public static KasaDevice Create(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static KasaDevice Create(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected KasaDevice(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected KasaDevice(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public bool IsConnected { get; } | |
- protected bool IsDisposed { get; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TMethodParameter>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodResult>(JsonEncodedText module, JsonEncodedText method, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- } | |
- | |
- public static class KasaDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class KasaDisconnectedException : KasaProtocolException { | |
- public KasaDisconnectedException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- } | |
- | |
- public class KasaErrorResponseException : KasaUnexpectedResponseException { | |
- public KasaErrorResponseException(EndPoint deviceEndPoint, string requestModule, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- } | |
- | |
- public class KasaIncompleteResponseException : KasaUnexpectedResponseException { | |
- public KasaIncompleteResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- } | |
- | |
- public abstract class KasaProtocolException : InvalidOperationException { | |
- protected KasaProtocolException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- | |
- public EndPoint DeviceEndPoint { get; } | |
- } | |
- | |
- public class KasaUnexpectedResponseException : KasaProtocolException { | |
- public KasaUnexpectedResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- | |
- public string RequestMethod { get; } | |
- public string RequestModule { get; } | |
- } | |
- | |
- public readonly struct KL130LightState { | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("brightness")] | |
- public int? Brightness { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("color_temp")] | |
- public int? ColorTemperature { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("hue")] | |
- public int? Hue { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- [JsonPropertyName("on_off")] | |
- public bool IsOn { get; init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("mode")] | |
- public string? Mode { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("saturation")] | |
- public int? Saturation { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol { | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public sealed class KasaClient : IDisposable { | |
- public const int DefaultPort = 9999; | |
- | |
- public KasaClient(EndPoint endPoint, ILogger? logger = null) {} | |
- | |
- public EndPoint EndPoint { get; } | |
- public bool IsConnected { get; } | |
- | |
- public void Dispose() {} | |
- public ValueTask<TMethodResult> SendAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public abstract class KasaClientExceptionHandler { | |
- internal protected static readonly KasaClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Kasa.Protocol.KasaClientDefaultExceptionHandler" | |
- | |
- protected KasaClientExceptionHandler() {} | |
- | |
- public abstract KasaClientExceptionHandling DetermineHandling(KasaDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public static class KasaJsonSerializer { | |
- public const byte InitialKey = 171; | |
- | |
- public static void DecryptInPlace(Span<byte> body) {} | |
- public static JsonElement Deserialize(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, ILogger? logger = null) {} | |
- public static void EncryptInPlace(Span<byte> body) {} | |
- public static void Serialize<TMethodParameter>(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, ILogger? logger = null) {} | |
- } | |
- | |
- public class KasaMessageBodyTooShortException : KasaMessageException { | |
- public KasaMessageBodyTooShortException(int indicatedLength, int actualLength) {} | |
- | |
- public int ActualLength { get; } | |
- public int IndicatedLength { get; } | |
- } | |
- | |
- public class KasaMessageException : SystemException { | |
- public KasaMessageException(string message) {} | |
- } | |
- | |
- public class KasaMessageHeaderTooShortException : KasaMessageException { | |
- public KasaMessageHeaderTooShortException(string message) {} | |
- } | |
- | |
- public readonly struct KasaClientExceptionHandling { | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- | |
- public static KasaClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
- public L530(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L530(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L530(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L530(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L530(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L530(string host, IServiceProvider serviceProvider) {} | |
- public L530(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class L900 : TapoDevice { | |
- public L900(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L900(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L900(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L900(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L900(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L900(string host, IServiceProvider serviceProvider) {} | |
- public L900(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class P105 : TapoDevice { | |
- public P105(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public P105(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public P105(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public P105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public P105(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public P105(string host, IServiceProvider serviceProvider) {} | |
- public P105(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- } | |
- | |
- public class TapoAuthenticationException : TapoProtocolException { | |
- public TapoAuthenticationException(string message, Uri endPoint, Exception? innerException = null) {} | |
- } | |
- | |
- public static class TapoCredentailProviderServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoBase64EncodedCredential(this IServiceCollection services, string base64UserNameSHA1Digest, string base64Password) {} | |
- public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
- public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
- } | |
- | |
- public class TapoDevice : | |
- IDisposable, | |
- ITapoCredentialIdentity | |
- { | |
- public static TapoDevice Create(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected TapoDevice(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, TapoClientExceptionHandler? exceptionHandler = null, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected bool IsDisposed { get; } | |
- public TapoSession? Session { get; } | |
- string ITapoCredentialIdentity.Name { get; } | |
- public string TerminalUuidString { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected ValueTask EnsureSessionEstablishedAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask SetDeviceInfoAsync<TParameters>(TParameters parameters, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public static class TapoDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class TapoDeviceInfo { | |
- public TapoDeviceInfo() {} | |
- | |
- [JsonPropertyName("avatar")] | |
- public string? Avatar { get; init; } | |
- [JsonPropertyName("fw_id")] | |
- public string? FirmwareId { get; init; } | |
- [JsonPropertyName("fw_ver")] | |
- public string? FirmwareVersion { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("latitude")] | |
- public decimal? GeolocationLatitude { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("longitude")] | |
- public decimal? GeolocationLongitude { get; init; } | |
- [JsonPropertyName("hw_id")] | |
- public string? HardwareId { get; init; } | |
- [JsonPropertyName("specs")] | |
- public string? HardwareSpecifications { get; init; } | |
- [JsonPropertyName("hw_ver")] | |
- public string? HardwareVersion { get; init; } | |
- [JsonPropertyName("has_set_location_info")] | |
- public bool HasGeolocationInfoSet { get; init; } | |
- [JsonConverter(typeof(TapoIPAddressJsonConverter))] | |
- [JsonPropertyName("ip")] | |
- public IPAddress? IPAddress { get; init; } | |
- [JsonPropertyName("device_id")] | |
- public string? Id { get; init; } | |
- [JsonPropertyName("device_on")] | |
- public bool IsOn { get; init; } | |
- [JsonPropertyName("overheated")] | |
- public bool IsOverheated { get; init; } | |
- [JsonPropertyName("lang")] | |
- public string? Language { get; init; } | |
- [JsonConverter(typeof(MacAddressJsonConverter))] | |
- [JsonPropertyName("mac")] | |
- public PhysicalAddress? MacAddress { get; init; } | |
- [JsonPropertyName("model")] | |
- public string? ModelName { get; init; } | |
- [JsonPropertyName("rssi")] | |
- public decimal? NetworkRssi { get; init; } | |
- [JsonPropertyName("signal_level")] | |
- public int? NetworkSignalLevel { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("ssid")] | |
- public string? NetworkSsid { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("nickname")] | |
- public string? NickName { get; init; } | |
- [JsonPropertyName("oem_id")] | |
- public string? OemId { get; init; } | |
- [JsonConverter(typeof(TimeSpanInSecondsJsonConverter))] | |
- [JsonPropertyName("on_time")] | |
- public TimeSpan? OnTimeDuration { get; init; } | |
- [JsonIgnore] | |
- public DateTimeOffset TimeStamp { get; } | |
- [JsonConverter(typeof(TimeSpanInMinutesJsonConverter))] | |
- [JsonPropertyName("time_diff")] | |
- public TimeSpan? TimeZoneOffset { get; init; } | |
- [JsonPropertyName("region")] | |
- public string? TimeZoneRegion { get; init; } | |
- [JsonPropertyName("type")] | |
- public string? TypeName { get; init; } | |
- } | |
- | |
- public class TapoErrorResponseException : TapoProtocolException { | |
- public TapoErrorResponseException(Uri requestEndPoint, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- public string RequestMethod { get; } | |
- } | |
- | |
- public static class TapoHttpClientFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoHttpClient(this IServiceCollection services, Action<HttpClient>? configureClient = null) {} | |
- } | |
- | |
- public class TapoProtocolException : InvalidOperationException { | |
- internal protected TapoProtocolException(string message, Uri endPoint, Exception? innerException) {} | |
- | |
- public Uri EndPoint { get; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
- public interface ITapoCredential : IDisposable { | |
- void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
- void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
- } | |
- | |
- public interface ITapoCredentialIdentity { | |
- string Name { get; } | |
- } | |
- | |
- public interface ITapoCredentialProvider { | |
- ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
- } | |
- | |
- public static class TapoCredentialUtils { | |
- public const int HexSHA1HashSizeInBytes = 40; | |
- | |
- public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
- public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
- public interface ITapoPassThroughRequest : ITapoRequest { | |
- } | |
- | |
- public interface ITapoPassThroughResponse : ITapoResponse { | |
- } | |
- | |
- public interface ITapoRequest { | |
- string Method { get; } | |
- } | |
- | |
- public interface ITapoResponse { | |
- ErrorCode ErrorCode { get; init; } | |
- } | |
- | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public class SecurePassThroughInvalidPaddingException : SystemException { | |
- public SecurePassThroughInvalidPaddingException(string message, Exception? innerException) {} | |
- } | |
- | |
- public sealed class SecurePassThroughJsonConverterFactory : | |
- JsonConverterFactory, | |
- IDisposable | |
- { | |
- public SecurePassThroughJsonConverterFactory(ITapoCredentialIdentity? identity, ICryptoTransform? encryptorForPassThroughRequest, ICryptoTransform? decryptorForPassThroughResponse, JsonSerializerOptions? baseJsonSerializerOptionsForPassThroughMessage, ILogger? logger = null) {} | |
- | |
- public override bool CanConvert(Type typeToConvert) {} | |
- public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) {} | |
- public void Dispose() {} | |
- } | |
- | |
- public sealed class TapoClient : IDisposable { | |
- public const int DefaultPort = 80; | |
- | |
- public TapoClient(EndPoint endPoint, IHttpClientFactory? httpClientFactory = null, ILogger? logger = null) {} | |
- | |
- public Uri EndPointUri { get; } | |
- public TapoSession? Session { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- public ValueTask AuthenticateAsync(ITapoCredentialIdentity? identity, ITapoCredentialProvider credential, CancellationToken cancellationToken = default) {} | |
- public void Dispose() {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest, new() where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- } | |
- | |
- public abstract class TapoClientExceptionHandler { | |
- internal protected static readonly TapoClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoClientDefaultExceptionHandler" | |
- | |
- protected TapoClientExceptionHandler() {} | |
- | |
- public abstract TapoClientExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public sealed class TapoSession : IDisposable { | |
- public DateTime ExpiresOn { get; } | |
- public bool HasExpired { get; } | |
- public Uri RequestPathAndQuery { get; } | |
- public string? SessionId { get; } | |
- public string? Token { get; } | |
- | |
- public void Dispose() {} | |
- } | |
- | |
- public static class TapoSessionCookieUtils { | |
- public static bool TryGetCookie(HttpResponseMessage response, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryGetCookie(IEnumerable<string>? cookieValues, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryParseCookie(ReadOnlySpan<char> cookie, out string? id, out int? timeout) {} | |
- } | |
- | |
- public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct GetDeviceInfoResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public TapoDeviceInfo Result { get; init; } | |
- } | |
- | |
- public readonly struct HandshakeRequest : ITapoRequest { | |
- public readonly struct RequestParameters : IEquatable<RequestParameters> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- | |
- public RequestParameters(string Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string Key { get; init; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeRequest.RequestParameters other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public HandshakeRequest(string key) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public HandshakeRequest.RequestParameters Parameters { get; } | |
- } | |
- | |
- public readonly struct HandshakeResponse : ITapoResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string? Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string? Key { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string? Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public HandshakeResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct LoginDeviceRequest : ITapoPassThroughRequest { | |
- public LoginDeviceRequest(ITapoCredentialProvider credential) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public ITapoCredentialProvider Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct LoginDeviceResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string Token) {} | |
- | |
- [JsonPropertyName("token")] | |
- public string Token { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Token) {} | |
- [CompilerGenerated] | |
- public bool Equals(LoginDeviceResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public LoginDeviceResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : ITapoPassThroughRequest { | |
- public readonly struct RequestParams : IEquatable<RequestParams> where TPassThroughRequest : ITapoPassThroughRequest { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- | |
- public RequestParams(TPassThroughRequest PassThroughRequest) {} | |
- | |
- [JsonPropertyName("request")] | |
- public TPassThroughRequest PassThroughRequest { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughRequest PassThroughRequest) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughRequest<TPassThroughRequest>.RequestParams other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughRequest(TPassThroughRequest passThroughRequest) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public SecurePassThroughRequest<TPassThroughRequest>.RequestParams Params { get; } | |
- } | |
- | |
- public readonly struct SecurePassThroughResponse<TPassThroughResponse> : ITapoResponse where TPassThroughResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> where TPassThroughResponse : ITapoPassThroughResponse { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- | |
- public ResponseResult(TPassThroughResponse PassThroughResponse) {} | |
- | |
- [JsonPropertyName("response")] | |
- public TPassThroughResponse PassThroughResponse { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughResponse PassThroughResponse) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughResponse<TPassThroughResponse>.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughResponse(ErrorCode errorCode, TPassThroughResponse passThroughResponse) {} | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SecurePassThroughResponse<TPassThroughResponse>.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoRequest<TParameters> : ITapoPassThroughRequest { | |
- public SetDeviceInfoRequest(string terminalUuid, TParameters parameters) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public TParameters Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- [JsonPropertyName("terminalUUID")] | |
- public string TerminalUuid { get; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(IDictionary<string, object>? ExtraData) {} | |
- | |
- [JsonExtensionData] | |
- public IDictionary<string, object>? ExtraData { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out IDictionary<string, object>? ExtraData) {} | |
- [CompilerGenerated] | |
- public bool Equals(SetDeviceInfoResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SetDeviceInfoResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct TapoClientExceptionHandling { | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
- | |
- public static TapoClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
-// 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/Smdn.TPSmartHomeDevices-net7.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-net7.0.apilist.cs | |
deleted file mode 100644 | |
index 6eb1832..0000000 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-net7.0.apilist.cs | |
+++ /dev/null | |
@@ -1,805 +0,0 @@ | |
-// Smdn.TPSmartHomeDevices.dll (Smdn.TPSmartHomeDevices-1.0.0-preview3) | |
-// Name: Smdn.TPSmartHomeDevices | |
-// AssemblyVersion: 1.0.0.0 | |
-// InformationalVersion: 1.0.0-preview3+ae327055c2651d84fffc372b481a72257af2a9d8 | |
-// TargetFramework: .NETCoreApp,Version=v7.0 | |
-// Configuration: Release | |
-// Referenced assemblies: | |
-// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Win32.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.1.0, Culture=neutral | |
-// Smdn.Net.AddressResolution, Version=1.0.0.0, Culture=neutral | |
-// System.Collections, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.ComponentModel, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Linq, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Memory, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Net.Http, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Net.Http.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Net.NetworkInformation, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Net.Primitives, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Net.Sockets, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Runtime, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Security.Cryptography, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
-// System.Text.Encodings.Web, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Text.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-#nullable enable annotations | |
- | |
-using System; | |
-using System.Buffers; | |
-using System.Collections.Generic; | |
-using System.Diagnostics.CodeAnalysis; | |
-using System.Net; | |
-using System.Net.Http; | |
-using System.Net.NetworkInformation; | |
-using System.Security.Cryptography; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.Net.AddressResolution; | |
-using Smdn.TPSmartHomeDevices; | |
-using Smdn.TPSmartHomeDevices.Kasa; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
-using Smdn.TPSmartHomeDevices.Tapo; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices { | |
- public interface IDeviceEndPointFactory<TAddress> { | |
- IDeviceEndPointProvider Create(TAddress address, int port = 0); | |
- } | |
- | |
- public interface IDeviceEndPointProvider { | |
- ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken = default); | |
- } | |
- | |
- public interface IDynamicDeviceEndPointProvider : IDeviceEndPointProvider { | |
- void InvalidateEndPoint(); | |
- } | |
- | |
- public static class DeviceEndPointFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) {} | |
- } | |
- | |
- public class DeviceEndPointResolutionException : Exception { | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider) {} | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider, string message, Exception? innerException) {} | |
- | |
- public IDeviceEndPointProvider EndPointProvider { get; } | |
- } | |
- | |
- public class MacAddressDeviceEndPointFactory : | |
- IDeviceEndPointFactory<PhysicalAddress>, | |
- IDisposable | |
- { | |
- protected class MacAddressDeviceEndPointProvider : IDynamicDeviceEndPointProvider { | |
- public MacAddressDeviceEndPointProvider(IAddressResolver<PhysicalAddress, IPAddress> resolver, PhysicalAddress address, int port) {} | |
- | |
- public async ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken) {} | |
- public void InvalidateEndPoint() {} | |
- public override string ToString() {} | |
- } | |
- | |
- protected MacAddressDeviceEndPointFactory(IAddressResolver<PhysicalAddress, IPAddress> resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolver resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {} | |
- | |
- public virtual IDeviceEndPointProvider Create(PhysicalAddress address, int port = 0) {} | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected void ThrowIfDisposed() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa { | |
- public class HS105 : KasaDevice { | |
- public HS105(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public HS105(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public HS105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public HS105(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KL130 : KasaDevice { | |
- public KL130(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public KL130(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public KL130(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public KL130(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<KL130LightState> GetLightStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KasaDevice : IDisposable { | |
- protected readonly struct NullParameter { | |
- } | |
- | |
- protected static readonly JsonEncodedText MethodTextGetSysInfo; // = "get_sysinfo" | |
- protected static readonly JsonEncodedText ModuleTextSystem; // = "system" | |
- | |
- public static KasaDevice Create(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static KasaDevice Create(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected KasaDevice(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected KasaDevice(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public bool IsConnected { get; } | |
- protected bool IsDisposed { get; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TMethodParameter>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodResult>(JsonEncodedText module, JsonEncodedText method, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- } | |
- | |
- public static class KasaDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class KasaDisconnectedException : KasaProtocolException { | |
- public KasaDisconnectedException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- } | |
- | |
- public class KasaErrorResponseException : KasaUnexpectedResponseException { | |
- public KasaErrorResponseException(EndPoint deviceEndPoint, string requestModule, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- } | |
- | |
- public class KasaIncompleteResponseException : KasaUnexpectedResponseException { | |
- public KasaIncompleteResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- } | |
- | |
- public abstract class KasaProtocolException : InvalidOperationException { | |
- protected KasaProtocolException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- | |
- public EndPoint DeviceEndPoint { get; } | |
- } | |
- | |
- public class KasaUnexpectedResponseException : KasaProtocolException { | |
- public KasaUnexpectedResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- | |
- public string RequestMethod { get; } | |
- public string RequestModule { get; } | |
- } | |
- | |
- public readonly struct KL130LightState { | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("brightness")] | |
- public int? Brightness { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("color_temp")] | |
- public int? ColorTemperature { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("hue")] | |
- public int? Hue { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- [JsonPropertyName("on_off")] | |
- public bool IsOn { get; init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("mode")] | |
- public string? Mode { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- [MemberNotNullWhen(true, "IsOn")] | |
- [JsonPropertyName("saturation")] | |
- public int? Saturation { [MemberNotNullWhen(true, "IsOn")] get; [MemberNotNullWhen(true, "IsOn")] init; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol { | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public sealed class KasaClient : IDisposable { | |
- public const int DefaultPort = 9999; | |
- | |
- public KasaClient(EndPoint endPoint, ILogger? logger = null) {} | |
- | |
- public EndPoint EndPoint { get; } | |
- public bool IsConnected { get; } | |
- | |
- public void Dispose() {} | |
- public ValueTask<TMethodResult> SendAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public abstract class KasaClientExceptionHandler { | |
- internal protected static readonly KasaClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Kasa.Protocol.KasaClientDefaultExceptionHandler" | |
- | |
- protected KasaClientExceptionHandler() {} | |
- | |
- public abstract KasaClientExceptionHandling DetermineHandling(KasaDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public static class KasaJsonSerializer { | |
- public const byte InitialKey = 171; | |
- | |
- public static void DecryptInPlace(Span<byte> body) {} | |
- public static JsonElement Deserialize(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, ILogger? logger = null) {} | |
- public static void EncryptInPlace(Span<byte> body) {} | |
- public static void Serialize<TMethodParameter>(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, ILogger? logger = null) {} | |
- } | |
- | |
- public class KasaMessageBodyTooShortException : KasaMessageException { | |
- public KasaMessageBodyTooShortException(int indicatedLength, int actualLength) {} | |
- | |
- public int ActualLength { get; } | |
- public int IndicatedLength { get; } | |
- } | |
- | |
- public class KasaMessageException : SystemException { | |
- public KasaMessageException(string message) {} | |
- } | |
- | |
- public class KasaMessageHeaderTooShortException : KasaMessageException { | |
- public KasaMessageHeaderTooShortException(string message) {} | |
- } | |
- | |
- public readonly struct KasaClientExceptionHandling { | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- | |
- public static KasaClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
- public L530(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L530(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L530(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L530(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L530(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L530(string host, IServiceProvider serviceProvider) {} | |
- public L530(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class L900 : TapoDevice { | |
- public L900(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L900(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L900(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L900(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L900(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L900(string host, IServiceProvider serviceProvider) {} | |
- public L900(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class P105 : TapoDevice { | |
- public P105(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public P105(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public P105(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public P105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public P105(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public P105(string host, IServiceProvider serviceProvider) {} | |
- public P105(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- } | |
- | |
- public class TapoAuthenticationException : TapoProtocolException { | |
- public TapoAuthenticationException(string message, Uri endPoint, Exception? innerException = null) {} | |
- } | |
- | |
- public static class TapoCredentailProviderServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoBase64EncodedCredential(this IServiceCollection services, string base64UserNameSHA1Digest, string base64Password) {} | |
- public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
- public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
- } | |
- | |
- public class TapoDevice : | |
- IDisposable, | |
- ITapoCredentialIdentity | |
- { | |
- public static TapoDevice Create(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected TapoDevice(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, TapoClientExceptionHandler? exceptionHandler = null, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected bool IsDisposed { get; } | |
- public TapoSession? Session { get; } | |
- string ITapoCredentialIdentity.Name { get; } | |
- public string TerminalUuidString { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected ValueTask EnsureSessionEstablishedAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask SetDeviceInfoAsync<TParameters>(TParameters parameters, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public static class TapoDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class TapoDeviceInfo { | |
- public TapoDeviceInfo() {} | |
- | |
- [JsonPropertyName("avatar")] | |
- public string? Avatar { get; init; } | |
- [JsonPropertyName("fw_id")] | |
- public string? FirmwareId { get; init; } | |
- [JsonPropertyName("fw_ver")] | |
- public string? FirmwareVersion { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("latitude")] | |
- public decimal? GeolocationLatitude { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("longitude")] | |
- public decimal? GeolocationLongitude { get; init; } | |
- [JsonPropertyName("hw_id")] | |
- public string? HardwareId { get; init; } | |
- [JsonPropertyName("specs")] | |
- public string? HardwareSpecifications { get; init; } | |
- [JsonPropertyName("hw_ver")] | |
- public string? HardwareVersion { get; init; } | |
- [JsonPropertyName("has_set_location_info")] | |
- public bool HasGeolocationInfoSet { get; init; } | |
- [JsonConverter(typeof(TapoIPAddressJsonConverter))] | |
- [JsonPropertyName("ip")] | |
- public IPAddress? IPAddress { get; init; } | |
- [JsonPropertyName("device_id")] | |
- public string? Id { get; init; } | |
- [JsonPropertyName("device_on")] | |
- public bool IsOn { get; init; } | |
- [JsonPropertyName("overheated")] | |
- public bool IsOverheated { get; init; } | |
- [JsonPropertyName("lang")] | |
- public string? Language { get; init; } | |
- [JsonConverter(typeof(MacAddressJsonConverter))] | |
- [JsonPropertyName("mac")] | |
- public PhysicalAddress? MacAddress { get; init; } | |
- [JsonPropertyName("model")] | |
- public string? ModelName { get; init; } | |
- [JsonPropertyName("rssi")] | |
- public decimal? NetworkRssi { get; init; } | |
- [JsonPropertyName("signal_level")] | |
- public int? NetworkSignalLevel { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("ssid")] | |
- public string? NetworkSsid { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("nickname")] | |
- public string? NickName { get; init; } | |
- [JsonPropertyName("oem_id")] | |
- public string? OemId { get; init; } | |
- [JsonConverter(typeof(TimeSpanInSecondsJsonConverter))] | |
- [JsonPropertyName("on_time")] | |
- public TimeSpan? OnTimeDuration { get; init; } | |
- [JsonIgnore] | |
- public DateTimeOffset TimeStamp { get; } | |
- [JsonConverter(typeof(TimeSpanInMinutesJsonConverter))] | |
- [JsonPropertyName("time_diff")] | |
- public TimeSpan? TimeZoneOffset { get; init; } | |
- [JsonPropertyName("region")] | |
- public string? TimeZoneRegion { get; init; } | |
- [JsonPropertyName("type")] | |
- public string? TypeName { get; init; } | |
- } | |
- | |
- public class TapoErrorResponseException : TapoProtocolException { | |
- public TapoErrorResponseException(Uri requestEndPoint, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- public string RequestMethod { get; } | |
- } | |
- | |
- public static class TapoHttpClientFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoHttpClient(this IServiceCollection services, Action<HttpClient>? configureClient = null) {} | |
- } | |
- | |
- public class TapoProtocolException : InvalidOperationException { | |
- internal protected TapoProtocolException(string message, Uri endPoint, Exception? innerException) {} | |
- | |
- public Uri EndPoint { get; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
- public interface ITapoCredential : IDisposable { | |
- void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
- void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
- } | |
- | |
- public interface ITapoCredentialIdentity { | |
- string Name { get; } | |
- } | |
- | |
- public interface ITapoCredentialProvider { | |
- ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
- } | |
- | |
- public static class TapoCredentialUtils { | |
- public const int HexSHA1HashSizeInBytes = 40; | |
- | |
- public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
- public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
- public interface ITapoPassThroughRequest : ITapoRequest { | |
- } | |
- | |
- public interface ITapoPassThroughResponse : ITapoResponse { | |
- } | |
- | |
- public interface ITapoRequest { | |
- string Method { get; } | |
- } | |
- | |
- public interface ITapoResponse { | |
- ErrorCode ErrorCode { get; init; } | |
- } | |
- | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public class SecurePassThroughInvalidPaddingException : SystemException { | |
- public SecurePassThroughInvalidPaddingException(string message, Exception? innerException) {} | |
- } | |
- | |
- public sealed class SecurePassThroughJsonConverterFactory : | |
- JsonConverterFactory, | |
- IDisposable | |
- { | |
- public SecurePassThroughJsonConverterFactory(ITapoCredentialIdentity? identity, ICryptoTransform? encryptorForPassThroughRequest, ICryptoTransform? decryptorForPassThroughResponse, JsonSerializerOptions? baseJsonSerializerOptionsForPassThroughMessage, ILogger? logger = null) {} | |
- | |
- public override bool CanConvert(Type typeToConvert) {} | |
- public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) {} | |
- public void Dispose() {} | |
- } | |
- | |
- public sealed class TapoClient : IDisposable { | |
- public const int DefaultPort = 80; | |
- | |
- public TapoClient(EndPoint endPoint, IHttpClientFactory? httpClientFactory = null, ILogger? logger = null) {} | |
- | |
- public Uri EndPointUri { get; } | |
- public TapoSession? Session { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- public ValueTask AuthenticateAsync(ITapoCredentialIdentity? identity, ITapoCredentialProvider credential, CancellationToken cancellationToken = default) {} | |
- public void Dispose() {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest, new() where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- } | |
- | |
- public abstract class TapoClientExceptionHandler { | |
- internal protected static readonly TapoClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoClientDefaultExceptionHandler" | |
- | |
- protected TapoClientExceptionHandler() {} | |
- | |
- public abstract TapoClientExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public sealed class TapoSession : IDisposable { | |
- public DateTime ExpiresOn { get; } | |
- public bool HasExpired { get; } | |
- public Uri RequestPathAndQuery { get; } | |
- public string? SessionId { get; } | |
- public string? Token { get; } | |
- | |
- public void Dispose() {} | |
- } | |
- | |
- public static class TapoSessionCookieUtils { | |
- public static bool TryGetCookie(HttpResponseMessage response, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryGetCookie(IEnumerable<string>? cookieValues, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryParseCookie(ReadOnlySpan<char> cookie, out string? id, out int? timeout) {} | |
- } | |
- | |
- public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct GetDeviceInfoResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public TapoDeviceInfo Result { get; init; } | |
- } | |
- | |
- public readonly struct HandshakeRequest : ITapoRequest { | |
- public readonly struct RequestParameters : IEquatable<RequestParameters> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- | |
- public RequestParameters(string Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string Key { get; init; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeRequest.RequestParameters other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public HandshakeRequest(string key) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public HandshakeRequest.RequestParameters Parameters { get; } | |
- } | |
- | |
- public readonly struct HandshakeResponse : ITapoResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string? Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string? Key { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string? Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public HandshakeResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct LoginDeviceRequest : ITapoPassThroughRequest { | |
- public LoginDeviceRequest(ITapoCredentialProvider credential) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public ITapoCredentialProvider Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct LoginDeviceResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string Token) {} | |
- | |
- [JsonPropertyName("token")] | |
- public string Token { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Token) {} | |
- [CompilerGenerated] | |
- public bool Equals(LoginDeviceResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public LoginDeviceResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : ITapoPassThroughRequest { | |
- public readonly struct RequestParams : IEquatable<RequestParams> where TPassThroughRequest : ITapoPassThroughRequest { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- | |
- public RequestParams(TPassThroughRequest PassThroughRequest) {} | |
- | |
- [JsonPropertyName("request")] | |
- public TPassThroughRequest PassThroughRequest { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughRequest PassThroughRequest) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughRequest<TPassThroughRequest>.RequestParams other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughRequest(TPassThroughRequest passThroughRequest) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public SecurePassThroughRequest<TPassThroughRequest>.RequestParams Params { get; } | |
- } | |
- | |
- public readonly struct SecurePassThroughResponse<TPassThroughResponse> : ITapoResponse where TPassThroughResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> where TPassThroughResponse : ITapoPassThroughResponse { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- | |
- public ResponseResult(TPassThroughResponse PassThroughResponse) {} | |
- | |
- [JsonPropertyName("response")] | |
- public TPassThroughResponse PassThroughResponse { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughResponse PassThroughResponse) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughResponse<TPassThroughResponse>.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughResponse(ErrorCode errorCode, TPassThroughResponse passThroughResponse) {} | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SecurePassThroughResponse<TPassThroughResponse>.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoRequest<TParameters> : ITapoPassThroughRequest { | |
- public SetDeviceInfoRequest(string terminalUuid, TParameters parameters) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public TParameters Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- [JsonPropertyName("terminalUUID")] | |
- public string TerminalUuid { get; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(IDictionary<string, object>? ExtraData) {} | |
- | |
- [JsonExtensionData] | |
- public IDictionary<string, object>? ExtraData { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out IDictionary<string, object>? ExtraData) {} | |
- [CompilerGenerated] | |
- public bool Equals(SetDeviceInfoResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SetDeviceInfoResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct TapoClientExceptionHandling { | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
- | |
- public static TapoClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
-// 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/Smdn.TPSmartHomeDevices-netstandard2.1.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-netstandard2.1.apilist.cs | |
deleted file mode 100644 | |
index aaed417..0000000 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices-netstandard2.1.apilist.cs | |
+++ /dev/null | |
@@ -1,789 +0,0 @@ | |
-// Smdn.TPSmartHomeDevices.dll (Smdn.TPSmartHomeDevices-1.0.0-preview3) | |
-// Name: Smdn.TPSmartHomeDevices | |
-// AssemblyVersion: 1.0.0.0 | |
-// InformationalVersion: 1.0.0-preview3+ae327055c2651d84fffc372b481a72257af2a9d8 | |
-// TargetFramework: .NETStandard,Version=v2.1 | |
-// Configuration: Release | |
-// Referenced assemblies: | |
-// Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Microsoft.Extensions.Logging.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 | |
-// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.1.0, Culture=neutral | |
-// Smdn.Net.AddressResolution, Version=1.0.0.0, Culture=neutral | |
-// System.Net.Http.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// System.Text.Encodings.Web, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
-// 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.Buffers; | |
-using System.Collections.Generic; | |
-using System.Net; | |
-using System.Net.Http; | |
-using System.Net.NetworkInformation; | |
-using System.Security.Cryptography; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.Net.AddressResolution; | |
-using Smdn.TPSmartHomeDevices; | |
-using Smdn.TPSmartHomeDevices.Kasa; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
-using Smdn.TPSmartHomeDevices.Tapo; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices { | |
- public interface IDeviceEndPointFactory<TAddress> { | |
- IDeviceEndPointProvider Create(TAddress address, int port = 0); | |
- } | |
- | |
- public interface IDeviceEndPointProvider { | |
- ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken = default); | |
- } | |
- | |
- public interface IDynamicDeviceEndPointProvider : IDeviceEndPointProvider { | |
- void InvalidateEndPoint(); | |
- } | |
- | |
- public static class DeviceEndPointFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddDeviceEndPointFactory<TAddress>(this IServiceCollection services, IDeviceEndPointFactory<TAddress> endPointFactory) {} | |
- } | |
- | |
- public class DeviceEndPointResolutionException : Exception { | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider) {} | |
- public DeviceEndPointResolutionException(IDeviceEndPointProvider deviceEndPointProvider, string message, Exception? innerException) {} | |
- | |
- public IDeviceEndPointProvider EndPointProvider { get; } | |
- } | |
- | |
- public class MacAddressDeviceEndPointFactory : | |
- IDeviceEndPointFactory<PhysicalAddress>, | |
- IDisposable | |
- { | |
- protected class MacAddressDeviceEndPointProvider : IDynamicDeviceEndPointProvider { | |
- public MacAddressDeviceEndPointProvider(IAddressResolver<PhysicalAddress, IPAddress> resolver, PhysicalAddress address, int port) {} | |
- | |
- public async ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken) {} | |
- public void InvalidateEndPoint() {} | |
- public override string ToString() {} | |
- } | |
- | |
- protected MacAddressDeviceEndPointFactory(IAddressResolver<PhysicalAddress, IPAddress> resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolver resolver, IServiceProvider? serviceProvider = null) {} | |
- public MacAddressDeviceEndPointFactory(MacAddressResolverOptions? options = null, IServiceProvider? serviceProvider = null) {} | |
- | |
- public virtual IDeviceEndPointProvider Create(PhysicalAddress address, int port = 0) {} | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected void ThrowIfDisposed() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa { | |
- public class HS105 : KasaDevice { | |
- public HS105(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public HS105(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public HS105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public HS105(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KL130 : KasaDevice { | |
- public KL130(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public KL130(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public KL130(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public KL130(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask<KL130LightState> GetLightStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(TimeSpan? transitionPeriod = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class KasaDevice : IDisposable { | |
- protected readonly struct NullParameter { | |
- } | |
- | |
- protected static readonly JsonEncodedText MethodTextGetSysInfo; | |
- protected static readonly JsonEncodedText ModuleTextSystem; | |
- | |
- public static KasaDevice Create(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- public static KasaDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static KasaDevice Create(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected KasaDevice(IDeviceEndPointProvider deviceEndPointProvider, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(IPAddress ipAddress, IServiceProvider? serviceProvider = null) {} | |
- protected KasaDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected KasaDevice(string host, IServiceProvider? serviceProvider = null) {} | |
- | |
- public bool IsConnected { get; } | |
- protected bool IsDisposed { get; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TMethodParameter>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameters, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodResult>(JsonEncodedText module, JsonEncodedText method, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken) {} | |
- } | |
- | |
- public static class KasaDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class KasaDisconnectedException : KasaProtocolException { | |
- public KasaDisconnectedException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- } | |
- | |
- public class KasaErrorResponseException : KasaUnexpectedResponseException { | |
- public KasaErrorResponseException(EndPoint deviceEndPoint, string requestModule, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- } | |
- | |
- public class KasaIncompleteResponseException : KasaUnexpectedResponseException { | |
- public KasaIncompleteResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- } | |
- | |
- public abstract class KasaProtocolException : InvalidOperationException { | |
- protected KasaProtocolException(string message, EndPoint deviceEndPoint, Exception? innerException) {} | |
- | |
- public EndPoint DeviceEndPoint { get; } | |
- } | |
- | |
- public class KasaUnexpectedResponseException : KasaProtocolException { | |
- public KasaUnexpectedResponseException(string message, EndPoint deviceEndPoint, string requestModule, string requestMethod, Exception? innerException) {} | |
- | |
- public string RequestMethod { get; } | |
- public string RequestModule { get; } | |
- } | |
- | |
- public readonly struct KL130LightState { | |
- [JsonPropertyName("brightness")] | |
- public int? Brightness { get; init; } | |
- [JsonPropertyName("color_temp")] | |
- public int? ColorTemperature { get; init; } | |
- [JsonPropertyName("hue")] | |
- public int? Hue { get; init; } | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- [JsonPropertyName("on_off")] | |
- public bool IsOn { get; init; } | |
- [JsonPropertyName("mode")] | |
- public string? Mode { get; init; } | |
- [JsonPropertyName("saturation")] | |
- public int? Saturation { get; init; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol { | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public sealed class KasaClient : IDisposable { | |
- public const int DefaultPort = 9999; | |
- | |
- public KasaClient(EndPoint endPoint, ILogger? logger = null) {} | |
- | |
- public EndPoint EndPoint { get; } | |
- public bool IsConnected { get; } | |
- | |
- public void Dispose() {} | |
- public ValueTask<TMethodResult> SendAsync<TMethodParameter, TMethodResult>(JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, Func<JsonElement, TMethodResult> composeResult, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public abstract class KasaClientExceptionHandler { | |
- internal protected static readonly KasaClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Kasa.Protocol.KasaClientDefaultExceptionHandler" | |
- | |
- protected KasaClientExceptionHandler() {} | |
- | |
- public abstract KasaClientExceptionHandling DetermineHandling(KasaDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public static class KasaJsonSerializer { | |
- public const byte InitialKey = 171; | |
- | |
- public static void DecryptInPlace(Span<byte> body) {} | |
- public static JsonElement Deserialize(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, ILogger? logger = null) {} | |
- public static void EncryptInPlace(Span<byte> body) {} | |
- public static void Serialize<TMethodParameter>(ArrayBufferWriter<byte> buffer, JsonEncodedText module, JsonEncodedText method, TMethodParameter parameter, ILogger? logger = null) {} | |
- } | |
- | |
- public class KasaMessageBodyTooShortException : KasaMessageException { | |
- public KasaMessageBodyTooShortException(int indicatedLength, int actualLength) {} | |
- | |
- public int ActualLength { get; } | |
- public int IndicatedLength { get; } | |
- } | |
- | |
- public class KasaMessageException : SystemException { | |
- public KasaMessageException(string message) {} | |
- } | |
- | |
- public class KasaMessageHeaderTooShortException : KasaMessageException { | |
- public KasaMessageHeaderTooShortException(string message) {} | |
- } | |
- | |
- public readonly struct KasaClientExceptionHandling { | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly KasaClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldInvalidateEndPoint=False}" | |
- public static readonly KasaClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldInvalidateEndPoint=False}" | |
- | |
- public static KasaClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
- public L530(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L530(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L530(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L530(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L530(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L530(string host, IServiceProvider serviceProvider) {} | |
- public L530(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class L900 : TapoDevice { | |
- public L900(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public L900(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public L900(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public L900(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public L900(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public L900(string host, IServiceProvider serviceProvider) {} | |
- public L900(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- public ValueTask SetBrightnessAsync(int brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorAsync(int hue, int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorHueAsync(int hue, int? brightness, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetColorSaturationAsync(int saturation, int? brightness = null, CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public class P105 : TapoDevice { | |
- public P105(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public P105(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public P105(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public P105(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public P105(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public P105(string host, IServiceProvider serviceProvider) {} | |
- public P105(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- } | |
- | |
- public class TapoAuthenticationException : TapoProtocolException { | |
- public TapoAuthenticationException(string message, Uri endPoint, Exception? innerException = null) {} | |
- } | |
- | |
- public static class TapoCredentailProviderServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoBase64EncodedCredential(this IServiceCollection services, string base64UserNameSHA1Digest, string base64Password) {} | |
- public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
- public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
- } | |
- | |
- public class TapoDevice : | |
- IDisposable, | |
- ITapoCredentialIdentity | |
- { | |
- public static TapoDevice Create(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, IServiceProvider serviceProvider) {} | |
- public static TapoDevice Create(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected TapoDevice(IDeviceEndPointProvider deviceEndPointProvider, ITapoCredentialProvider? credential = null, TapoClientExceptionHandler? exceptionHandler = null, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- protected TapoDevice(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(PhysicalAddress macAddress, string email, string password, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, IServiceProvider serviceProvider) {} | |
- protected TapoDevice(string host, string email, string password, IServiceProvider? serviceProvider = null) {} | |
- | |
- protected bool IsDisposed { get; } | |
- public TapoSession? Session { get; } | |
- string ITapoCredentialIdentity.Name { get; } | |
- public string TerminalUuidString { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- protected virtual void Dispose(bool disposing) {} | |
- public void Dispose() {} | |
- protected ValueTask EnsureSessionEstablishedAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
- protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask SetDeviceInfoAsync<TParameters>(TParameters parameters, CancellationToken cancellationToken = default) {} | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
- } | |
- | |
- public static class TapoDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IDeviceEndPointFactory<PhysicalAddress> endPointFactory) {} | |
- public static IDeviceEndPointProvider Create(PhysicalAddress macAddress, IServiceProvider serviceProvider) {} | |
- public static IDeviceEndPointProvider Create(string host) {} | |
- } | |
- | |
- public class TapoDeviceInfo { | |
- public TapoDeviceInfo() {} | |
- | |
- [JsonPropertyName("avatar")] | |
- public string? Avatar { get; init; } | |
- [JsonPropertyName("fw_id")] | |
- public string? FirmwareId { get; init; } | |
- [JsonPropertyName("fw_ver")] | |
- public string? FirmwareVersion { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("latitude")] | |
- public decimal? GeolocationLatitude { get; init; } | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- [JsonPropertyName("longitude")] | |
- public decimal? GeolocationLongitude { get; init; } | |
- [JsonPropertyName("hw_id")] | |
- public string? HardwareId { get; init; } | |
- [JsonPropertyName("specs")] | |
- public string? HardwareSpecifications { get; init; } | |
- [JsonPropertyName("hw_ver")] | |
- public string? HardwareVersion { get; init; } | |
- [JsonPropertyName("has_set_location_info")] | |
- public bool HasGeolocationInfoSet { get; init; } | |
- [JsonConverter(typeof(TapoIPAddressJsonConverter))] | |
- [JsonPropertyName("ip")] | |
- public IPAddress? IPAddress { get; init; } | |
- [JsonPropertyName("device_id")] | |
- public string? Id { get; init; } | |
- [JsonPropertyName("device_on")] | |
- public bool IsOn { get; init; } | |
- [JsonPropertyName("overheated")] | |
- public bool IsOverheated { get; init; } | |
- [JsonPropertyName("lang")] | |
- public string? Language { get; init; } | |
- [JsonConverter(typeof(MacAddressJsonConverter))] | |
- [JsonPropertyName("mac")] | |
- public PhysicalAddress? MacAddress { get; init; } | |
- [JsonPropertyName("model")] | |
- public string? ModelName { get; init; } | |
- [JsonPropertyName("rssi")] | |
- public decimal? NetworkRssi { get; init; } | |
- [JsonPropertyName("signal_level")] | |
- public int? NetworkSignalLevel { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("ssid")] | |
- public string? NetworkSsid { get; init; } | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- [JsonPropertyName("nickname")] | |
- public string? NickName { get; init; } | |
- [JsonPropertyName("oem_id")] | |
- public string? OemId { get; init; } | |
- [JsonConverter(typeof(TimeSpanInSecondsJsonConverter))] | |
- [JsonPropertyName("on_time")] | |
- public TimeSpan? OnTimeDuration { get; init; } | |
- [JsonIgnore] | |
- public DateTimeOffset TimeStamp { get; } | |
- [JsonConverter(typeof(TimeSpanInMinutesJsonConverter))] | |
- [JsonPropertyName("time_diff")] | |
- public TimeSpan? TimeZoneOffset { get; init; } | |
- [JsonPropertyName("region")] | |
- public string? TimeZoneRegion { get; init; } | |
- [JsonPropertyName("type")] | |
- public string? TypeName { get; init; } | |
- } | |
- | |
- public class TapoErrorResponseException : TapoProtocolException { | |
- public TapoErrorResponseException(Uri requestEndPoint, string requestMethod, ErrorCode errorCode) {} | |
- | |
- public ErrorCode ErrorCode { get; } | |
- public string RequestMethod { get; } | |
- } | |
- | |
- public static class TapoHttpClientFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoHttpClient(this IServiceCollection services, Action<HttpClient>? configureClient = null) {} | |
- } | |
- | |
- public class TapoProtocolException : InvalidOperationException { | |
- internal protected TapoProtocolException(string message, Uri endPoint, Exception? innerException) {} | |
- | |
- public Uri EndPoint { get; } | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
- public interface ITapoCredential : IDisposable { | |
- [...] <unknown> WritePasswordPropertyValue(...); | |
- [...] <unknown> WriteUsernamePropertyValue(...); | |
- } | |
- | |
- public interface ITapoCredentialIdentity { | |
- string Name { get; } | |
- } | |
- | |
- public interface ITapoCredentialProvider { | |
- ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
- } | |
- | |
- public static class TapoCredentialUtils { | |
- public const int HexSHA1HashSizeInBytes = 40; | |
- | |
- public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
- public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
- } | |
-} | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
- public interface ITapoPassThroughRequest : ITapoRequest { | |
- } | |
- | |
- public interface ITapoPassThroughResponse : ITapoResponse { | |
- } | |
- | |
- public interface ITapoRequest { | |
- string Method { get; } | |
- } | |
- | |
- public interface ITapoResponse { | |
- ErrorCode ErrorCode { get; init; } | |
- } | |
- | |
- public enum ErrorCode : int { | |
- Success = 0, | |
- } | |
- | |
- public class SecurePassThroughInvalidPaddingException : SystemException { | |
- public SecurePassThroughInvalidPaddingException(string message, Exception? innerException) {} | |
- } | |
- | |
- public sealed class SecurePassThroughJsonConverterFactory : | |
- JsonConverterFactory, | |
- IDisposable | |
- { | |
- public SecurePassThroughJsonConverterFactory(ITapoCredentialIdentity? identity, ICryptoTransform? encryptorForPassThroughRequest, ICryptoTransform? decryptorForPassThroughResponse, JsonSerializerOptions? baseJsonSerializerOptionsForPassThroughMessage, ILogger? logger = null) {} | |
- | |
- public override bool CanConvert(Type typeToConvert) {} | |
- public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) {} | |
- public void Dispose() {} | |
- } | |
- | |
- public sealed class TapoClient : IDisposable { | |
- public const int DefaultPort = 80; | |
- | |
- public TapoClient(EndPoint endPoint, IHttpClientFactory? httpClientFactory = null, ILogger? logger = null) {} | |
- | |
- public Uri EndPointUri { get; } | |
- public TapoSession? Session { get; } | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- public ValueTask AuthenticateAsync(ITapoCredentialIdentity? identity, ITapoCredentialProvider credential, CancellationToken cancellationToken = default) {} | |
- public void Dispose() {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest, new() where TResponse : ITapoPassThroughResponse {} | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
- } | |
- | |
- public abstract class TapoClientExceptionHandler { | |
- internal protected static readonly TapoClientExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoClientDefaultExceptionHandler" | |
- | |
- protected TapoClientExceptionHandler() {} | |
- | |
- public abstract TapoClientExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
- } | |
- | |
- public sealed class TapoSession : IDisposable { | |
- public DateTime ExpiresOn { get; } | |
- public bool HasExpired { get; } | |
- public Uri RequestPathAndQuery { get; } | |
- public string? SessionId { get; } | |
- public string? Token { get; } | |
- | |
- public void Dispose() {} | |
- } | |
- | |
- public static class TapoSessionCookieUtils { | |
- public static bool TryGetCookie(HttpResponseMessage response, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryGetCookie(IEnumerable<string>? cookieValues, out string? sessionId, out int? sessionTimeout) {} | |
- public static bool TryParseCookie(ReadOnlySpan<char> cookie, out string? id, out int? timeout) {} | |
- } | |
- | |
- public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct GetDeviceInfoResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public TapoDeviceInfo Result { get; init; } | |
- } | |
- | |
- public readonly struct HandshakeRequest : ITapoRequest { | |
- public readonly struct RequestParameters : IEquatable<RequestParameters> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeRequest.RequestParameters left, HandshakeRequest.RequestParameters right) {} | |
- | |
- public RequestParameters(string Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string Key { get; init; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeRequest.RequestParameters other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public HandshakeRequest(string key) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public HandshakeRequest.RequestParameters Parameters { get; } | |
- } | |
- | |
- public readonly struct HandshakeResponse : ITapoResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (HandshakeResponse.ResponseResult left, HandshakeResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string? Key) {} | |
- | |
- [JsonPropertyName("key")] | |
- public string? Key { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string? Key) {} | |
- [CompilerGenerated] | |
- public bool Equals(HandshakeResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public HandshakeResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct LoginDeviceRequest : ITapoPassThroughRequest { | |
- public LoginDeviceRequest(ITapoCredentialProvider credential) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public ITapoCredentialProvider Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- } | |
- | |
- public readonly struct LoginDeviceResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (LoginDeviceResponse.ResponseResult left, LoginDeviceResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(string Token) {} | |
- | |
- [JsonPropertyName("token")] | |
- public string Token { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out string Token) {} | |
- [CompilerGenerated] | |
- public bool Equals(LoginDeviceResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public LoginDeviceResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : ITapoPassThroughRequest { | |
- public readonly struct RequestParams : IEquatable<RequestParams> where TPassThroughRequest : ITapoPassThroughRequest { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughRequest<TPassThroughRequest>.RequestParams left, SecurePassThroughRequest<TPassThroughRequest>.RequestParams right) {} | |
- | |
- public RequestParams(TPassThroughRequest PassThroughRequest) {} | |
- | |
- [JsonPropertyName("request")] | |
- public TPassThroughRequest PassThroughRequest { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughRequest PassThroughRequest) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughRequest<TPassThroughRequest>.RequestParams other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughRequest(TPassThroughRequest passThroughRequest) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public SecurePassThroughRequest<TPassThroughRequest>.RequestParams Params { get; } | |
- } | |
- | |
- public readonly struct SecurePassThroughResponse<TPassThroughResponse> : ITapoResponse where TPassThroughResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> where TPassThroughResponse : ITapoPassThroughResponse { | |
- [CompilerGenerated] | |
- public static bool operator == (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SecurePassThroughResponse<TPassThroughResponse>.ResponseResult left, SecurePassThroughResponse<TPassThroughResponse>.ResponseResult right) {} | |
- | |
- public ResponseResult(TPassThroughResponse PassThroughResponse) {} | |
- | |
- [JsonPropertyName("response")] | |
- public TPassThroughResponse PassThroughResponse { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out TPassThroughResponse PassThroughResponse) {} | |
- [CompilerGenerated] | |
- public bool Equals(SecurePassThroughResponse<TPassThroughResponse>.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- public SecurePassThroughResponse(ErrorCode errorCode, TPassThroughResponse passThroughResponse) {} | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SecurePassThroughResponse<TPassThroughResponse>.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoRequest<TParameters> : ITapoPassThroughRequest { | |
- public SetDeviceInfoRequest(string terminalUuid, TParameters parameters) {} | |
- | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method { get; } | |
- [JsonPropertyName("params")] | |
- public TParameters Parameters { get; } | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds { get; } | |
- [JsonPropertyName("terminalUUID")] | |
- public string TerminalUuid { get; } | |
- } | |
- | |
- public readonly struct SetDeviceInfoResponse : ITapoPassThroughResponse { | |
- public readonly struct ResponseResult : IEquatable<ResponseResult> { | |
- [CompilerGenerated] | |
- public static bool operator == (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- [CompilerGenerated] | |
- public static bool operator != (SetDeviceInfoResponse.ResponseResult left, SetDeviceInfoResponse.ResponseResult right) {} | |
- | |
- public ResponseResult(IDictionary<string, object>? ExtraData) {} | |
- | |
- [JsonExtensionData] | |
- public IDictionary<string, object>? ExtraData { get; init; } | |
- | |
- [CompilerGenerated] | |
- public void Deconstruct(out IDictionary<string, object>? ExtraData) {} | |
- [CompilerGenerated] | |
- public bool Equals(SetDeviceInfoResponse.ResponseResult other) {} | |
- [CompilerGenerated] | |
- public override bool Equals(object obj) {} | |
- [CompilerGenerated] | |
- public override int GetHashCode() {} | |
- [CompilerGenerated] | |
- public override string ToString() {} | |
- } | |
- | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- [JsonPropertyName("result")] | |
- public SetDeviceInfoResponse.ResponseResult Result { get; init; } | |
- } | |
- | |
- public readonly struct TapoClientExceptionHandling { | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
- public static readonly TapoClientExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling RetryAfterReconnect; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReconnect=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
- public static readonly TapoClientExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReconnect=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
- | |
- public static TapoClientExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReconnect = false) {} | |
- | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldRetry { get; init; } | |
- public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
- | |
- public override string ToString() {} | |
- } | |
-} | |
-// 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- Smdn.TPSmartHomeDevices.latest.nuspec | |
+++ Smdn.TPSmartHomeDevices.1.0.0-rc1.nuspec | |
@@ -1,41 +1,30 @@ | |
<?xml version="1.0" encoding="utf-8"?> | |
-<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | |
+<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> | |
<metadata> | |
<id>Smdn.TPSmartHomeDevices</id> | |
- <version>1.0.0-preview3</version> | |
+ <version>1.0.0-rc1</version> | |
<title>Smdn.TPSmartHomeDevices</title> | |
<authors>smdn</authors> | |
<license type="expression">MIT</license> | |
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl> | |
<icon>Smdn.TPSmartHomeDevices.png</icon> | |
<readme>README.md</readme> | |
<projectUrl>https://github.com/smdn/Smdn.TPSmartHomeDevices/</projectUrl> | |
- <description>.NET implementations for Kasa and Tapo, the TP-Link smart home devices.</description> | |
+ <description>A meta package that addes the dependency of Smdn.TPSmartHomeDevices.Kasa, Smdn.TPSmartHomeDevices.Tapo and Smdn.TPSmartHomeDevices.MacAddressEndPoint. This package is deprecated, use the individual packages instead.</description> | |
<copyright>Copyright © 2022 smdn</copyright> | |
- <tags>smdn.jp tplink-kasa,tplink-tapo,smarthome,homeautomation,smartdevice</tags> | |
- <repository type="git" url="https://github.com/smdn/Smdn.TPSmartHomeDevices" branch="main" commit="ae327055c2651d84fffc372b481a72257af2a9d8" /> | |
+ <tags>smdn.jp metapackage,tplink-kasa,tplink-tapo,smarthome,homeautomation,smartdevice</tags> | |
+ <repository type="git" url="https://github.com/smdn/Smdn.TPSmartHomeDevices" commit="18af508b38d77fa369978c80b2088c6b506a74e7" /> | |
<dependencies> | |
<group targetFramework="net6.0"> | |
- <dependency id="Microsoft.Extensions.DependencyInjection" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Microsoft.Extensions.Http" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Fundamental.PrintableEncoding.Hexadecimal" version="3.0.1" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Net.AddressResolution" version="1.0.0-preview5" exclude="Build,Analyzers" /> | |
- <dependency id="System.Net.Http.Json" version="6.0.0" exclude="Build,Analyzers" /> | |
- </group> | |
- <group targetFramework="net7.0"> | |
- <dependency id="Microsoft.Extensions.DependencyInjection" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Microsoft.Extensions.Http" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Fundamental.PrintableEncoding.Hexadecimal" version="3.0.1" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Net.AddressResolution" version="1.0.0-preview5" exclude="Build,Analyzers" /> | |
- <dependency id="System.Net.Http.Json" version="6.0.0" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Kasa" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.MacAddressEndPoint" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Tapo" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
</group> | |
<group targetFramework=".NETStandard2.1"> | |
- <dependency id="Microsoft.Extensions.DependencyInjection" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Microsoft.Extensions.Http" version="6.0.0" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Fundamental.PrintableEncoding.Hexadecimal" version="3.0.1" exclude="Build,Analyzers" /> | |
- <dependency id="Smdn.Net.AddressResolution" version="1.0.0-preview5" exclude="Build,Analyzers" /> | |
- <dependency id="System.Net.Http.Json" version="6.0.0" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Kasa" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.MacAddressEndPoint" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Tapo" version="[1.0.0-rc1, 1.0.0)" exclude="Build,Analyzers" /> | |
</group> | |
</dependencies> | |
</metadata> | |
</package> | |
\ No newline at end of file |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs | |
deleted file mode 100644 | |
index 7445a17..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/GeolocationInDecimalDegreesJsonConverter.cs | |
+++ /dev/null | |
@@ -1,27 +0,0 @@ | |
-// 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; | |
- | |
-internal sealed class GeolocationInDecimalDegreesJsonConverter : JsonConverter<decimal?> { | |
- private const decimal Scaler = 10000m; | |
- | |
- public override decimal? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => 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/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs | |
deleted file mode 100644 | |
index 989c75a..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/MacAddressJsonConverter.cs | |
+++ /dev/null | |
@@ -1,42 +0,0 @@ | |
-// 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; | |
- | |
-internal sealed class MacAddressJsonConverter : JsonConverter<PhysicalAddress> { | |
- public override PhysicalAddress? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- { | |
- var str = reader.GetString(); | |
- | |
- 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/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs | |
deleted file mode 100644 | |
index d8cf379..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/TimeSpanInMinutesJsonConverter.cs | |
+++ /dev/null | |
@@ -1,25 +0,0 @@ | |
-// 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; | |
- | |
-internal sealed class TimeSpanInMinutesJsonConverter : JsonConverter<TimeSpan?> { | |
- public override TimeSpan? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => reader.TryGetInt32(out var timeDiff) | |
- ? TimeSpan.FromMinutes(timeDiff) | |
- : null; | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- TimeSpan? value, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotImplementedException(); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs | |
deleted file mode 100644 | |
index 35f6793..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Json/TimeSpanInSecondsJsonConverter.cs | |
+++ /dev/null | |
@@ -1,25 +0,0 @@ | |
-// 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; | |
- | |
-internal sealed class TimeSpanInSecondsJsonConverter : JsonConverter<TimeSpan?> { | |
- public override TimeSpan? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => reader.TryGetInt32(out var onTime) | |
- ? TimeSpan.FromSeconds(onTime) | |
- : null; | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- TimeSpan? value, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotImplementedException(); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Json/KasaNumericalBooleanJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Json/KasaNumericalBooleanJsonConverter.cs | |
deleted file mode 100644 | |
index c53f246..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Json/KasaNumericalBooleanJsonConverter.cs | |
+++ /dev/null | |
@@ -1,23 +0,0 @@ | |
-// 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.Kasa.Json; | |
- | |
-internal sealed class KasaNumericalBooleanJsonConverter : JsonConverter<bool> { | |
- public override bool Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => reader.TryGetInt32(out var numericalBool) && numericalBool != 0; | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- bool value, | |
- JsonSerializerOptions options | |
- ) | |
- => writer.WriteNumberValue(value ? 1 : 0); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/ErrorCode.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/ErrorCode.cs | |
deleted file mode 100644 | |
index 66b11c5..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/ErrorCode.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public enum ErrorCode : int { | |
- Success = 0, | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClient.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClient.cs | |
deleted file mode 100644 | |
index e6c7476..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClient.cs | |
+++ /dev/null | |
@@ -1,314 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Buffers; | |
-using System.Net; | |
-using System.Net.Sockets; | |
-using System.Text; | |
-using System.Text.Json; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-/// <remarks> | |
-/// This implementation is based on and ported from the following implementation: <see href="https://github.com/plasticrake/tplink-smarthome-api">plasticrake/tplink-smarthome-api</see>. | |
-/// </remarks> | |
-public sealed partial class KasaClient : IDisposable { | |
- public const int DefaultPort = 9999; | |
- internal const int DefaultBufferCapacity = 1536; // 1.5 [kB] | |
- private static readonly JsonEncodedText PropertyNameForErrorCode = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "err_code"u8 | |
-#else | |
- "err_code" | |
-#endif | |
- ); | |
- | |
- private bool IsDisposed => endPoint is null; | |
- | |
- private EndPoint? endPoint; // if null, it indicates a 'disposed' state. | |
- private Socket? socket; | |
- private readonly ILogger? logger; | |
- private readonly ArrayBufferWriter<byte> buffer; | |
- | |
- public bool IsConnected { | |
- get { | |
- ThrowIfDisposed(); | |
- return socket is not null && socket.Connected; | |
- } | |
- } | |
- | |
- public EndPoint EndPoint { | |
- get { | |
- ThrowIfDisposed(); | |
- return endPoint; | |
- } | |
- } | |
- | |
- internal ILogger? Logger { | |
- get { | |
- ThrowIfDisposed(); | |
- return logger; | |
- } | |
- } | |
- | |
- public KasaClient( | |
- EndPoint endPoint, | |
- ILogger? logger = null | |
- ) | |
- : this( | |
- endPoint: endPoint ?? throw new ArgumentNullException(nameof(endPoint)), | |
- buffer: new(initialCapacity: DefaultBufferCapacity), | |
- logger: logger | |
- ) | |
- { | |
- } | |
- | |
- internal KasaClient( | |
- EndPoint endPoint, | |
- ArrayBufferWriter<byte> buffer, | |
- ILogger? logger | |
- ) | |
- { | |
- this.endPoint = endPoint switch { | |
- null => throw new ArgumentNullException(nameof(endPoint)), | |
- DnsEndPoint dnsEndPoint => dnsEndPoint.Port == 0 | |
- ? new DnsEndPoint( | |
- host: dnsEndPoint.Host, | |
- port: DefaultPort | |
- ) | |
- : dnsEndPoint, | |
- IPEndPoint ipEndPoint => ipEndPoint.Port == 0 | |
- ? new IPEndPoint( | |
- address: ipEndPoint.Address, | |
- port: DefaultPort | |
- ) | |
- : ipEndPoint, | |
- EndPoint ep => ep, // TODO: should throw exception? | |
- }; | |
- | |
- this.buffer = buffer; | |
- this.logger = logger; | |
- this.logger?.LogTrace("Device end point: {DeviceEndPoint} ({DeviceEndPointAddressFamily})", endPoint, endPoint.AddressFamily); | |
- } | |
- | |
- private void Dispose(bool disposing) | |
- { | |
- if (!disposing) | |
- return; | |
- | |
- endPoint = null; | |
- | |
- socket?.Dispose(); | |
- socket = null; | |
- } | |
- | |
- public void Dispose() | |
- { | |
- Dispose(true); | |
- GC.SuppressFinalize(this); | |
- } | |
- | |
- private void ThrowIfDisposed() | |
- { | |
- if (IsDisposed) | |
- throw new ObjectDisposedException(GetType().FullName); | |
- } | |
- | |
- private async ValueTask<Socket> ConnectAsync( | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- var s = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); | |
- | |
- try { | |
- logger?.LogDebug("Connecting"); | |
- | |
- await s.ConnectAsync( | |
- remoteEP: endPoint | |
- // TODO: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- logger?.LogDebug("Connected"); | |
- | |
- return s; | |
- } | |
- catch (Exception ex) { | |
- s.Dispose(); | |
- | |
- if (ex is SocketException exSocket) | |
- logger?.LogError(ex, "Connection failed (NativeErrorCode = {NativeErrorCode})", exSocket.NativeErrorCode); | |
- else | |
- logger?.LogCritical(ex, "Connection failed with unexpected exception"); | |
- throw; | |
- } | |
- } | |
- | |
- public ValueTask<TMethodResult> SendAsync<TMethodParameter, TMethodResult>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameter, | |
- Func<JsonElement, TMethodResult> composeResult, | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- if (parameter is null) | |
- throw new ArgumentNullException(nameof(parameter)); | |
- if (composeResult is null) | |
- throw new ArgumentNullException(nameof(composeResult)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return SendAsyncCore( | |
- module: module, | |
- method: method, | |
- parameter: parameter, | |
- composeResult: composeResult, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- private async ValueTask<TMethodResult> SendAsyncCore<TMethodParameter, TMethodResult>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameter, | |
- Func<JsonElement, TMethodResult> composeResult, | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- if (socket is null) { | |
- // ensure socket created and connected | |
- socket = await ConnectAsync(cancellationToken); | |
- | |
- // clear buffer for the initial use | |
- buffer.Clear(); | |
- } | |
- | |
- /* | |
- * send | |
- */ | |
- try { | |
- const SocketFlags sendSocketFlags = default; | |
- | |
- KasaJsonSerializer.Serialize(buffer, module, method, parameter, logger); | |
- | |
- logger?.LogTrace("Sending request {RequestSize} bytes", buffer.WrittenCount); | |
- | |
- try { | |
- await socket.SendAsync( | |
- buffer.WrittenMemory, | |
- sendSocketFlags, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- } | |
- catch (SocketException ex) when ( | |
- ex.SocketErrorCode is | |
- SocketError.Shutdown or // EPIPE | |
- SocketError.ConnectionReset or // ECONNRESET | |
- SocketError.ConnectionAborted // WSAECONNABORTED | |
- ) { | |
- throw new KasaDisconnectedException(ex.Message, endPoint, ex); | |
- } | |
- } | |
- finally { | |
- buffer.Clear(); // clear buffer state for next use | |
- } | |
- | |
- /* | |
- * receive | |
- */ | |
- try { | |
- const SocketFlags receiveSocketFlags = default; | |
- const int receiveBlockSize = 0x100; | |
- | |
- for (; ;) { | |
- var buf = buffer.GetMemory(receiveBlockSize); | |
- | |
- int len = default; | |
- | |
- try { | |
- len = await socket.ReceiveAsync( | |
- buf, | |
- receiveSocketFlags, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- } | |
- catch (SocketException ex) when ( | |
- ex.SocketErrorCode is | |
- SocketError.ConnectionReset // ECONNRESET | |
- ) { | |
- throw new KasaDisconnectedException(ex.Message, endPoint, ex); | |
- } | |
- | |
- if (len <= 0) | |
- break; | |
- | |
- buffer.Advance(len); | |
- | |
- if (len < buf.Length) | |
- break; | |
- } | |
- | |
- if (buffer.WrittenCount == 0) | |
- throw new KasaDisconnectedException("The peer may have dropped connection", endPoint, innerException: null); | |
- | |
- logger?.LogTrace("Received response {ResponseSize} bytes", buffer.WrittenCount); | |
- logger?.LogTrace("Buffer capacity: {Capacity} bytes", buffer.Capacity); | |
- | |
- JsonElement result = default; | |
- | |
- try { | |
- result = KasaJsonSerializer.Deserialize(buffer, module, method, logger); | |
- } | |
- catch (KasaMessageBodyTooShortException ex) { | |
- throw new KasaIncompleteResponseException( | |
- message: "Received incomplete response: " + ex.Message, | |
- deviceEndPoint: endPoint, | |
- requestModule: Encoding.UTF8.GetString(module.EncodedUtf8Bytes), | |
- requestMethod: Encoding.UTF8.GetString(method.EncodedUtf8Bytes), | |
- innerException: ex | |
- ); | |
- } | |
- catch (KasaMessageException ex) { | |
- throw new KasaUnexpectedResponseException( | |
- message: "Received unexpected or invalid response", | |
- deviceEndPoint: endPoint, | |
- requestModule: Encoding.UTF8.GetString(module.EncodedUtf8Bytes), | |
- requestMethod: Encoding.UTF8.GetString(method.EncodedUtf8Bytes), | |
- innerException: ex | |
- ); | |
- } | |
- | |
- if ( | |
- result.TryGetProperty(PropertyNameForErrorCode.EncodedUtf8Bytes, out var propErrorCode) && | |
- propErrorCode.TryGetInt32(out var errorCode) && | |
- errorCode != 0 | |
- ) { | |
- throw new KasaErrorResponseException( | |
- deviceEndPoint: endPoint, | |
- requestModule: Encoding.UTF8.GetString(module.EncodedUtf8Bytes), | |
- requestMethod: Encoding.UTF8.GetString(method.EncodedUtf8Bytes), | |
- errorCode: (ErrorCode)errorCode | |
- ); | |
- } | |
- | |
- try { | |
- return composeResult(result); | |
- } | |
- catch (Exception ex) { | |
- throw new KasaUnexpectedResponseException( | |
- message: "Unexpected method result: " + JsonSerializer.Serialize(result), | |
- deviceEndPoint: endPoint, | |
- requestModule: Encoding.UTF8.GetString(module.EncodedUtf8Bytes), | |
- requestMethod: Encoding.UTF8.GetString(method.EncodedUtf8Bytes), | |
- ex | |
- ); | |
- } | |
- } | |
- finally { | |
- buffer.Clear(); // clear buffer state for next use | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientDefaultExceptionHandler.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientDefaultExceptionHandler.cs | |
deleted file mode 100644 | |
index 0e3ee1b..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientDefaultExceptionHandler.cs | |
+++ /dev/null | |
@@ -1,77 +0,0 @@ | |
-using System; | |
-using System.Net.Sockets; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-internal class KasaClientDefaultExceptionHandler : KasaClientExceptionHandler { | |
- public override KasaClientExceptionHandling DetermineHandling( | |
- KasaDevice device, | |
- Exception exception, | |
- int attempt, | |
- ILogger? logger | |
- ) | |
- { | |
- switch (exception) { | |
- case SocketException socketException: { | |
- var socketErrorCode = socketException.SocketErrorCode; | |
- | |
- if ( | |
- socketErrorCode is | |
- SocketError.ConnectionRefused or // ECONNREFUSED | |
- SocketError.HostUnreachable or // EHOSTUNREACH | |
- SocketError.NetworkUnreachable // ENETUNREACH | |
- ) { | |
- if (attempt == 0 /* retry just once */) { | |
- // The end point may have changed. | |
- logger?.LogInformation($"Endpoint may have changed ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return KasaClientExceptionHandling.InvalidateEndPointAndRetry; | |
- } | |
- else { | |
- logger?.LogError($"Endpoint unreachable ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return KasaClientExceptionHandling.InvalidateEndPointAndThrow; | |
- } | |
- } | |
- | |
- // The client may have been invalid due to an exception at the transport layer. | |
- logger?.LogError(socketException, $"Unexpected socket exception ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return KasaClientExceptionHandling.Throw; | |
- } | |
- | |
- case KasaDisconnectedException disconnectedException: | |
- if (attempt == 0) { // retry just once | |
- // The peer device disconnected the connection, or may have dropped the connection. | |
- if (disconnectedException.InnerException is SocketException innerSocketException) | |
- logger?.LogDebug($"Disconnected ({nameof(SocketError)}: {(int)innerSocketException.SocketErrorCode} {innerSocketException.SocketErrorCode})"); | |
- else | |
- logger?.LogDebug($"Disconnected ({disconnectedException.Message})"); | |
- | |
- return KasaClientExceptionHandling.RetryAfterReconnect; | |
- } | |
- | |
- return KasaClientExceptionHandling.Throw; | |
- | |
- case KasaIncompleteResponseException ex: | |
- // The peer has been in invalid state(?) and returnd incomplete response. | |
- const int maxRetryIncompleteResponse = 3; | |
- var nextAttempt = attempt + 1; | |
- | |
- if (nextAttempt < maxRetryIncompleteResponse) { // retry up to max attempts | |
- logger?.LogWarning(ex.Message); | |
- return KasaClientExceptionHandling.CreateRetry( | |
- retryAfter: TimeSpan.FromSeconds(2.0), | |
- shouldReconnect: true | |
- ); | |
- } | |
- | |
- return KasaClientExceptionHandling.Throw; | |
- | |
- default: | |
- logger?.LogError(exception, $"Unhandled exception ({exception.GetType().FullName})"); | |
- return KasaClientExceptionHandling.Throw; | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandler.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandler.cs | |
deleted file mode 100644 | |
index 274f0d6..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandler.cs | |
+++ /dev/null | |
@@ -1,17 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public abstract class KasaClientExceptionHandler { | |
- protected internal static readonly KasaClientExceptionHandler Default = new KasaClientDefaultExceptionHandler(); | |
- | |
- public abstract KasaClientExceptionHandling DetermineHandling( | |
- KasaDevice device, | |
- Exception exception, | |
- int attempt, | |
- ILogger? logger | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandling.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandling.cs | |
deleted file mode 100644 | |
index 2415c4f..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaClientExceptionHandling.cs | |
+++ /dev/null | |
@@ -1,31 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public readonly struct KasaClientExceptionHandling { | |
- public static readonly KasaClientExceptionHandling Throw = default; | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndThrow = new() { ShouldInvalidateEndPoint = true }; | |
- | |
- public static readonly KasaClientExceptionHandling Retry = new() { ShouldRetry = true }; | |
- public static readonly KasaClientExceptionHandling RetryAfterReconnect = new() { ShouldRetry = true, ShouldReconnect = true }; | |
- public static readonly KasaClientExceptionHandling InvalidateEndPointAndRetry = new() { ShouldRetry = true, ShouldInvalidateEndPoint = true, }; | |
- | |
- public static KasaClientExceptionHandling CreateRetry( | |
- TimeSpan retryAfter, | |
- bool shouldReconnect = false | |
- ) | |
- => new() { | |
- ShouldRetry = true, | |
- RetryAfter = retryAfter, | |
- ShouldReconnect = shouldReconnect, | |
- }; | |
- | |
- public bool ShouldRetry { get; init; } | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- | |
- public override string ToString() => $"{{{nameof(ShouldRetry)}={ShouldRetry}, {nameof(RetryAfter)}={RetryAfter}, {nameof(ShouldReconnect)}={ShouldReconnect}, {nameof(ShouldInvalidateEndPoint)}={ShouldInvalidateEndPoint}}}"; | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaJsonSerializer.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaJsonSerializer.cs | |
deleted file mode 100644 | |
index ffd4c5a..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaJsonSerializer.cs | |
+++ /dev/null | |
@@ -1,136 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Buffers; | |
-using System.Buffers.Binary; | |
-using System.Runtime.InteropServices; | |
-using System.Text; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-/// <remarks> | |
-/// This implementation is based on and ported from the following implementation: <see href="https://github.com/plasticrake/tplink-smarthome-api">plasticrake/tplink-smarthome-api</see>. | |
-/// </remarks> | |
-public static class KasaJsonSerializer { | |
- public const byte InitialKey = 0xAB; | |
- private const int SizeOfHeaderInBytes = 4; | |
- | |
- private static readonly JsonSerializerOptions serializerOptions = new() { | |
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, | |
- }; | |
- | |
- public static void Serialize<TMethodParameter>( | |
- ArrayBufferWriter<byte> buffer, | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter? parameter, | |
- ILogger? logger = null | |
- ) | |
- { | |
- if (buffer is null) | |
- throw new ArgumentNullException(nameof(buffer)); | |
- | |
- // reserve placeholder for header | |
- buffer.GetSpan(SizeOfHeaderInBytes); | |
- buffer.Advance(SizeOfHeaderInBytes); | |
- | |
- // write json body after header placeholder | |
- using var writer = new Utf8JsonWriter(buffer); | |
- | |
- writer.WriteStartObject(); | |
- writer.WriteStartObject(module); | |
- writer.WritePropertyName(method); | |
- | |
- JsonSerializer.Serialize( | |
- writer, | |
- parameter, | |
- options: serializerOptions | |
- ); | |
- | |
- writer.WriteEndObject(); // method | |
- writer.WriteEndObject(); // module | |
- writer.Flush(); | |
- | |
- if (!MemoryMarshal.TryGetArray(buffer.WrittenMemory, out var writtenArraySegment)) | |
- throw new NotSupportedException("cannot get underlying array segment"); | |
- | |
- // write header | |
- BinaryPrimitives.WriteInt32BigEndian( | |
- writtenArraySegment.AsSpan(0, SizeOfHeaderInBytes), | |
- (int)writer.BytesCommitted | |
- ); | |
- | |
- logger?.LogTrace( | |
- "Request: {Request}", | |
- Encoding.UTF8.GetString(writtenArraySegment.AsSpan(SizeOfHeaderInBytes)) | |
- ); | |
- | |
- // encrypt body | |
- EncryptInPlace(writtenArraySegment.AsSpan(SizeOfHeaderInBytes)); | |
- } | |
- | |
- public static void EncryptInPlace(Span<byte> body) | |
- { | |
- var key = InitialKey; | |
- | |
- for (var i = 0; i < body.Length; i++) { | |
- key = body[i] = (byte)(body[i] ^ key); | |
- } | |
- } | |
- | |
- public static JsonElement Deserialize( | |
- ArrayBufferWriter<byte> buffer, | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- ILogger? logger = null | |
- ) | |
- { | |
- if (buffer is null) | |
- throw new ArgumentNullException(nameof(buffer)); | |
- | |
- var buf = buffer.WrittenMemory; | |
- | |
- if (buf.Length < 4) | |
- throw new KasaMessageHeaderTooShortException($"input too short (expects at least 4 bytes of header but was {buf.Length})"); | |
- | |
- var length = BinaryPrimitives.ReadInt32BigEndian(buf.Slice(0, 4).Span); | |
- var body = buf.Slice(4); | |
- | |
- if (body.Length < length) | |
- throw new KasaMessageBodyTooShortException(indicatedLength: length, actualLength: body.Length); | |
- | |
- body = body.Slice(0, length); | |
- | |
- if (!MemoryMarshal.TryGetArray(body, out var bodyArraySegment)) | |
- throw new NotSupportedException("cannot get underlying array segment"); | |
- | |
- DecryptInPlace(bodyArraySegment.AsSpan()); | |
- | |
- logger?.LogTrace( | |
- "Response: {Response}", | |
- Encoding.UTF8.GetString(bodyArraySegment.AsSpan()) | |
- ); | |
- | |
- var doc = JsonDocument.Parse(bodyArraySegment.AsMemory()); | |
- | |
- if (!doc.RootElement.TryGetProperty(module.EncodedUtf8Bytes, out var propModule)) | |
- throw new KasaMessageException($"The response JSON does not contain the expected property for the module '{module}'."); | |
- | |
- if (!propModule.TryGetProperty(method.EncodedUtf8Bytes, out var propMethod)) | |
- throw new KasaMessageException($"The response JSON does not contain the expected property for the method '{method}'."); | |
- | |
- return propMethod; | |
- } | |
- | |
- public static void DecryptInPlace(Span<byte> body) | |
- { | |
- var key = InitialKey; | |
- | |
- for (var i = 0; i < body.Length; i++) { | |
- (key, body[i]) = (body[i], (byte)(body[i] ^ key)); | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageBodyTooShortException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageBodyTooShortException.cs | |
deleted file mode 100644 | |
index 880307d..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageBodyTooShortException.cs | |
+++ /dev/null | |
@@ -1,20 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public class KasaMessageBodyTooShortException : KasaMessageException { | |
- public int IndicatedLength { get; } | |
- public int ActualLength { get; } | |
- | |
- public KasaMessageBodyTooShortException( | |
- int indicatedLength, | |
- int actualLength | |
- ) | |
- : base( | |
- message: $"length of body is {actualLength} bytes but falls short of {indicatedLength} bytes which is indicated in the header." | |
- ) | |
- { | |
- IndicatedLength = indicatedLength; | |
- ActualLength = actualLength; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageException.cs | |
deleted file mode 100644 | |
index 405ff02..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageException.cs | |
+++ /dev/null | |
@@ -1,12 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public class KasaMessageException : SystemException { | |
- public KasaMessageException(string message) | |
- : base(message: message) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageHeaderTooShortException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageHeaderTooShortException.cs | |
deleted file mode 100644 | |
index 4b2ea5d..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa.Protocol/KasaMessageHeaderTooShortException.cs | |
+++ /dev/null | |
@@ -1,10 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-public class KasaMessageHeaderTooShortException : KasaMessageException { | |
- public KasaMessageHeaderTooShortException(string message) | |
- : base(message: message) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/HS105.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/HS105.cs | |
deleted file mode 100644 | |
index 48f11cb..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/HS105.cs | |
+++ /dev/null | |
@@ -1,145 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-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 Smdn.TPSmartHomeDevices.Kasa.Json; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class HS105 : KasaDevice { | |
- private static readonly JsonEncodedText MethodTextSetRelayState = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "set_relay_state"u8 | |
-#else | |
- "set_relay_state" | |
-#endif | |
- ); | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="HS105"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(string, IServiceProvider?)" /> | |
- public HS105( | |
- string host, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="HS105"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(IPAddress, IServiceProvider?)" /> | |
- public HS105( | |
- IPAddress ipAddress, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="HS105"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(PhysicalAddress, IServiceProvider)" /> | |
- public HS105( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="HS105"/> class. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(IDeviceEndPointProvider, IServiceProvider?)" /> | |
- public HS105( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- private readonly struct SetRelayStateParameter { | |
- public static readonly SetRelayStateParameter SetOff = new(false); | |
- public static readonly SetRelayStateParameter SetOn = new(true); | |
- | |
- [JsonPropertyName("state")] | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- public bool State { get; } | |
- | |
- public SetRelayStateParameter(bool state) | |
- { | |
- State = state; | |
- } | |
- } | |
- | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextSetRelayState, | |
- parameters: SetRelayStateParameter.SetOn, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextSetRelayState, | |
- parameters: SetRelayStateParameter.SetOff, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- /// <summary> | |
- /// Sets the on/off state of the device according to the parameter <paramref name="newOnOffState" />. | |
- /// </summary> | |
- /// <param name="newOnOffState"> | |
- /// The value that indicates new on/off state to be set. <see langword="true"/> for on, otherwise off. | |
- /// </param> | |
- public ValueTask SetOnOffStateAsync( | |
- bool newOnOffState, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextSetRelayState, | |
- parameters: newOnOffState ? SetRelayStateParameter.SetOn : SetRelayStateParameter.SetOff, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- private readonly struct GetSysInfoRelayStateParameter { | |
- [JsonPropertyName("relay_state")] | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- public bool RelayState { get; init; } | |
- } | |
- | |
- public ValueTask<bool> GetOnOffStateAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextGetSysInfo, | |
- composeResult: static result => JsonSerializer.Deserialize<GetSysInfoRelayStateParameter>(result).RelayState, | |
- cancellationToken: cancellationToken | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130.cs | |
deleted file mode 100644 | |
index 949f691..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130.cs | |
+++ /dev/null | |
@@ -1,273 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-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 Smdn.TPSmartHomeDevices.Kasa.Json; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class KL130 : KasaDevice { | |
- private static readonly JsonEncodedText ModuleTextLightingService = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "smartlife.iot.smartbulb.lightingservice"u8 | |
-#else | |
- "smartlife.iot.smartbulb.lightingservice" | |
-#endif | |
- ); | |
- private static readonly JsonEncodedText MethodTextTransitionLightState = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "transition_light_state"u8 | |
-#else | |
- "transition_light_state" | |
-#endif | |
- ); | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KL130"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(string, IServiceProvider?)" /> | |
- public KL130( | |
- string host, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KL130"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(IPAddress, IServiceProvider?)" /> | |
- public KL130( | |
- IPAddress ipAddress, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KL130"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(PhysicalAddress, IServiceProvider)" /> | |
- public KL130( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KL130"/> class. | |
- /// </summary> | |
- /// <inheritdoc cref="KasaDevice(IDeviceEndPointProvider, IServiceProvider?)" /> | |
- public KL130( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- private static TimeSpan ValidateTransitionPeriod(TimeSpan? value, string paramName) | |
- { | |
- if (value == null) | |
- return TimeSpan.Zero; | |
- | |
- if (value.Value < TimeSpan.Zero) | |
- throw new ArgumentOutOfRangeException(paramName: paramName, actualValue: value, message: "The value for transition period must be zero or positive value."); | |
- | |
- return value.Value; | |
- } | |
- | |
- private readonly record struct SetOnOffStateParameter( | |
- [property: JsonPropertyName("on_off")] | |
- [property: JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- bool OnOff, | |
- [property: JsonPropertyName("transition_period")] | |
- int TransitionPeriodInMilliseconds | |
- ) { | |
- [JsonPropertyName("ignore_default")] | |
- public int IgnoreDefault => 0; // turn on/off with the default configuration | |
- } | |
- | |
- public ValueTask TurnOnAsync( | |
- TimeSpan? transitionPeriod = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextLightingService, | |
- method: MethodTextTransitionLightState, | |
- parameters: new SetOnOffStateParameter( | |
- OnOff: true, // on | |
- TransitionPeriodInMilliseconds: (int)ValidateTransitionPeriod(transitionPeriod, nameof(transitionPeriod)).TotalMilliseconds | |
- ), | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- public ValueTask TurnOffAsync( | |
- TimeSpan? transitionPeriod = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextLightingService, | |
- method: MethodTextTransitionLightState, | |
- parameters: new SetOnOffStateParameter( | |
- OnOff: false, // off | |
- TransitionPeriodInMilliseconds: (int)ValidateTransitionPeriod(transitionPeriod, nameof(transitionPeriod)).TotalMilliseconds | |
- ), | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- /// <summary> | |
- /// Sets the on/off state of the device according to the parameter <paramref name="newOnOffState" />. | |
- /// </summary> | |
- /// <param name="newOnOffState"> | |
- /// The value that indicates new on/off state to be set. <see langword="true"/> for on, otherwise off. | |
- /// </param> | |
- public ValueTask SetOnOffStateAsync( | |
- bool newOnOffState, | |
- TimeSpan? transitionPeriod = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextLightingService, | |
- method: MethodTextTransitionLightState, | |
- parameters: new SetOnOffStateParameter( | |
- OnOff: newOnOffState, | |
- TransitionPeriodInMilliseconds: (int)ValidateTransitionPeriod(transitionPeriod, nameof(transitionPeriod)).TotalMilliseconds | |
- ), | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- private readonly struct GetSysInfoLightStateParameter { | |
- [JsonPropertyName("light_state")] | |
- public KL130LightState LightState { get; init; } | |
- } | |
- | |
- public ValueTask<KL130LightState> GetLightStateAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextGetSysInfo, | |
- composeResult: static result => JsonSerializer.Deserialize<GetSysInfoLightStateParameter>(result).LightState, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- public ValueTask<bool> GetOnOffStateAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextSystem, | |
- method: MethodTextGetSysInfo, | |
- composeResult: static result => JsonSerializer.Deserialize<GetSysInfoLightStateParameter>(result).LightState.IsOn, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- private readonly record struct SetColorTemperatureParameter( | |
- [property: JsonPropertyName("color_temp")] | |
- int ColorTemperature, | |
- [property: JsonPropertyName("brightness")] | |
- int? Brightness, | |
- [property: JsonPropertyName("transition_period")] | |
- int TransitionPeriodInMilliseconds | |
- ) { | |
- [JsonPropertyName("on_off")] | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- public bool OnOff => true; // on | |
- | |
- [JsonPropertyName("ignore_default")] | |
- public int IgnoreDefault => 1; // ignore the default configuration | |
- } | |
- | |
- /// <param name="colorTemperature"> | |
- /// The color temperature in kelvin, in range of 2500~9000[K]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorTemperatureAsync( | |
- int colorTemperature, | |
- int? brightness = null, | |
- TimeSpan? transitionPeriod = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextLightingService, | |
- method: MethodTextTransitionLightState, | |
- new SetColorTemperatureParameter( | |
- ColorTemperature: colorTemperature, // TODO: validation | |
- Brightness: brightness, // TODO: validation | |
- TransitionPeriodInMilliseconds: (int)ValidateTransitionPeriod(transitionPeriod, nameof(transitionPeriod)).TotalMilliseconds | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- private readonly record struct SetColorParameter( | |
- [property: JsonPropertyName("hue")] | |
- int? Hue, | |
- [property: JsonPropertyName("saturation")] | |
- int? Saturation, | |
- [property: JsonPropertyName("brightness")] | |
- int? Brightness, | |
- [property: JsonPropertyName("transition_period")] | |
- int TransitionPeriodInMilliseconds | |
- ) { | |
- [JsonPropertyName("color_temp")] | |
- public int ColorTemperature => 0; // set color with hue and saturation | |
- | |
- [JsonPropertyName("on_off")] | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- public bool OnOff => true; // on | |
- | |
- [JsonPropertyName("ignore_default")] | |
- public int IgnoreDefault => 1; // ignore the default configuration | |
- } | |
- | |
- /// <param name="hue"> | |
- /// The hue of the color in degree, in range of 0~360[°]. | |
- /// </param> | |
- /// <param name="saturation"> | |
- /// The saturation of the color in percent value, in range of 0~100[%]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorAsync( | |
- int hue, | |
- int saturation, | |
- int? brightness = null, | |
- TimeSpan? transitionPeriod = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync( | |
- module: ModuleTextLightingService, | |
- method: MethodTextTransitionLightState, | |
- new SetColorParameter( | |
- Hue: hue, // TODO: validation | |
- Saturation: saturation, // TODO: validation | |
- Brightness: brightness, // TODO: validation | |
- TransitionPeriodInMilliseconds: (int)ValidateTransitionPeriod(transitionPeriod, nameof(transitionPeriod)).TotalMilliseconds | |
- ), | |
- cancellationToken | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130LightState.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130LightState.cs | |
deleted file mode 100644 | |
index d7cc5f6..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KL130LightState.cs | |
+++ /dev/null | |
@@ -1,55 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
-using System.Diagnostics.CodeAnalysis; | |
-#endif | |
-using System.Text.Json.Serialization; | |
-using Smdn.TPSmartHomeDevices.Kasa.Json; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public readonly struct KL130LightState { | |
- [JsonPropertyName("on_off")] | |
- [JsonConverter(typeof(KasaNumericalBooleanJsonConverter))] | |
- public bool IsOn { get; init; } | |
- | |
- /// <summary>Gets the current mode string of the light bulb.</summary> | |
- /// <value>If light is off, the value will be <see langword="null"/>.</value> | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
- [MemberNotNullWhenAttribute(true, nameof(IsOn))] | |
-#endif | |
- [JsonPropertyName("mode")] | |
- public string? Mode { get; init; } | |
- | |
- /// <summary>Gets the current hue value of the light bulb.</summary> | |
- /// <value>If light is off, the value will be <see langword="null"/>.</value> | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
- [MemberNotNullWhenAttribute(true, nameof(IsOn))] | |
-#endif | |
- [JsonPropertyName("hue")] | |
- public int? Hue { get; init; } | |
- | |
- /// <summary>Gets the current saturation value of the light bulb.</summary> | |
- /// <value>If light is off, the value will be <see langword="null"/>.</value> | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
- [MemberNotNullWhenAttribute(true, nameof(IsOn))] | |
-#endif | |
- [JsonPropertyName("saturation")] | |
- public int? Saturation { get; init; } | |
- | |
- /// <summary>Gets the current color temperature value of the light bulb.</summary> | |
- /// <value>If light is off, the value will be <see langword="null"/>.</value> | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
- [MemberNotNullWhenAttribute(true, nameof(IsOn))] | |
-#endif | |
- [JsonPropertyName("color_temp")] | |
- public int? ColorTemperature { get; init; } | |
- | |
- /// <summary>Gets the current brightness value of the light bulb.</summary> | |
- /// <value>If light is off, the value will be <see langword="null"/>.</value> | |
-#if SYSTEM_DIAGNOSTICS_CODEANALYSIS_MEMBERNOTNULLWHENATTRIBUTE | |
- [MemberNotNullWhenAttribute(true, nameof(IsOn))] | |
-#endif | |
- [JsonPropertyName("brightness")] | |
- public int? Brightness { get; init; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.Create.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.Create.cs | |
deleted file mode 100644 | |
index e7e5f2e..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.Create.cs | |
+++ /dev/null | |
@@ -1,51 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-#pragma warning disable IDE0040 | |
-partial class KasaDevice { | |
-#pragma warning restore IDE0040 | |
- /// <inheritdoc cref="KasaDevice(string, IServiceProvider?)" /> | |
- public static KasaDevice Create( | |
- string host, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="KasaDevice(IPAddress, IServiceProvider?)" /> | |
- public static KasaDevice Create( | |
- IPAddress ipAddress, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="KasaDevice(PhysicalAddress, IServiceProvider)" /> | |
- public static KasaDevice Create( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- => new( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="KasaDevice(IDeviceEndPointProvider, IServiceProvider?)" /> | |
- public static KasaDevice Create( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- serviceProvider: serviceProvider | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.cs | |
deleted file mode 100644 | |
index 854662c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDevice.cs | |
+++ /dev/null | |
@@ -1,396 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Buffers; | |
-#if SYSTEM_DIAGNOSTICS_UNREACHABLEEXCEPTION | |
-using System.Diagnostics; | |
-#endif | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Text.Json; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public partial class KasaDevice : IDisposable { | |
- protected static readonly JsonEncodedText ModuleTextSystem = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "system"u8 | |
-#else | |
- "system" | |
-#endif | |
- ); | |
- protected static readonly JsonEncodedText MethodTextGetSysInfo = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "get_sysinfo"u8 | |
-#else | |
- "get_sysinfo" | |
-#endif | |
- ); | |
- | |
- protected readonly struct NullParameter { } | |
- | |
- private IDeviceEndPointProvider? deviceEndPointProvider; // if null, it indicates a 'disposed' state. | |
- protected bool IsDisposed => deviceEndPointProvider is null; | |
- | |
- private readonly KasaClientExceptionHandler exceptionHandler; | |
- private readonly ArrayBufferWriter<byte> buffer = new(initialCapacity: KasaClient.DefaultBufferCapacity); | |
- private readonly IServiceProvider? serviceProvider; | |
- | |
- private KasaClient? client; | |
- | |
- public bool IsConnected { | |
- get { | |
- ThrowIfDisposed(); | |
- return client is not null && client.IsConnected; | |
- } | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KasaDevice"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <param name="host"> | |
- /// A <see cref="string"/> that holds the host name or IP address string, representing the device endpoint. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// </param> | |
- protected KasaDevice( | |
- string host, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- deviceEndPointProvider: KasaDeviceEndPointProvider.Create(host), | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KasaDevice"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <param name="ipAddress"> | |
- /// A <see cref="IPAddress"/> that holds the IP address representing the device end point. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// </param> | |
- protected KasaDevice( | |
- IPAddress ipAddress, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- deviceEndPointProvider: KasaDeviceEndPointProvider.Create(ipAddress), | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KasaDevice"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <param name="macAddress"> | |
- /// A <see cref="PhysicalAddress"/> that holds the MAC address representing the device end point. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> must be registered to create an end point from the <paramref name="macAddress"/>. | |
- /// </param> | |
- /// <exception cref="InvalidOperationException">No service for type <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> has been registered for <paramref name="serviceProvider"/>.</exception> | |
- protected KasaDevice( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: KasaDeviceEndPointProvider.Create(macAddress, serviceProvider), | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="KasaDevice"/> class. | |
- /// </summary> | |
- /// <param name="deviceEndPointProvider"> | |
- /// A <see cref="IDeviceEndPointProvider"/> that provides the device end point. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// </param> | |
- /// <exception cref="InvalidOperationException">No service for type <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> has been registered for <paramref name="serviceProvider"/>.</exception> | |
- /// <exception cref="ArgumentNullException"> | |
- /// <paramref name="deviceEndPointProvider"/> is <see langword="null"/>. | |
- /// </exception> | |
- protected KasaDevice( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- { | |
- this.deviceEndPointProvider = deviceEndPointProvider ?? throw new ArgumentNullException(nameof(deviceEndPointProvider)); | |
- this.serviceProvider = serviceProvider; | |
- | |
- exceptionHandler = serviceProvider?.GetService<KasaClientExceptionHandler>() ?? KasaClientExceptionHandler.Default; | |
- } | |
- | |
- public void Dispose() | |
- { | |
- Dispose(true); | |
- GC.SuppressFinalize(this); | |
- } | |
- | |
- protected virtual void Dispose(bool disposing) | |
- { | |
- if (!disposing) | |
- return; | |
- | |
- deviceEndPointProvider = null; // mark as disposed | |
- | |
- client?.Dispose(); | |
- client = null; | |
- } | |
- | |
- private void ThrowIfDisposed() | |
- { | |
- if (IsDisposed) | |
- throw new ObjectDisposedException(GetType().FullName); | |
- } | |
- | |
- public ValueTask<EndPoint> ResolveEndPointAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- ThrowIfDisposed(); | |
- | |
- return cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled<EndPoint>(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled<EndPoint>(cancellationToken) | |
-#endif | |
- : deviceEndPointProvider.ResolveOrThrowAsync( | |
- defaultPort: KasaClient.DefaultPort, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- protected ValueTask SendRequestAsync<TMethodParameter>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameters, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- if (parameters is null) | |
- throw new ArgumentNullException(nameof(parameters)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled(cancellationToken) | |
-#endif | |
- : SendRequestAsyncCore( | |
- module: module, | |
- method: method, | |
- parameters: parameters, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodResult>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- Func<JsonElement, TMethodResult> composeResult, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- if (composeResult is null) | |
- throw new ArgumentNullException(nameof(composeResult)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled<TMethodResult>(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled<TMethodResult>(cancellationToken) | |
-#endif | |
- : SendRequestAsyncCore<NullParameter, TMethodResult>( | |
- module: module, | |
- method: method, | |
- parameters: default, | |
- composeResult: composeResult, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- protected ValueTask<TMethodResult> SendRequestAsync<TMethodParameter, TMethodResult>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameters, | |
- Func<JsonElement, TMethodResult> composeResult, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- if (parameters is null) | |
- throw new ArgumentNullException(nameof(parameters)); | |
- if (composeResult is null) | |
- throw new ArgumentNullException(nameof(composeResult)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled<TMethodResult>(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled<TMethodResult>(cancellationToken) | |
-#endif | |
- : SendRequestAsyncCore( | |
- module: module, | |
- method: method, | |
- parameters: parameters, | |
- composeResult: composeResult, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- private async ValueTask SendRequestAsyncCore<TMethodParameter>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameters, | |
- CancellationToken cancellationToken | |
- ) | |
- => await SendRequestAsyncCore<TMethodParameter, None /* as an alternative to System.Void */>( | |
- module: module, | |
- method: method, | |
- parameters: parameters, | |
- composeResult: static _ => default, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- private async ValueTask<TMethodResult> SendRequestAsyncCore<TMethodParameter, TMethodResult>( | |
- JsonEncodedText module, | |
- JsonEncodedText method, | |
- TMethodParameter parameters, | |
- Func<JsonElement, TMethodResult> composeResult, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- const int maxAttempts = 5; | |
- var delay = TimeSpan.Zero; | |
- EndPoint? endPoint = null; | |
- | |
- for (var attempt = 0; attempt < maxAttempts; attempt++) { | |
- if (TimeSpan.Zero < delay) | |
- await Task.Delay(delay, cancellationToken).ConfigureAwait(false); | |
- | |
- if (endPoint is null) { | |
- endPoint = await ResolveEndPointAsync(cancellationToken).ConfigureAwait(false); | |
- | |
- if (client is not null && !client.EndPoint.Equals(endPoint)) { | |
- // endpoint has changed, recreate client with new endpoint | |
- client.Logger?.LogInformation($"Endpoint has changed: {client.EndPoint} -> {endPoint}"); | |
- client.Dispose(); | |
- client = null; | |
- } | |
- } | |
- | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- client ??= new KasaClient( | |
- endPoint: endPoint, | |
- buffer: buffer, | |
- logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger(GenerateLoggerCategoryName()) | |
- ); | |
- | |
- string GenerateLoggerCategoryName() | |
- => deviceEndPointProvider is IDynamicDeviceEndPointProvider | |
- ? $"{nameof(KasaClient)}({endPoint}, {deviceEndPointProvider})" | |
- : $"{nameof(KasaClient)}({endPoint})"; | |
- | |
- try { | |
- return await client.SendAsync( | |
- module: module, | |
- method: method, | |
- parameter: parameters, | |
- composeResult: composeResult, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- } | |
- catch (Exception ex) { | |
- // OperationCanceledException and TaskCanceledException due to a cancel | |
- // request triggered by a given CancellationToken must not be handled | |
- // by exception handler, just rethrow instead. | |
- var handling = ( | |
- ex is OperationCanceledException exOperationCanceled && | |
- exOperationCanceled.CancellationToken.Equals(cancellationToken) | |
- ) | |
- ? KasaClientExceptionHandling.Throw | |
- : exceptionHandler.DetermineHandling(this, ex, attempt, client.Logger); | |
- | |
- static void LogRequest(ILogger logger, JsonEncodedText mod, JsonEncodedText meth, TMethodParameter param) | |
- => logger.LogError($"{{{mod}:{{{meth}:{{{JsonSerializer.Serialize(param)}}}}}}}"); | |
- | |
- client.Logger?.LogTrace( | |
- "Exception handling for {TypeOfException}: {ExceptionHandling}", | |
- ex.GetType().FullName, | |
- handling | |
- ); | |
- | |
- if (client.Logger is not null && !handling.ShouldRetry) | |
- LogRequest(client.Logger, module, method, parameters); | |
- | |
- if (handling.ShouldInvalidateEndPoint) { | |
- if (deviceEndPointProvider is IDynamicDeviceEndPointProvider dynamicEndPoint) { | |
- // mark end point as invalid to have the end point refreshed or rescanned | |
- dynamicEndPoint.InvalidateEndPoint(); | |
- | |
- endPoint = null; // should resolve end point | |
- } | |
- else { | |
- // disallow retry | |
- handling = handling with { ShouldRetry = false }; | |
- } | |
- } | |
- | |
- if (handling.ShouldRetry) { | |
- /* | |
- * retry | |
- */ | |
- delay = handling.RetryAfter; | |
- | |
- if (handling.ShouldReconnect) { | |
- client.Dispose(); | |
- client = null; | |
- } | |
- | |
- continue; | |
- } | |
- | |
- /* | |
- * rethrow | |
- */ | |
- client.Dispose(); | |
- client = null; | |
- | |
- throw; | |
- } // try | |
- } // for | |
- | |
-#if SYSTEM_DIAGNOSTICS_UNREACHABLEEXCEPTION | |
- throw new UnreachableException(); | |
-#else | |
- throw new NotImplementedException("unreachable"); | |
-#endif | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDeviceEndPointProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDeviceEndPointProvider.cs | |
deleted file mode 100644 | |
index 1c029da..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDeviceEndPointProvider.cs | |
+++ /dev/null | |
@@ -1,28 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public static class KasaDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(string host) | |
- => DeviceEndPointProvider.Create(host, KasaClient.DefaultPort); | |
- | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) | |
- => DeviceEndPointProvider.Create(ipAddress, KasaClient.DefaultPort); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- => DeviceEndPointProvider.Create(macAddress, KasaClient.DefaultPort, serviceProvider); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- IDeviceEndPointFactory<PhysicalAddress> endPointFactory | |
- ) | |
- => DeviceEndPointProvider.Create(macAddress, KasaClient.DefaultPort, endPointFactory); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDisconnectedException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDisconnectedException.cs | |
deleted file mode 100644 | |
index ebd8161..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaDisconnectedException.cs | |
+++ /dev/null | |
@@ -1,21 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class KasaDisconnectedException : KasaProtocolException { | |
- public KasaDisconnectedException( | |
- string message, | |
- EndPoint deviceEndPoint, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- deviceEndPoint: deviceEndPoint, | |
- innerException: innerException | |
- ) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaErrorResponseException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaErrorResponseException.cs | |
deleted file mode 100644 | |
index 0ca6785..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaErrorResponseException.cs | |
+++ /dev/null | |
@@ -1,27 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Net; | |
-using Smdn.TPSmartHomeDevices.Kasa.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class KasaErrorResponseException : KasaUnexpectedResponseException { | |
- public ErrorCode ErrorCode { get; } | |
- | |
- public KasaErrorResponseException( | |
- EndPoint deviceEndPoint, | |
- string requestModule, | |
- string requestMethod, | |
- ErrorCode errorCode | |
- ) | |
- : base( | |
- message: $"Request '{requestModule}:{requestMethod}' failed with error code {(int)errorCode}. (Device end point: {deviceEndPoint})", | |
- deviceEndPoint: deviceEndPoint, | |
- requestModule: requestModule, | |
- requestMethod: requestMethod, | |
- innerException: null | |
- ) | |
- { | |
- ErrorCode = errorCode; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaIncompleteResponseException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaIncompleteResponseException.cs | |
deleted file mode 100644 | |
index 954aa05..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaIncompleteResponseException.cs | |
+++ /dev/null | |
@@ -1,25 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class KasaIncompleteResponseException : KasaUnexpectedResponseException { | |
- public KasaIncompleteResponseException( | |
- string message, | |
- EndPoint deviceEndPoint, | |
- string requestModule, | |
- string requestMethod, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- deviceEndPoint: deviceEndPoint, | |
- requestModule: requestModule, | |
- requestMethod: requestMethod, | |
- innerException: innerException | |
- ) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaProtocolException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaProtocolException.cs | |
deleted file mode 100644 | |
index a0e4b91..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaProtocolException.cs | |
+++ /dev/null | |
@@ -1,23 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public abstract class KasaProtocolException : InvalidOperationException { | |
- public EndPoint DeviceEndPoint { get; } | |
- | |
- protected KasaProtocolException( | |
- string message, | |
- EndPoint deviceEndPoint, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- innerException: innerException | |
- ) | |
- { | |
- DeviceEndPoint = deviceEndPoint; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaUnexpectedResponseException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaUnexpectedResponseException.cs | |
deleted file mode 100644 | |
index c3d54f4..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Kasa/KasaUnexpectedResponseException.cs | |
+++ /dev/null | |
@@ -1,28 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Kasa; | |
- | |
-public class KasaUnexpectedResponseException : KasaProtocolException { | |
- public string RequestModule { get; } | |
- public string RequestMethod { get; } | |
- | |
- public KasaUnexpectedResponseException( | |
- string message, | |
- EndPoint deviceEndPoint, | |
- string requestModule, | |
- string requestMethod, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- deviceEndPoint: deviceEndPoint, | |
- innerException: innerException | |
- ) | |
- { | |
- RequestModule = requestModule; | |
- RequestMethod = requestMethod; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs | |
deleted file mode 100644 | |
index cb398bd..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs | |
+++ /dev/null | |
@@ -1,11 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Text.Json; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-public interface ITapoCredential : IDisposable { | |
- void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
- void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialIdentity.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialIdentity.cs | |
deleted file mode 100644 | |
index 224a5fd..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialIdentity.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-public interface ITapoCredentialIdentity { | |
- string Name { get; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs | |
deleted file mode 100644 | |
index d4707cf..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-public interface ITapoCredentialProvider { | |
- ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentialUtils.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentialUtils.cs | |
deleted file mode 100644 | |
index 9f42fbf..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentialUtils.cs | |
+++ /dev/null | |
@@ -1,121 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Buffers; | |
-using System.Security.Cryptography; | |
-using System.Text; | |
-using Smdn.Formats; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-/// <remarks> | |
-/// This implementation is based on and ported from the following implementation: <see href="https://github.com/fishbigger/TapoP100">fishbigger/TapoP100</see>. | |
-/// </remarks> | |
-public static class TapoCredentialUtils { | |
-#if SYSTEM_SECURITY_CRYPTOGRAPHY_SHA1_HASHSIZEINBYTES | |
- private const int SHA1HashSizeInBytes = SHA1.HashSizeInBytes; | |
-#else | |
- private const int SHA1HashSizeInBytes = 160/*bits*/ / 8; | |
-#endif | |
- public const int HexSHA1HashSizeInBytes = SHA1HashSizeInBytes * 2; // byte array -> hex byte array | |
- | |
- /// <summary> | |
- /// Hash the string passed by the <paramref name="str"/> with SHA-1 algorithm and converts to the base64 format used for authentication of Tapo devices. | |
- /// </summary> | |
- /// <param name="str">The string to convert.</param> | |
- /// <returns>The <see cref="string"/> containing the result of the conversion.</returns> | |
- public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) | |
- { | |
- byte[]? bytes = null; | |
- | |
- try { | |
- // string -> UTF-8 byte array | |
- var length = Encoding.UTF8.GetByteCount(str); | |
- | |
- bytes = ArrayPool<byte>.Shared.Rent(length); | |
- | |
- Encoding.UTF8.GetBytes(str, bytes); | |
- | |
- // UTF-8 byte array -> hex SHA-1 hash byte array | |
- Span<byte> hexSHA1Hash = stackalloc byte[HexSHA1HashSizeInBytes]; | |
- | |
- if (!TryConvertToHexSHA1Hash(bytes.AsSpan(0, length), hexSHA1Hash, out _)) | |
- throw new InvalidOperationException("failed to convert hex SHA-1 hash"); | |
- | |
- // hex SHA-1 hash byte array -> base64 string | |
- return Convert.ToBase64String(hexSHA1Hash, Base64FormattingOptions.None); | |
- } | |
- finally { | |
- if (bytes is not null) | |
- ArrayPool<byte>.Shared.Return(bytes, clearArray: true); | |
- } | |
- } | |
- | |
- /// <summary> | |
- /// Attempts to convert the UTF-8 string passed by the <paramref name="input"/> to the SHA-1 hash represented in the hexadecimal format (base16). | |
- /// </summary> | |
- /// <param name="input">The UTF-8 string to convert.</param> | |
- /// <param name="destination">The buffer to receive the converted value.</param> | |
- /// <param name="bytesWritten">When this method returns, the total number of bytes written into destination.</param> | |
- /// <returns><see langword="false"/> if <paramref name="destination"/> is too small to hold the calculated hash, <see langword="true"/> otherwise.</returns> | |
- public static bool TryConvertToHexSHA1Hash( | |
- ReadOnlySpan<byte> input, | |
- Span<byte> destination, | |
- out int bytesWritten | |
- ) | |
- { | |
- bytesWritten = 0; | |
- | |
- if (destination.Length < HexSHA1HashSizeInBytes) | |
- return false; | |
- | |
- Span<byte> sha1hash = stackalloc byte[SHA1HashSizeInBytes]; | |
- | |
- try { | |
-#if SYSTEM_SECURITY_CRYPTOGRAPHY_SHA1_TRYHASHDATA | |
- if (!SHA1.TryHashData(input, sha1hash, out var bytesWrittenSHA1)) | |
- return false; // destination too short | |
-#else | |
-#pragma warning disable CA5350 | |
- using var sha1 = SHA1.Create(); | |
-#pragma warning restore CA5350 | |
- | |
- if (!sha1.TryComputeHash(input, sha1hash, out var bytesWrittenSHA1)) | |
- return false; // destination too short | |
-#endif | |
- | |
- if (bytesWrittenSHA1 != SHA1HashSizeInBytes) | |
- return false; // unexpected state | |
- | |
- /* | |
- * SHA-1 hash byte array -> hex byte array | |
- */ | |
- if (!Hexadecimal.TryEncodeLowerCase(sha1hash, destination, out bytesWritten)) | |
- return false; // destination too short | |
- | |
- return true; | |
- } | |
- finally { | |
- sha1hash.Clear(); | |
- } | |
- } | |
- | |
- public static string ToBase64EncodedString(ReadOnlySpan<char> str) | |
- { | |
- byte[]? bytes = null; | |
- | |
- try { | |
- var length = Encoding.UTF8.GetByteCount(str); | |
- | |
- bytes = ArrayPool<byte>.Shared.Rent(length); | |
- | |
- var bytesWritten = Encoding.UTF8.GetBytes(str, bytes); | |
- | |
- return Convert.ToBase64String(bytes.AsSpan(0, bytesWritten), Base64FormattingOptions.None); | |
- } | |
- finally { | |
- if (bytes is not null) | |
- ArrayPool<byte>.Shared.Return(bytes, clearArray: true); | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoBase64StringJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoBase64StringJsonConverter.cs | |
deleted file mode 100644 | |
index 6bce9ec..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoBase64StringJsonConverter.cs | |
+++ /dev/null | |
@@ -1,26 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Text; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Json; | |
- | |
-internal sealed class TapoBase64StringJsonConverter : JsonConverter<string?> { | |
- public override string? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => reader.TryGetBytesFromBase64(out var base64) && base64 is not null | |
- ? Encoding.UTF8.GetString(base64) | |
- : null; | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- string? value, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotImplementedException(); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoIPAddressJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoIPAddressJsonConverter.cs | |
deleted file mode 100644 | |
index efed510..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Json/TapoIPAddressJsonConverter.cs | |
+++ /dev/null | |
@@ -1,33 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Json; | |
- | |
-internal sealed class TapoIPAddressJsonConverter : JsonConverter<IPAddress> { | |
- public override IPAddress? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- { | |
- var str = reader.GetString(); | |
- | |
- if (str is null) | |
- return null; | |
- | |
- return IPAddress.TryParse(str, out var ret) | |
- ? ret | |
- : null; | |
- } | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- IPAddress value, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotImplementedException(); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ErrorCode.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ErrorCode.cs | |
deleted file mode 100644 | |
index d2f8e0a..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ErrorCode.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public enum ErrorCode { | |
- Success = 0, | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoRequest.cs | |
deleted file mode 100644 | |
index bbc8544..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoRequest.cs | |
+++ /dev/null | |
@@ -1,16 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method => "get_device_info"; | |
- | |
-#pragma warning disable CA1822 | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds => 0L; // DateTimeOffset.Now.ToUnixTimeMilliseconds(); | |
-#pragma warning restore CA1822 | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoResponse.cs | |
deleted file mode 100644 | |
index ecb2f39..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/GetDeviceInfoResponse.cs | |
+++ /dev/null | |
@@ -1,15 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-#pragma warning disable SA1313 | |
- | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct GetDeviceInfoResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- | |
- [JsonPropertyName("result")] | |
- public TapoDeviceInfo Result { get; init; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeRequest.cs | |
deleted file mode 100644 | |
index 90fe240..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeRequest.cs | |
+++ /dev/null | |
@@ -1,30 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct HandshakeRequest : ITapoRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method => "handshake"; | |
- | |
- [JsonPropertyName("params")] | |
- public RequestParameters Parameters { get; } | |
- | |
- public HandshakeRequest(string key) | |
- { | |
- Parameters = new(key); | |
- } | |
- | |
- public readonly record struct RequestParameters( | |
-#pragma warning disable SA1313 | |
- [property: JsonPropertyName("key")] string Key | |
-#pragma warning restore SA1313 | |
- ) { | |
-#pragma warning disable CA1822 | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds => 0L; // DateTimeOffset.Now.ToUnixTimeMilliseconds(); | |
-#pragma warning restore CA1822 | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeResponse.cs | |
deleted file mode 100644 | |
index 8939a70..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/HandshakeResponse.cs | |
+++ /dev/null | |
@@ -1,19 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-#pragma warning disable SA1313 | |
- | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct HandshakeResponse : ITapoResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- | |
- [JsonPropertyName("result")] | |
- public ResponseResult Result { get; init; } | |
- | |
- public readonly record struct ResponseResult( | |
- [property: JsonPropertyName("key")] string? Key | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughRequest.cs | |
deleted file mode 100644 | |
index 598543d..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughRequest.cs | |
+++ /dev/null | |
@@ -1,6 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public interface ITapoPassThroughRequest : ITapoRequest { | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughResponse.cs | |
deleted file mode 100644 | |
index 93705a5..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoPassThroughResponse.cs | |
+++ /dev/null | |
@@ -1,6 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public interface ITapoPassThroughResponse : ITapoResponse { | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoRequest.cs | |
deleted file mode 100644 | |
index ed9ffda..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoRequest.cs | |
+++ /dev/null | |
@@ -1,11 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public interface ITapoRequest { | |
-/* | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(int.MinValue)] | |
-*/ | |
- string Method { get; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoResponse.cs | |
deleted file mode 100644 | |
index 22eee02..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/ITapoResponse.cs | |
+++ /dev/null | |
@@ -1,10 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public interface ITapoResponse { | |
-/* | |
- [JsonPropertyName("error_code")] | |
-*/ | |
- public ErrorCode ErrorCode { get; init; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs | |
deleted file mode 100644 | |
index a26abeb..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs | |
+++ /dev/null | |
@@ -1,67 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-#pragma warning disable IDE0040 | |
-partial struct LoginDeviceRequest { | |
-#pragma warning restore IDE0040 | |
- private static readonly JsonEncodedText PropertyNamePassword = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "password"u8 | |
-#else | |
- "password" | |
-#endif | |
- ); | |
- private static readonly JsonEncodedText PropertyNameUsername = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "username"u8 | |
-#else | |
- "username" | |
-#endif | |
- ); | |
- | |
- internal sealed class TapoCredentialJsonConverter : JsonConverter<ITapoCredentialProvider> { | |
- private readonly ITapoCredentialIdentity? identity; | |
- | |
- internal TapoCredentialJsonConverter(ITapoCredentialIdentity? identity) | |
- { | |
- this.identity = identity; | |
- } | |
- | |
- public override ITapoCredentialProvider? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotSupportedException(); | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- ITapoCredentialProvider value, | |
- JsonSerializerOptions options | |
- ) | |
- { | |
- using var credential = value.GetCredential(identity); | |
- | |
- if (credential is null) | |
- throw new InvalidOperationException($"Could not get a credential for an identity '{identity?.Name ?? "(null)"}'"); | |
- | |
- writer.WriteStartObject(); | |
- | |
- writer.WritePropertyName(PropertyNamePassword); | |
- | |
- credential.WritePasswordPropertyValue(writer); | |
- | |
- writer.WritePropertyName(PropertyNameUsername); | |
- | |
- credential.WriteUsernamePropertyValue(writer); | |
- | |
- writer.WriteEndObject(); | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialMaskingJsonConverter.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialMaskingJsonConverter.cs | |
deleted file mode 100644 | |
index a94e827..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialMaskingJsonConverter.cs | |
+++ /dev/null | |
@@ -1,47 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-#pragma warning disable IDE0040 | |
-partial struct LoginDeviceRequest { | |
-#pragma warning restore IDE0040 | |
- internal sealed class TapoCredentialMaskingJsonConverter : JsonConverter<ITapoCredentialProvider> { | |
- private static readonly JsonEncodedText PropertyValueMaskedCredential = JsonEncodedText.Encode( | |
-#if LANG_VERSION_11_OR_GREATER | |
- "****"u8 | |
-#else | |
- "****" | |
-#endif | |
- ); | |
- | |
- public static readonly JsonConverter<ITapoCredentialProvider> Instance = new TapoCredentialMaskingJsonConverter(); | |
- | |
- private TapoCredentialMaskingJsonConverter() | |
- { | |
- } | |
- | |
- public override ITapoCredentialProvider? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => throw new NotSupportedException(); | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- ITapoCredentialProvider value, | |
- JsonSerializerOptions options | |
- ) | |
- { | |
- writer.WriteStartObject(); | |
- writer.WriteString(PropertyNamePassword, PropertyValueMaskedCredential); | |
- writer.WriteString(PropertyNameUsername, PropertyValueMaskedCredential); | |
- writer.WriteEndObject(); | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.cs | |
deleted file mode 100644 | |
index 56ff224..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.cs | |
+++ /dev/null | |
@@ -1,25 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly partial struct LoginDeviceRequest : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method => "login_device"; | |
- | |
- [JsonPropertyName("params")] | |
- public ITapoCredentialProvider Parameters { get; } | |
- | |
-#pragma warning disable CA1822 | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds => 0L; // DateTimeOffset.Now.ToUnixTimeMilliseconds(); | |
-#pragma warning restore CA1822 | |
- | |
- public LoginDeviceRequest(ITapoCredentialProvider credential) | |
- { | |
- Parameters = credential; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceResponse.cs | |
deleted file mode 100644 | |
index ec5c298..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceResponse.cs | |
+++ /dev/null | |
@@ -1,19 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-#pragma warning disable SA1313 | |
- | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct LoginDeviceResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- | |
- [JsonPropertyName("result")] | |
- public ResponseResult Result { get; init; } | |
- | |
- public readonly record struct ResponseResult( | |
- [property: JsonPropertyName("token")] string Token | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughInvalidPaddingException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughInvalidPaddingException.cs | |
deleted file mode 100644 | |
index a851592..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughInvalidPaddingException.cs | |
+++ /dev/null | |
@@ -1,18 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public class SecurePassThroughInvalidPaddingException : SystemException { | |
- public SecurePassThroughInvalidPaddingException( | |
- string message, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- innerException: innerException | |
- ) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs | |
deleted file mode 100644 | |
index a393794..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs | |
+++ /dev/null | |
@@ -1,233 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.IO; | |
-using System.Reflection; | |
-using System.Security.Cryptography; | |
-using System.Text; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
- | |
-using Microsoft.Extensions.Logging; | |
- | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-/// <remarks> | |
-/// This implementation is based on and ported from the following implementation: <see href="https://github.com/fishbigger/TapoP100">fishbigger/TapoP100</see>. | |
-/// </remarks> | |
-public sealed class SecurePassThroughJsonConverterFactory : | |
- JsonConverterFactory, | |
- IDisposable, | |
- SecurePassThroughJsonConverterFactory.IPassThroughObjectJsonConverter | |
-{ | |
- private interface IPassThroughObjectJsonConverter { | |
- void WriteEncryptedValue<TValue>( | |
- Utf8JsonWriter writer, | |
- TValue value | |
- ); | |
- TValue ReadDecryptedValue<TValue>( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert | |
- ); | |
- } | |
- | |
- private ICryptoTransform? encryptorForPassThroughRequest; | |
- private ICryptoTransform? decryptorForPassThroughResponse; | |
- | |
- // used for avoiding JsonSerializer to (de)serialize recursively | |
- private readonly JsonSerializerOptions jsonSerializerOptionsForPassThroughMessage; | |
- private readonly JsonSerializerOptions? jsonSerializerOptionsForPassThroughMessageLogger; | |
- | |
- private readonly ILogger? logger; | |
- private bool disposed; | |
- | |
- public SecurePassThroughJsonConverterFactory( | |
- ITapoCredentialIdentity? identity, | |
- ICryptoTransform? encryptorForPassThroughRequest, | |
- ICryptoTransform? decryptorForPassThroughResponse, | |
- JsonSerializerOptions? baseJsonSerializerOptionsForPassThroughMessage, | |
- ILogger? logger = null | |
- ) | |
- { | |
- this.encryptorForPassThroughRequest = encryptorForPassThroughRequest; | |
- this.decryptorForPassThroughResponse = decryptorForPassThroughResponse; | |
- this.logger = logger; | |
- | |
- jsonSerializerOptionsForPassThroughMessage = baseJsonSerializerOptionsForPassThroughMessage is null | |
- ? new() | |
- : new(baseJsonSerializerOptionsForPassThroughMessage); | |
- jsonSerializerOptionsForPassThroughMessage.Converters.Add( | |
- new LoginDeviceRequest.TapoCredentialJsonConverter(identity: identity) | |
- ); | |
- | |
- if (logger is not null) { | |
- jsonSerializerOptionsForPassThroughMessageLogger = baseJsonSerializerOptionsForPassThroughMessage is null | |
- ? new() | |
- : new(baseJsonSerializerOptionsForPassThroughMessage); | |
- jsonSerializerOptionsForPassThroughMessageLogger.Converters.Add( | |
- LoginDeviceRequest.TapoCredentialMaskingJsonConverter.Instance // mask credentials for logger output | |
- ); | |
- } | |
- } | |
- | |
- public void Dispose() | |
- { | |
- encryptorForPassThroughRequest?.Dispose(); | |
- encryptorForPassThroughRequest = null; | |
- | |
- decryptorForPassThroughResponse?.Dispose(); | |
- decryptorForPassThroughResponse = null; | |
- | |
- disposed = true; | |
- } | |
- | |
- private Stream CreateEncryptingStream(Stream stream) | |
- => disposed | |
- ? throw new ObjectDisposedException(GetType().FullName) | |
- : new CryptoStream( | |
- stream: stream ?? throw new ArgumentNullException(nameof(stream)), | |
- transform: encryptorForPassThroughRequest ?? throw new NotSupportedException("encryption not supported"), | |
- mode: CryptoStreamMode.Write, | |
- leaveOpen: true | |
- ); | |
- | |
- private Stream CreateDecryptingStream(Stream stream) | |
- => disposed | |
- ? throw new ObjectDisposedException(GetType().FullName) | |
- : new CryptoStream( | |
- stream: stream ?? throw new ArgumentNullException(nameof(stream)), | |
- transform: decryptorForPassThroughResponse ?? throw new NotSupportedException("decryption not supported"), | |
- mode: CryptoStreamMode.Read, | |
- leaveOpen: true | |
- ); | |
- | |
- public override bool CanConvert(Type typeToConvert) | |
- => | |
- typeof(ITapoPassThroughRequest).IsAssignableFrom(typeToConvert) || | |
- typeof(ITapoPassThroughResponse).IsAssignableFrom(typeToConvert); | |
- | |
- public override JsonConverter? CreateConverter( | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => CanConvert(typeToConvert) | |
- ? (JsonConverter)Activator.CreateInstance( | |
- type: typeof(PassThroughObjectJsonConverter<>).MakeGenericType(typeToConvert), | |
- bindingAttr: BindingFlags.Instance | BindingFlags.Public, | |
- binder: null, | |
- args: new object[] { this }, | |
- culture: null | |
- )! | |
- // should throw exception? | |
- // throw new NotSupportedException($"unexpected type to convert: {typeToConvert.FullName}"); | |
- : null; | |
- | |
- void IPassThroughObjectJsonConverter.WriteEncryptedValue<TValue>( | |
- Utf8JsonWriter writer, | |
- TValue value | |
- ) | |
- { | |
- logger?.LogTrace( | |
- "PassThroughRequest: {RawJson} ({TypeFullName})", | |
- JsonSerializer.Serialize(value: value, options: jsonSerializerOptionsForPassThroughMessageLogger), | |
- typeof(TValue).FullName | |
- ); | |
- | |
- var stream = new MemoryStream(capacity: 256); // TODO: IMemoryAllocator | |
- | |
- using var encryptingStream = CreateEncryptingStream(stream); | |
- | |
- JsonSerializer.Serialize( | |
- utf8Json: encryptingStream, | |
- value: value, | |
- options: jsonSerializerOptionsForPassThroughMessage | |
- ); | |
- | |
- encryptingStream.Close(); | |
- | |
- if (!stream.TryGetBuffer(out var buffer)) | |
- throw new InvalidOperationException("cannot get buffer from MemoryStream"); | |
- | |
- writer.WriteBase64StringValue(buffer.AsSpan()); | |
- } | |
- | |
- TValue IPassThroughObjectJsonConverter.ReadDecryptedValue<TValue>( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert | |
- ) | |
- { | |
- if (!reader.TryGetBytesFromBase64(out var base64)) | |
- throw new JsonException("could not read base64 string"); | |
- | |
- var cryptographicExceptionThrown = false; | |
- | |
- try { | |
- using var stream = new MemoryStream(base64, writable: false); | |
- using var decryptingStream = CreateDecryptingStream(stream); | |
- | |
- try { | |
- return (TValue?)JsonSerializer.Deserialize( | |
- utf8Json: decryptingStream, | |
- returnType: typeToConvert, | |
- options: jsonSerializerOptionsForPassThroughMessage | |
- ); | |
- } | |
- catch (CryptographicException ex) when (IsInvalidPaddingException(ex)) { | |
- cryptographicExceptionThrown = true; | |
- throw new SecurePassThroughInvalidPaddingException("Invalid padding detected in encrypted JSON", ex); | |
- } | |
- catch (CryptographicException ex) { | |
- cryptographicExceptionThrown = true; | |
- throw; | |
- } | |
- | |
- static bool IsInvalidPaddingException(CryptographicException ex) | |
- { | |
- // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Cryptography/src/Resources/Strings.resx | |
- const string SR_Cryptography_InvalidPadding = "Padding is invalid and cannot be removed."; | |
- | |
- return SR_Cryptography_InvalidPadding.AsSpan().SequenceEqual(ex.Message); // TODO: consider localized message strings | |
- } | |
- } | |
- finally { | |
- if (!cryptographicExceptionThrown && logger is not null) { | |
- using var stream = new MemoryStream(base64, writable: false); | |
- using var decryptingStream = CreateDecryptingStream(stream); | |
- | |
- logger.LogTrace( | |
- "PassThroughResponse: {RawJson} ({TypeFullName})", | |
- new StreamReader(decryptingStream, Encoding.UTF8).ReadToEnd(), | |
- typeof(TValue).FullName | |
- ); | |
- } | |
- } | |
- } | |
- | |
- private class PassThroughObjectJsonConverter<TPassThroughObject> | |
- : JsonConverter<TPassThroughObject> | |
- // where TPassThroughObject : ITapoPassThroughRequest or ITapoPassThroughResponse | |
- { | |
- private readonly IPassThroughObjectJsonConverter converter; | |
- | |
- public PassThroughObjectJsonConverter(IPassThroughObjectJsonConverter converter) | |
- { | |
- this.converter = converter; | |
- } | |
- | |
- public override void Write( | |
- Utf8JsonWriter writer, | |
- TPassThroughObject value, | |
- JsonSerializerOptions options | |
- ) | |
- => converter.WriteEncryptedValue(writer, value); | |
- | |
- public override TPassThroughObject? Read( | |
- ref Utf8JsonReader reader, | |
- Type typeToConvert, | |
- JsonSerializerOptions options | |
- ) | |
- => converter.ReadDecryptedValue<TPassThroughObject>(ref reader, typeToConvert); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs | |
deleted file mode 100644 | |
index 22451b4..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs | |
+++ /dev/null | |
@@ -1,29 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct SecurePassThroughRequest<TPassThroughRequest> : | |
- ITapoRequest | |
- where TPassThroughRequest : ITapoPassThroughRequest | |
-{ | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method => "securePassthrough"; | |
- | |
- [JsonPropertyName("params")] | |
- public RequestParams Params { get; } | |
- | |
- public readonly record struct RequestParams( | |
-#pragma warning disable SA1313 | |
- [property: JsonPropertyName("request")] | |
- TPassThroughRequest PassThroughRequest | |
-#pragma warning restore SA1313 | |
- ); | |
- | |
- public SecurePassThroughRequest(TPassThroughRequest passThroughRequest) | |
- { | |
- Params = new(passThroughRequest); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs | |
deleted file mode 100644 | |
index 697c0a4..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs | |
+++ /dev/null | |
@@ -1,32 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct SecurePassThroughResponse<TPassThroughResponse> : | |
- ITapoResponse | |
- where TPassThroughResponse : ITapoPassThroughResponse | |
-{ | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- | |
- [JsonPropertyName("result")] | |
- public ResponseResult Result { get; init; } | |
- | |
- public SecurePassThroughResponse( | |
- ErrorCode errorCode, | |
- TPassThroughResponse passThroughResponse | |
- ) | |
- { | |
- ErrorCode = errorCode; | |
- Result = new(passThroughResponse); | |
- } | |
- | |
- public readonly record struct ResponseResult( | |
-#pragma warning disable SA1313 | |
- [property: JsonPropertyName("response")] | |
- TPassThroughResponse PassThroughResponse | |
-#pragma warning restore SA1313 | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoRequest.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoRequest.cs | |
deleted file mode 100644 | |
index f89a90d..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoRequest.cs | |
+++ /dev/null | |
@@ -1,31 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct SetDeviceInfoRequest<TParameters> : ITapoPassThroughRequest { | |
- [JsonPropertyName("method")] | |
- [JsonPropertyOrder(0)] | |
- public string Method => "set_device_info"; | |
- | |
-#pragma warning disable CA1822 | |
- [JsonPropertyName("requestTimeMils")] | |
- public long RequestTimeMilliseconds => 0L; // DateTimeOffset.Now.ToUnixTimeMilliseconds(); | |
-#pragma warning restore CA1822 | |
- | |
- [JsonPropertyName("terminalUUID")] | |
- public string TerminalUuid { get; } | |
- | |
- [JsonPropertyName("params")] | |
- public TParameters Parameters { get; } | |
- | |
- public SetDeviceInfoRequest( | |
- string terminalUuid, | |
- TParameters parameters | |
- ) | |
- { | |
- TerminalUuid = terminalUuid; | |
- Parameters = parameters; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoResponse.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoResponse.cs | |
deleted file mode 100644 | |
index 7cced70..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/SetDeviceInfoResponse.cs | |
+++ /dev/null | |
@@ -1,19 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-#pragma warning disable SA1313 | |
-using System.Collections.Generic; | |
-using System.Text.Json.Serialization; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct SetDeviceInfoResponse : ITapoPassThroughResponse { | |
- [JsonPropertyName("error_code")] | |
- public ErrorCode ErrorCode { get; init; } | |
- | |
- [JsonPropertyName("result")] | |
- public ResponseResult Result { get; init; } | |
- | |
- public readonly record struct ResponseResult( | |
- [property: JsonExtensionData] IDictionary<string, object>? ExtraData | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.cs | |
deleted file mode 100644 | |
index fe60a62..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.cs | |
+++ /dev/null | |
@@ -1,237 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net.Http; | |
-using System.Security.Cryptography; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-#pragma warning disable IDE0040 | |
-partial class TapoClient { | |
-#pragma warning restore IDE0040 | |
- private const int KeyExchangeAlgorithmKeySizeInBytes = 128; | |
- | |
- private async ValueTask AuthenticateAsyncCore( | |
- ITapoCredentialIdentity identity, | |
- ITapoCredentialProvider credential, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- var prevSession = session; | |
- | |
- try { | |
- /* | |
- * handshake | |
- */ | |
- logger?.LogDebug("Handshake starting: {EndPointUri}", endPointUri); | |
- | |
- session = await HandshakeAsync(identity, cancellationToken).ConfigureAwait(false); | |
- | |
- logger?.LogDebug("Handshake completed."); | |
- | |
- /* | |
- * login_device | |
- */ | |
- logger?.LogDebug("Login starting: {EndPointUri}", endPointUri); | |
- | |
- var token = await LoginDeviceAsync(credential, cancellationToken).ConfigureAwait(false); | |
- | |
- logger?.LogDebug("Login completed, access token has issued: {Token}", token); | |
- | |
- /* | |
- * session established | |
- */ | |
- session.SetToken(token); | |
- | |
- logger?.LogDebug("Request path and query for the session: '{PathAndQuery}'", session.RequestPathAndQuery); | |
- | |
- logger?.LogInformation( | |
- "Session established: {SessionRequestPathAndQuery}; {SessionIDPrefix}{SessionID}; expires on {ExpiresOn}", | |
- session.RequestPathAndQuery, | |
- TapoSessionCookieUtils.HttpCookiePrefixForSessionId, | |
- session.SessionId, | |
- session.ExpiresOn.ToString("o", provider: null) | |
- ); | |
- | |
- prevSession?.Dispose(); | |
- } | |
- catch { | |
- session?.Dispose(); | |
- session = null; | |
- | |
- throw; | |
- } | |
- } | |
- | |
- private async ValueTask<TapoSession> HandshakeAsync( | |
- ITapoCredentialIdentity? identity, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- using var keyExchangeAlgorithm = RSA.Create(keySizeInBits: KeyExchangeAlgorithmKeySizeInBytes * 8); | |
- | |
- var publicKeyInfoPem = | |
-#if SYSTEM_SECURITY_CRYPTOGRAPHY_ASYMMETRICALGORITHM_EXPORTSUBJECTPUBLICKEYINFOPEM | |
- keyExchangeAlgorithm.ExportSubjectPublicKeyInfoPem(); | |
-#else | |
- AsymmetricAlgorithmShim.ExportSubjectPublicKeyInfoPem(keyExchangeAlgorithm!); | |
-#endif | |
- | |
- logger?.LogTrace("[Handshake] Public key: {PublicKeyInfoPem}", publicKeyInfoPem); | |
- | |
- var baseTimeForExpiration = DateTime.Now; | |
- string base64EncryptedKey; | |
- string? sessionId; | |
- int? sessionTimeout; | |
- | |
- try { | |
- (var response, (sessionId, sessionTimeout)) = await PostPlainTextRequestAsync< | |
- HandshakeRequest, | |
- HandshakeResponse, | |
- (string?, int?) | |
- >( | |
- request: new(key: publicKeyInfoPem), | |
- jsonSerializerOptions: CommonJsonSerializerOptions, | |
- processHttpResponse: ParseSessionCookie, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- if (response.Result.Key is null) { | |
- logger?.LogCritical("Could not exchange the key during handshaking."); | |
- throw new TapoAuthenticationException( | |
- message: $"Could not exchange the key during handshaking with the device at '{endPointUri}'.", | |
- endPoint: endPointUri | |
- ); | |
- } | |
- | |
- base64EncryptedKey = response.Result.Key; | |
- } | |
- catch (TapoErrorResponseException ex) { | |
- logger?.LogCritical("Failed to handshake with error code {ErrorCode}.", (int)ex.ErrorCode); | |
- throw new TapoAuthenticationException( | |
- message: $"Failed to handshake with the device at '{endPointUri}' with error code {(int)ex.ErrorCode}.", | |
- endPoint: endPointUri, | |
- innerException: ex | |
- ); | |
- } | |
- catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException exTimeout) { | |
- logger?.LogCritical("Failed to handshake due to timeout. ({ExceptionMessage})", ex.Message); | |
- throw new TapoAuthenticationException( | |
- message: $"Failed to handshake with the device at '{endPointUri}' due to timeout. ({ex.Message})", | |
- endPoint: endPointUri, | |
- innerException: exTimeout | |
- ); | |
- } | |
- | |
- logger?.LogTrace("[Handshake] Exchanged key: {Key}", base64EncryptedKey); | |
- logger?.LogTrace("[Handshake] Session ID: {SessionId}", sessionId); | |
- logger?.LogTrace("[Handshake] Session timeout: {SessionTimeout}", sessionTimeout); | |
- | |
- var encryptedKey = Convert.FromBase64String(base64EncryptedKey); | |
- | |
- if (encryptedKey.Length != KeyExchangeAlgorithmKeySizeInBytes) { | |
- logger?.LogCritical("Exchanged unexpecting length of key"); | |
- throw new TapoAuthenticationException( | |
- message: $"Exchanged unexpecting length of key from the device at '{endPointUri}'.", | |
- endPoint: endPointUri | |
- ); | |
- } | |
- | |
- var keyBytes = keyExchangeAlgorithm.Decrypt( | |
- data: encryptedKey, | |
- padding: RSAEncryptionPadding.Pkcs1 | |
- ); | |
- var expiresOn = sessionTimeout.HasValue | |
- ? baseTimeForExpiration + TimeSpan.FromMinutes(sessionTimeout.Value) | |
- : DateTime.MaxValue; | |
- | |
- logger?.LogTrace( | |
- "[Handshake] Session expires on: {ExpiresOn}", | |
- expiresOn.ToString("o", provider: null) | |
- ); | |
- logger?.LogTrace( | |
- "[Handshake] Key: {Key}", | |
-#if SYSTEM_CONVERT_TOHEXSTRING | |
- Convert.ToHexString(keyBytes.AsSpan(0, 16)) | |
-#else | |
- Smdn.Formats.Hexadecimal.ToUpperCaseString(keyBytes.AsSpan(0, 16)) | |
-#endif | |
- ); | |
- logger?.LogTrace( | |
- "[Handshake] IV: {IV}", | |
-#if SYSTEM_CONVERT_TOHEXSTRING | |
- Convert.ToHexString(keyBytes.AsSpan(16, 16)) | |
-#else | |
- Smdn.Formats.Hexadecimal.ToUpperCaseString(keyBytes.AsSpan(16, 16)) | |
-#endif | |
- ); | |
- | |
- return new( | |
- identity: identity, | |
- sessionId: sessionId, | |
- expiresOn: expiresOn, | |
- key: keyBytes.AsSpan(0, 16), | |
- iv: keyBytes.AsSpan(16, 16), | |
- baseJsonSerializerOptions: CommonJsonSerializerOptions, | |
- logger: logger | |
- ); | |
- | |
- // TODO: logger | |
- static (string? SessionId, int? SessionTimeout) ParseSessionCookie(HttpResponseMessage response) | |
- => TapoSessionCookieUtils.TryGetCookie(response, out var sessionId, out var sessionTimeout) | |
- ? (sessionId, sessionTimeout) | |
- : default; | |
- } | |
- | |
- private async ValueTask<string> LoginDeviceAsync( | |
- ITapoCredentialProvider credential, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- string token; | |
- | |
- try { | |
- var loginDeviceResponse = await SendRequestAsync<LoginDeviceRequest, LoginDeviceResponse>( | |
- request: new(credential: credential), | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- token = loginDeviceResponse.Result.Token; | |
- } | |
- catch (TapoErrorResponseException ex) { | |
- throw new TapoAuthenticationException( | |
- message: $"Denied to initiate authorized session with the device at '{endPointUri}'. (error code: {(int)ex.ErrorCode})", | |
- endPoint: endPointUri, | |
- innerException: ex | |
- ); | |
- } | |
- catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException exTimeout) { | |
- logger?.LogCritical("Failed to initiate authorized session due to timeout. ({ExceptionMessage})", ex.Message); | |
- throw new TapoAuthenticationException( | |
- message: $"Failed to initiate authorized session with the device at '{endPointUri}' due to timeout. ({ex.Message})", | |
- endPoint: endPointUri, | |
- innerException: exTimeout | |
- ); | |
- } | |
- | |
- if (string.IsNullOrEmpty(token)) { | |
- logger?.LogError("Access token has not been issued."); | |
- throw new TapoAuthenticationException( | |
- message: $"An access token was not issued from the device at '{endPointUri}'.", | |
- endPoint: endPointUri | |
- ); | |
- } | |
- | |
- return token; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs | |
deleted file mode 100644 | |
index 4643604..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs | |
+++ /dev/null | |
@@ -1,156 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Linq; | |
-using System.Net.Http; | |
-using System.Net.Http.Headers; | |
-using System.Net.Http.Json; | |
-using System.Text.Json; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-#pragma warning disable IDE0040 | |
-partial class TapoClient { | |
-#pragma warning restore IDE0040 | |
- private static readonly MediaTypeHeaderValue mediaTypeJson = new(mediaType: "application/json"); | |
- | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- private async ValueTask<TResponse> PostSecurePassThroughRequestAsync<TRequest, TResponse>( | |
- TRequest request, | |
- JsonSerializerOptions jsonSerializerOptions, | |
- CancellationToken cancellationToken | |
- ) | |
- where TRequest : ITapoPassThroughRequest | |
- where TResponse : ITapoPassThroughResponse | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- logger?.LogDebug("Request: {Request}", JsonSerializer.Serialize(request, jsonSerializerOptions)); | |
- | |
- var (securePassThroughResponse, requestUri) = await PostPlainTextRequestAsync< | |
- SecurePassThroughRequest<TRequest>, | |
- SecurePassThroughResponse<TResponse>, | |
- Uri | |
- >( | |
- request: new(passThroughRequest: request), | |
- jsonSerializerOptions: jsonSerializerOptions, | |
- processHttpResponse: static httpResponse => httpResponse.RequestMessage.RequestUri, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- var response = securePassThroughResponse.Result.PassThroughResponse; | |
- | |
- logger?.LogDebug("Respose error code: {ErrorCode}", (int)response.ErrorCode); | |
- | |
- TapoErrorResponseException.ThrowIfError( | |
- requestUri, | |
- request.Method, | |
- response.ErrorCode | |
- ); | |
- | |
- return response; | |
- } | |
- | |
- private async ValueTask<( | |
- TResponse? Response, | |
- THttpResult? HttpResult | |
- )> | |
- PostPlainTextRequestAsync<TRequest, TResponse, THttpResult>( | |
- TRequest request, | |
- JsonSerializerOptions jsonSerializerOptions, | |
- Func<HttpResponseMessage, THttpResult?>? processHttpResponse, | |
- CancellationToken cancellationToken | |
- ) | |
- where TRequest : ITapoRequest | |
- where TResponse : ITapoResponse | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- logger?.LogDebug("HTTP Transaction: Session={SessionId}, Token={Token}", session?.SessionId, session?.Token); | |
- | |
- using var requestContent = JsonContent.Create( | |
- inputValue: request, | |
- mediaType: mediaTypeJson, | |
- options: jsonSerializerOptions | |
- ); | |
- | |
- if (session?.SessionId is not null) { | |
- requestContent.Headers.Add( | |
- "Cookie", | |
- string.Concat(TapoSessionCookieUtils.HttpCookiePrefixForSessionId, session.SessionId) | |
- ); | |
- } | |
- | |
- // Disables 'chunked' transfer encoding | |
- // The HTTP server inside of the Tapo devices does not seem to support 'chunked' transfer encoding. | |
- // To prevent content from being transferred by 'chunked', serialize the content onto a memory buffer | |
- // and ensure the HttpClient to calculate Content-Length before transferring. | |
- // | |
- // ref: | |
- // https://github.com/dotnet/runtime/issues/49357 | |
- // https://github.com/dotnet/runtime/issues/70793 | |
- await requestContent.LoadIntoBufferAsync().ConfigureAwait(false); | |
- | |
- var requestUri = session is null ? TapoSession.RequestPath : session.RequestPathAndQuery; | |
- | |
- logger?.LogTrace("HTTP Request URI: {RequestUri}", requestUri); | |
- logger?.LogTrace( | |
- "HTTP Request headers: {RequestHeaders}", | |
- string.Join(" ", requestContent.Headers.Select(static header => string.Concat(header.Key, ": ", string.Join("; ", header.Value)))) | |
- ); | |
- logger?.LogTrace( | |
- "HTTP Request content: {RequestContent}", | |
- await requestContent.ReadAsStringAsync().ConfigureAwait(false) | |
- ); | |
- | |
- using var httpClient = httpClientFactory.CreateClient( | |
- name: string.Concat(nameof(TapoClient), " (", endPointUri, ")") | |
- ); | |
- | |
- httpClient.BaseAddress = endPointUri; | |
- | |
- if (Timeout.HasValue) | |
- // override timeout value configured by IHttpClientFactory | |
- httpClient.Timeout = Timeout.Value; | |
- | |
- using var httpResponse = await httpClient.PostAsync( | |
- requestUri: requestUri, | |
- content: requestContent, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- var httpResult = processHttpResponse is null | |
- ? default | |
- : processHttpResponse(httpResponse); | |
- | |
- logger?.LogTrace( | |
- "HTTP Response status: {ResponseStatusCode} {ResponseReasonPhrase}", | |
- (int)httpResponse.StatusCode, | |
- httpResponse.ReasonPhrase | |
- ); | |
- logger?.LogTrace( | |
- "HTTP Response headers: {ResponseHeaders}", | |
- string.Join(" ", httpResponse.Content.Headers.Concat(httpResponse.Headers).Select(static header => string.Concat(header.Key, ": ", string.Join("; ", header.Value)))) | |
- ); | |
- logger?.LogTrace("HTTP Response content: {ResponseContent}", await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false)); | |
- | |
- httpResponse.EnsureSuccessStatusCode(); | |
- | |
- var response = await httpResponse.Content.ReadFromJsonAsync<TResponse>( | |
- cancellationToken: cancellationToken, | |
- options: jsonSerializerOptions | |
- ).ConfigureAwait(false); | |
- | |
- TapoErrorResponseException.ThrowIfError( | |
- httpResponse.RequestMessage.RequestUri, | |
- request.Method, | |
- response.ErrorCode | |
- ); | |
- | |
- return (response, httpResult); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.cs | |
deleted file mode 100644 | |
index 0bb2d04..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.cs | |
+++ /dev/null | |
@@ -1,184 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.Http; | |
-using System.Text.Json; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-/// <remarks> | |
-/// This implementation is based on and ported from the following implementation: <see href="https://github.com/fishbigger/TapoP100">fishbigger/TapoP100</see>. | |
-/// </remarks> | |
-public sealed partial class TapoClient : IDisposable { | |
- public const int DefaultPort = 80; // HTTP | |
- | |
- internal static Uri GetEndPointUri(EndPoint endPoint) | |
- { | |
- // 'http://<endPoint.Host>:<endPoint.Port>/' | |
- var uriBuilder = endPoint switch { | |
- null => throw new ArgumentNullException(nameof(endPoint)), | |
- DnsEndPoint dnsEndPoint => new UriBuilder() { | |
- Host = dnsEndPoint.Host, | |
- Port = dnsEndPoint.Port == 0 ? -1 : dnsEndPoint.Port, | |
- }, | |
- IPEndPoint ipEndPoint => new UriBuilder() { | |
- Host = ipEndPoint.Address.ToString(), | |
- Port = ipEndPoint.Port == 0 ? -1 : ipEndPoint.Port, | |
- }, | |
- _ => new UriBuilder() { | |
- Host = endPoint.ToString(), // XXX | |
- Port = -1, | |
- }, | |
- }; | |
- | |
- uriBuilder.Scheme = Uri.UriSchemeHttp; | |
- | |
- return uriBuilder.Uri; | |
- } | |
- | |
- private static readonly JsonSerializerOptions CommonJsonSerializerOptions = new() { | |
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, | |
- | |
-#if false | |
- // disable encoding '+' in base64 strings | |
- // ref: https://github.com/dotnet/runtime/issues/35281 | |
- // Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, | |
-#endif | |
- }; | |
- | |
- /* | |
- * instance members | |
- */ | |
- private bool IsDisposed => httpClientFactory is null; | |
- | |
- private IHttpClientFactory? httpClientFactory; // if null, it indicates a 'disposed' state. | |
- private readonly Uri endPointUri; | |
- private readonly EndPoint endPoint; | |
- private readonly ILogger? logger; | |
- private TapoSession? session; | |
- | |
- public TapoSession? Session { | |
- get { | |
- ThrowIfDisposed(); | |
- return session; | |
- } | |
- } | |
- | |
- public Uri EndPointUri { | |
- get { | |
- ThrowIfDisposed(); | |
- return endPointUri; | |
- } | |
- } | |
- | |
- internal EndPoint EndPoint { | |
- get { | |
- ThrowIfDisposed(); | |
- return endPoint; | |
- } | |
- } | |
- | |
- internal ILogger? Logger { | |
- get { | |
- ThrowIfDisposed(); | |
- return logger; | |
- } | |
- } | |
- | |
- public TapoClient( | |
- EndPoint endPoint, | |
- IHttpClientFactory? httpClientFactory = null, | |
- ILogger? logger = null | |
- ) | |
- { | |
- this.endPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint)); | |
- this.logger = logger; | |
- | |
- endPointUri = GetEndPointUri(endPoint); | |
- | |
- logger?.LogTrace("Device end point: {DeviceEndPointUri}", endPointUri); | |
- | |
- this.httpClientFactory = httpClientFactory ?? TapoHttpClientFactory.Default; | |
- | |
- logger?.LogTrace("IHttpClientFactory: {IHttpClientFactory}", this.httpClientFactory.GetType().FullName); | |
- } | |
- | |
- private void Dispose(bool disposing) | |
- { | |
- if (!disposing) | |
- return; | |
- | |
- session?.Dispose(); | |
- session = null; | |
- | |
- httpClientFactory = null; | |
- } | |
- | |
- public void Dispose() | |
- { | |
- Dispose(true); | |
- GC.SuppressFinalize(this); | |
- } | |
- | |
- private void ThrowIfDisposed() | |
- { | |
- if (IsDisposed) | |
- throw new ObjectDisposedException(GetType().FullName); | |
- } | |
- | |
- public ValueTask AuthenticateAsync( | |
- ITapoCredentialIdentity? identity, | |
- ITapoCredentialProvider credential, | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- if (credential is null) | |
- throw new ArgumentNullException(nameof(credential)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return AuthenticateAsyncCore( | |
- identity, | |
- credential, | |
- cancellationToken | |
- ); | |
- } | |
- | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>( | |
- CancellationToken cancellationToken = default | |
- ) | |
- where TRequest : ITapoPassThroughRequest, new() | |
- where TResponse : ITapoPassThroughResponse | |
- => SendRequestAsync<TRequest, TResponse>( | |
- request: new(), | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- public ValueTask<TResponse> SendRequestAsync<TRequest, TResponse>( | |
- TRequest request, | |
- CancellationToken cancellationToken = default | |
- ) | |
- where TRequest : ITapoPassThroughRequest | |
- where TResponse : ITapoPassThroughResponse | |
- { | |
- if (request is null) | |
- throw new ArgumentNullException(nameof(request)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- if (session is null) | |
- throw new InvalidOperationException("The session for this instance has not been established."); | |
- | |
- return PostSecurePassThroughRequestAsync<TRequest, TResponse>( | |
- request: request, | |
- jsonSerializerOptions: session.SecurePassThroughJsonSerializerOptions, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientDefaultExceptionHandler.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientDefaultExceptionHandler.cs | |
deleted file mode 100644 | |
index 9b4999c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientDefaultExceptionHandler.cs | |
+++ /dev/null | |
@@ -1,104 +0,0 @@ | |
-using System; | |
-using System.Net.Http; | |
-using System.Net.Sockets; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-internal class TapoClientDefaultExceptionHandler : TapoClientExceptionHandler { | |
- public override TapoClientExceptionHandling DetermineHandling( | |
- TapoDevice device, | |
- Exception exception, | |
- int attempt, | |
- ILogger? logger | |
- ) | |
- { | |
- switch (exception) { | |
- case HttpRequestException httpRequestException: | |
- if (httpRequestException.InnerException is SocketException innerSocketException) { | |
- var socketErrorCode = innerSocketException.SocketErrorCode; | |
- | |
- if ( | |
- socketErrorCode is | |
- SocketError.ConnectionRefused or // ECONNREFUSED | |
- SocketError.HostUnreachable or // EHOSTUNREACH | |
- SocketError.NetworkUnreachable // ENETUNREACH | |
- ) { | |
- if (attempt == 0 /* retry just once */) { | |
- // The end point may have changed. | |
- logger?.LogInformation($"Endpoint may have changed ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return TapoClientExceptionHandling.InvalidateEndPointAndRetry; | |
- } | |
- else { | |
- logger?.LogError($"Endpoint unreachable ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return TapoClientExceptionHandling.InvalidateEndPointAndThrow; | |
- } | |
- } | |
- | |
- // The HTTP client may have been invalid due to an exception at the transport layer. | |
- logger?.LogError(innerSocketException, $"Unexpected socket exception ({nameof(SocketError)}: {(int)socketErrorCode} {socketErrorCode})"); | |
- | |
- return TapoClientExceptionHandling.Throw; | |
- } | |
- | |
- return TapoClientExceptionHandling.Throw; | |
- | |
- case SecurePassThroughInvalidPaddingException securePassThroughInvalidPaddingException: | |
- if (attempt == 0 /* retry just once */) { | |
- logger?.LogWarning(securePassThroughInvalidPaddingException.Message); | |
- | |
- // The session might have been in invalid state(?) | |
- return TapoClientExceptionHandling.RetryAfterReconnect; | |
- } | |
- | |
- return TapoClientExceptionHandling.ThrowAsTapoProtocolException; | |
- | |
- case TapoErrorResponseException errorResponseException: | |
- if (attempt == 0 /* retry just once */) { | |
- switch (errorResponseException.ErrorCode) { | |
- case TapoErrorCodes.DeviceBusy: | |
- logger?.LogWarning(errorResponseException.Message); | |
- | |
- // The session might have been in invalid state(?) | |
- return TapoClientExceptionHandling.CreateRetry( | |
- retryAfter: TimeSpan.FromSeconds(2.0), | |
- shouldReconnect: true | |
- ); | |
- | |
- case TapoErrorCodes.RequestParameterError: | |
- logger?.LogWarning(errorResponseException.Message); | |
- return TapoClientExceptionHandling.Throw; | |
- | |
- default: | |
- logger?.LogWarning($"Unexpected error ({nameof(errorResponseException.ErrorCode)}: {(int)errorResponseException.ErrorCode})"); | |
- | |
- // The session might have been in invalid state(?) | |
- return TapoClientExceptionHandling.RetryAfterReconnect; | |
- } | |
- } | |
- | |
- return TapoClientExceptionHandling.Throw; | |
- | |
- case TaskCanceledException taskCanceledException: | |
- if (taskCanceledException.InnerException is TimeoutException) { | |
- if (attempt < 2 /* retry up to 3 times */) { | |
- logger?.LogWarning("Request timed out; {ExceptionMessage}", taskCanceledException.Message); | |
- return TapoClientExceptionHandling.Retry; | |
- } | |
- | |
- logger?.LogError(taskCanceledException, "Request timed out"); | |
- | |
- return TapoClientExceptionHandling.ThrowAsTapoProtocolException; | |
- } | |
- | |
- return TapoClientExceptionHandling.Throw; | |
- | |
- default: | |
- logger?.LogError(exception, $"Unhandled exception ({exception.GetType().FullName})"); | |
- return TapoClientExceptionHandling.Throw; | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandler.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandler.cs | |
deleted file mode 100644 | |
index e74f149..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandler.cs | |
+++ /dev/null | |
@@ -1,17 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using Microsoft.Extensions.Logging; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public abstract class TapoClientExceptionHandler { | |
- protected internal static readonly TapoClientExceptionHandler Default = new TapoClientDefaultExceptionHandler(); | |
- | |
- public abstract TapoClientExceptionHandling DetermineHandling( | |
- TapoDevice device, | |
- Exception exception, | |
- int attempt, | |
- ILogger? logger | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandling.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandling.cs | |
deleted file mode 100644 | |
index 03b40a4..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClientExceptionHandling.cs | |
+++ /dev/null | |
@@ -1,33 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public readonly struct TapoClientExceptionHandling { | |
- public static readonly TapoClientExceptionHandling Throw = default; | |
- public static readonly TapoClientExceptionHandling ThrowAsTapoProtocolException = new() { ShouldWrapIntoTapoProtocolException = true }; | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndThrow = new() { ShouldInvalidateEndPoint = true }; | |
- | |
- public static readonly TapoClientExceptionHandling Retry = new() { ShouldRetry = true }; | |
- public static readonly TapoClientExceptionHandling RetryAfterReconnect = new() { ShouldRetry = true, ShouldReconnect = true }; | |
- public static readonly TapoClientExceptionHandling InvalidateEndPointAndRetry = new() { ShouldRetry = true, ShouldInvalidateEndPoint = true, }; | |
- | |
- public static TapoClientExceptionHandling CreateRetry( | |
- TimeSpan retryAfter, | |
- bool shouldReconnect = false | |
- ) | |
- => new() { | |
- ShouldRetry = true, | |
- RetryAfter = retryAfter, | |
- ShouldReconnect = shouldReconnect, | |
- }; | |
- | |
- public bool ShouldRetry { get; init; } | |
- public TimeSpan RetryAfter { get; init; } | |
- public bool ShouldReconnect { get; init; } | |
- public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
- public bool ShouldInvalidateEndPoint { get; init; } | |
- | |
- public override string ToString() => $"{{{nameof(ShouldRetry)}={ShouldRetry}, {nameof(RetryAfter)}={RetryAfter}, {nameof(ShouldReconnect)}={ShouldReconnect}, {nameof(ShouldWrapIntoTapoProtocolException)}={ShouldWrapIntoTapoProtocolException}, {nameof(ShouldInvalidateEndPoint)}={ShouldInvalidateEndPoint}}}"; | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoErrorCodes.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoErrorCodes.cs | |
deleted file mode 100644 | |
index fac7838..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoErrorCodes.cs | |
+++ /dev/null | |
@@ -1,12 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-// The situation represented by the following error codes are not well-confirmed. | |
-// Therefore, these error codes cannot be made public APIs. | |
-internal static class TapoErrorCodes { | |
- public const ErrorCode DeviceBusy = (ErrorCode)(-1301); | |
- public const ErrorCode RequestParameterError = (ErrorCode)(-1008); | |
- // -1012: ??? | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs | |
deleted file mode 100644 | |
index 5d3682d..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs | |
+++ /dev/null | |
@@ -1,55 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.Http; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-internal class TapoHttpClientFactory : IHttpClientFactory { | |
- private static HttpMessageHandler CreateHandler() | |
- => | |
-#if NETCOREAPP2_1_OR_GREATER || NET5_0_OR_GREATER | |
- new SocketsHttpHandler() | |
-#else | |
- new HttpClientHandler() | |
-#endif | |
- { | |
- AllowAutoRedirect = false, | |
- AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, | |
- MaxConnectionsPerServer = 1, | |
- UseCookies = false, | |
- }; | |
- | |
- private static HttpMessageHandler DefaultHandler { get; } = CreateHandler(); | |
- | |
- public static IHttpClientFactory Default { get; } = new TapoHttpClientFactory( | |
- configureClient: null | |
- ); | |
- | |
- /* | |
- * instance members | |
- */ | |
- private readonly Action<HttpClient>? configureClient; | |
- | |
- internal TapoHttpClientFactory( | |
- Action<HttpClient>? configureClient | |
- ) | |
- { | |
- this.configureClient = configureClient; | |
- } | |
- | |
- public HttpClient CreateClient(string name) | |
- { | |
- var client = new HttpClient( | |
- handler: DefaultHandler, | |
- disposeHandler: false | |
- ) { | |
- Timeout = TimeSpan.FromSeconds(20), | |
- }; | |
- | |
- configureClient?.Invoke(client); | |
- | |
- return client; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSession.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSession.cs | |
deleted file mode 100644 | |
index 85fb0e8..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSession.cs | |
+++ /dev/null | |
@@ -1,79 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Security.Cryptography; | |
-using System.Text.Json; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public sealed class TapoSession : IDisposable { | |
- internal static readonly Uri RequestPath = new("/app", UriKind.Relative); | |
- | |
- public Uri RequestPathAndQuery { get; private set; } = RequestPath; | |
- public string? Token { get; private set; } | |
- public string? SessionId { get; } | |
- public DateTime ExpiresOn { get; } | |
- public bool HasExpired => ExpiresOn <= DateTime.Now; | |
- | |
- private Aes? aes; | |
- private SecurePassThroughJsonConverterFactory? securePassThroughJsonConverterFactory; | |
- | |
- internal JsonSerializerOptions SecurePassThroughJsonSerializerOptions { get; } | |
- | |
- internal TapoSession( | |
- ITapoCredentialIdentity? identity, | |
- string? sessionId, | |
- DateTime expiresOn, | |
- ReadOnlySpan<byte> key, | |
- ReadOnlySpan<byte> iv, | |
- JsonSerializerOptions baseJsonSerializerOptions, | |
- ILogger? logger | |
- ) | |
- { | |
- SessionId = sessionId; | |
- ExpiresOn = expiresOn; | |
- | |
- aes = Aes.Create(); | |
- aes.Mode = CipherMode.CBC; | |
- aes.Padding = PaddingMode.PKCS7; | |
- aes.Key = key.ToArray(); | |
- aes.IV = iv.ToArray(); | |
- | |
- securePassThroughJsonConverterFactory = new( | |
- identity: identity, | |
- encryptorForPassThroughRequest: aes.CreateEncryptor(), | |
- decryptorForPassThroughResponse: aes.CreateDecryptor(), | |
- baseJsonSerializerOptionsForPassThroughMessage: baseJsonSerializerOptions, | |
- logger: logger | |
- ); | |
- | |
- SecurePassThroughJsonSerializerOptions = new(baseJsonSerializerOptions); | |
- SecurePassThroughJsonSerializerOptions.Converters.Add(securePassThroughJsonConverterFactory); | |
- } | |
- | |
- public void Dispose() | |
- { | |
- securePassThroughJsonConverterFactory?.Dispose(); | |
- securePassThroughJsonConverterFactory = null; | |
- | |
- aes?.Dispose(); | |
- aes = null; | |
- } | |
- | |
- internal void SetToken(string token) | |
- { | |
- Token = token; | |
- | |
- // append issued token to the request path query | |
- RequestPathAndQuery = new Uri( | |
- string.Concat( | |
- RequestPath.ToString(), // only path | |
- "?token=", | |
- token | |
- ), | |
- UriKind.Relative | |
- ); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs | |
deleted file mode 100644 | |
index 279ae37..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs | |
+++ /dev/null | |
@@ -1,110 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Collections.Generic; | |
-using System.Globalization; | |
-using System.Linq; | |
-using System.Net.Http; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-public static class TapoSessionCookieUtils { | |
- private const string HeaderNameSetCookie = "Set-Cookie"; | |
- | |
- // Format and example of Set-Cookie header sent back by the Tapo HTTP server. | |
- // Set-Cookie: TP_SESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX;TIMEOUT=1440 | |
- internal static readonly string HttpCookiePrefixForSessionId = "TP_SESSIONID="; // TP_SESSIONID=XXXXXX | |
- internal static readonly string HttpCookieAttributePrefixForTimeout = "TIMEOUT="; // TIMEOUT=xxxx | |
- | |
- public static bool TryGetCookie( | |
- HttpResponseMessage response, | |
- out string? sessionId, | |
- out int? sessionTimeout | |
- ) | |
- { | |
- sessionId = default; | |
- sessionTimeout = default; | |
- | |
- return | |
- response.Headers.TryGetValues(HeaderNameSetCookie, out var setCookieValues) && | |
- TryGetCookie(setCookieValues, out sessionId, out sessionTimeout); | |
- } | |
- | |
- public static bool TryGetCookie( | |
- IEnumerable<string>? cookieValues, | |
- out string? sessionId, | |
- out int? sessionTimeout | |
- ) | |
- { | |
- sessionId = default; | |
- sessionTimeout = default; | |
- | |
- var cookieValueOfTPSessionId = cookieValues?.FirstOrDefault( | |
- static val => val.StartsWith(HttpCookiePrefixForSessionId, StringComparison.Ordinal) | |
- ); | |
- | |
- return | |
- cookieValueOfTPSessionId is not null && | |
- TryParseCookie( | |
- cookieValueOfTPSessionId, | |
- id: out sessionId, | |
- timeout: out sessionTimeout | |
- ); | |
- } | |
- | |
- // We cannot use System.Net.Cookie and its relevant collection classes to handle cookies | |
- // since the Tapo HTTP server uses non-standard cookie attribute 'TIMEOUT'. | |
- public static bool TryParseCookie( | |
- ReadOnlySpan<char> cookie, | |
- out string? id, | |
- out int? timeout | |
- ) | |
- { | |
- id = default; | |
- timeout = default; | |
- | |
- var indexOfSessionIdPrefix = cookie.IndexOf(HttpCookiePrefixForSessionId, StringComparison.Ordinal); | |
- | |
- if (indexOfSessionIdPrefix < 0) | |
- return false; | |
- | |
- var cookieName = cookie.Slice(indexOfSessionIdPrefix + HttpCookiePrefixForSessionId.Length); | |
- var indexOfCookieNameTerminator = cookieName.IndexOf(';'); | |
- | |
- ReadOnlySpan<char> cookieAttributes; | |
- | |
- if (0 <= indexOfCookieNameTerminator) { | |
- id = cookieName.Slice(0, indexOfCookieNameTerminator).TrimEnd().ToString(); // for the case of "TP_SESSIONID=XXXXXXXXXX ;" | |
- | |
- if (id.Length == 0) | |
- return false; | |
- | |
- cookieAttributes = cookie.Slice(indexOfSessionIdPrefix + HttpCookiePrefixForSessionId.Length + indexOfCookieNameTerminator); | |
- } | |
- else { | |
- id = cookieName.TrimEnd().ToString(); // for the case of "TP_SESSIONID=XXXXXXXXXX" | |
- return id.Length != 0; // no attributes | |
- } | |
- | |
- var indexOfTimeoutPrefix = cookieAttributes.IndexOf(HttpCookieAttributePrefixForTimeout, StringComparison.Ordinal); | |
- | |
- if (0 <= indexOfTimeoutPrefix) { | |
- var timeoutAttributeValue = cookieAttributes.Slice(indexOfTimeoutPrefix + HttpCookieAttributePrefixForTimeout.Length); | |
- var indexOfTimeoutTerminator = timeoutAttributeValue.IndexOf(';'); | |
- | |
- if (0 <= indexOfTimeoutTerminator) | |
- // for the case of "TIMEOUT=XXXXXXXXXX ;" | |
- // no need to trim whitespaces here since NumberStyles.Integer trims leading and trailing white spaces | |
- timeoutAttributeValue = timeoutAttributeValue.Slice(0, indexOfTimeoutTerminator); | |
- else | |
- timeoutAttributeValue = timeoutAttributeValue.TrimEnd(); // for the case of "TIMEOUT=XXXXXXXXXX" | |
- | |
- const NumberStyles timeoutNumberStyles = NumberStyles.AllowTrailingWhite; | |
- | |
- if (int.TryParse(timeoutAttributeValue, timeoutNumberStyles, provider: null, out var to)) | |
- timeout = to; | |
- } | |
- | |
- return true; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L530.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L530.cs | |
deleted file mode 100644 | |
index c949754..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L530.cs | |
+++ /dev/null | |
@@ -1,258 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class L530 : TapoDevice { | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public L530( | |
- string host, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- host: host, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, IServiceProvider)" /> | |
- public L530( | |
- string host, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public L530( | |
- IPAddress ipAddress, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(IPAddress, IServiceProvider)" /> | |
- public L530( | |
- IPAddress ipAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, string, string, IServiceProvider?)" /> | |
- public L530( | |
- PhysicalAddress macAddress, | |
- string email, | |
- string password, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, IServiceProvider)" /> | |
- public L530( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L530"/> class. | |
- /// </summary> | |
- /// <inheritdoc | |
- /// cref="TapoDevice(IDeviceEndPointProvider, ITapoCredentialProvider?, Protocol.TapoClientExceptionHandler?, IServiceProvider?)" | |
- /// path="/exception | /param[@name='deviceEndPointProvider' or @name='credential' or @name='serviceProvider']" | |
- /// /> | |
- public L530( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- ITapoCredentialProvider? credential = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- credential: credential, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
-#pragma warning disable SA1313, CA1822 | |
- private readonly record struct SetBrightnessParameter( | |
- [property: JsonPropertyName("brightness")] int Brightness | |
- ) { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- } | |
- | |
- private readonly record struct SetColorTemperatureParameter( | |
- [property: JsonPropertyName("color_temp")] int Temperature, | |
- [property: JsonPropertyName("brightness")] int? Brightness | |
- ) { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- } | |
- | |
- private readonly record struct SetColorParameter( | |
- [property: JsonPropertyName("hue")] int? Hue, | |
- [property: JsonPropertyName("saturation")] int? Saturation, | |
- [property: JsonPropertyName("brightness")] int? Brightness | |
- ) { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- } | |
-#pragma warning restore SA1313, CA1822 | |
- | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. | |
- /// </param> | |
- public ValueTask SetBrightnessAsync( | |
- int brightness, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetBrightnessParameter( | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)).Value | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="colorTemperature"> | |
- /// The color temperature in kelvin, in range of 2500~6500[K]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorTemperatureAsync( | |
- int colorTemperature, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorTemperatureParameter( | |
- Temperature: MulticolorLightUtils.ValidateColorTemperatureValue(colorTemperature, nameof(colorTemperature)), | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="hue"> | |
- /// The hue of the color in degree, in range of 0~360[°]. | |
- /// </param> | |
- /// <param name="saturation"> | |
- /// The saturation of the color in percent value, in range of 0~100[%]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorAsync( | |
- int hue, | |
- int saturation, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: MulticolorLightUtils.ValidateHueValue(hue, nameof(hue)), | |
- Saturation: MulticolorLightUtils.ValidateSaturationValue(saturation, nameof(saturation)), | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="hue"> | |
- /// The hue of the color in degree, in range of 0~360[°]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorHueAsync( | |
- int hue, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: MulticolorLightUtils.ValidateHueValue(hue, nameof(hue)), | |
- Saturation: null, | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="saturation"> | |
- /// The saturation of the color in percent value, in range of 0~100[%]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorSaturationAsync( | |
- int saturation, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: null, | |
- Saturation: MulticolorLightUtils.ValidateSaturationValue(saturation, nameof(saturation)), | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L900.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L900.cs | |
deleted file mode 100644 | |
index 501b03e..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/L900.cs | |
+++ /dev/null | |
@@ -1,243 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class L900 : TapoDevice { | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public L900( | |
- string host, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- host: host, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, IServiceProvider)" /> | |
- public L900( | |
- string host, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public L900( | |
- IPAddress ipAddress, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(IPAddress, IServiceProvider)" /> | |
- public L900( | |
- IPAddress ipAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, string, string, IServiceProvider?)" /> | |
- public L900( | |
- PhysicalAddress macAddress, | |
- string email, | |
- string password, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, IServiceProvider)" /> | |
- public L900( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="L900"/> class. | |
- /// </summary> | |
- /// <inheritdoc | |
- /// cref="TapoDevice(IDeviceEndPointProvider, ITapoCredentialProvider?, Protocol.TapoClientExceptionHandler?, IServiceProvider?)" | |
- /// path="/exception | /param[@name='deviceEndPointProvider' or @name='credential' or @name='serviceProvider']" | |
- /// /> | |
- public L900( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- ITapoCredentialProvider? credential = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- credential: credential, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
-#pragma warning disable SA1313, CA1822 | |
- internal readonly record struct LightingEffectParameter( | |
- [property: JsonPropertyName("enable")] int Enable | |
- ) { | |
- public static readonly LightingEffectParameter Disabled = new(0); | |
- } | |
- | |
- private readonly record struct SetBrightnessParameter( | |
- [property: JsonPropertyName("brightness")] int Brightness | |
- ) { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- | |
- [JsonPropertyName("lighting_effect")] | |
- public LightingEffectParameter LightingEffect => LightingEffectParameter.Disabled; | |
- } | |
- | |
- private readonly record struct SetColorParameter( | |
- [property: JsonPropertyName("hue")] int? Hue, | |
- [property: JsonPropertyName("saturation")] int? Saturation, | |
- [property: JsonPropertyName("brightness")] int? Brightness | |
- ) { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- | |
- [JsonPropertyName("lighting_effect")] | |
- public LightingEffectParameter LightingEffect => LightingEffectParameter.Disabled; | |
- } | |
-#pragma warning restore SA1313, CA1822 | |
- | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. | |
- /// </param> | |
- public ValueTask SetBrightnessAsync( | |
- int brightness, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetBrightnessParameter( | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)).Value | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="hue"> | |
- /// The hue of the color in degree, in range of 0~360[°]. | |
- /// </param> | |
- /// <param name="saturation"> | |
- /// The saturation of the color in percent value, in range of 0~100[%]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorAsync( | |
- int hue, | |
- int saturation, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: MulticolorLightUtils.ValidateHueValue(hue, nameof(hue)), | |
- Saturation: MulticolorLightUtils.ValidateSaturationValue(saturation, nameof(saturation)), | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="hue"> | |
- /// The hue of the color in degree, in range of 0~360[°]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorHueAsync( | |
- int hue, | |
- int? brightness, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: MulticolorLightUtils.ValidateHueValue(hue, nameof(hue)), | |
- Saturation: null, | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
- | |
- /// <param name="saturation"> | |
- /// The saturation of the color in percent value, in range of 0~100[%]. | |
- /// </param> | |
- /// <param name="brightness"> | |
- /// The brightness in percent value, in range of 1~100[%]. If <see langword="null"/>, the current brightness will be kept. | |
- /// </param> | |
- public ValueTask SetColorSaturationAsync( | |
- int saturation, | |
- int? brightness = null, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SetDeviceInfoAsync( | |
- new SetColorParameter( | |
- Hue: null, | |
- Saturation: MulticolorLightUtils.ValidateSaturationValue(saturation, nameof(saturation)), | |
- Brightness: MulticolorLightUtils.ValidateBrightnessValue(brightness, nameof(brightness)) | |
- ), | |
- cancellationToken | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/MulticolorLightUtils.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/MulticolorLightUtils.cs | |
deleted file mode 100644 | |
index 6a720e7..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/MulticolorLightUtils.cs | |
+++ /dev/null | |
@@ -1,108 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-#if NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
-using System.Diagnostics.CodeAnalysis; | |
-#endif | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-internal static class MulticolorLightUtils { | |
- private const int BrightnessMinValue = 1; | |
- private const int BrightnessMaxValue = 100; | |
- | |
- private const int HueMinValue = 0; | |
- private const int HueMaxValue = 360; | |
- | |
- private const int SaturationMinValue = 0; | |
- private const int SaturationMaxValue = 100; | |
- | |
- private static readonly string BrightnessVallueOutOfRangeExceptionMessage = | |
- $"The value for brightness must be in range of {BrightnessMinValue}~{BrightnessMaxValue}"; | |
- | |
- private static readonly string HueValueOutOfRangeExceptionMessage = | |
- $"The value for hue must be in range of {HueMinValue}~{HueMaxValue}"; | |
- | |
- private static readonly string SaturationValueOutOfRangeExceptionMessage = | |
- $"The value for saturation must be in range of {SaturationMinValue}~{SaturationMaxValue}"; | |
- | |
-#if false // TODO | |
- private static readonly string ColorTemperatureValueOutOfRangeExceptionMessage = | |
- "The value for saturation must be in range of {ColorTemperatureMinValue}~{ColorTemperatureMaxValue}"; | |
-#endif | |
- | |
-#if LANG_VERSION_11_OR_GREATER && NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
- [return: NotNullIfNotNull(nameof(newValue))] | |
-#endif | |
- public static int? ValidateBrightnessValue(int? newValue, string paramName) | |
- { | |
- if (newValue == null) | |
- return null; | |
- | |
- if (newValue.Value is < BrightnessMinValue or > BrightnessMaxValue) { | |
- throw new ArgumentOutOfRangeException( | |
- paramName: paramName, | |
- actualValue: newValue.Value, | |
- message: BrightnessVallueOutOfRangeExceptionMessage | |
- ); | |
- } | |
- | |
- return newValue; | |
- } | |
- | |
-#if LANG_VERSION_11_OR_GREATER && NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
- [return: NotNullIfNotNull(nameof(newValue))] | |
-#endif | |
- public static int? ValidateHueValue(int? newValue, string paramName) | |
- { | |
- if (newValue == null) | |
- return null; | |
- | |
- if (newValue.Value is < HueMinValue or > HueMaxValue) { | |
- throw new ArgumentOutOfRangeException( | |
- paramName: paramName, | |
- actualValue: newValue.Value, | |
- message: HueValueOutOfRangeExceptionMessage | |
- ); | |
- } | |
- | |
- return newValue; | |
- } | |
- | |
-#if LANG_VERSION_11_OR_GREATER && NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
- [return: NotNullIfNotNull(nameof(newValue))] | |
-#endif | |
- public static int? ValidateSaturationValue(int? newValue, string paramName) | |
- { | |
- if (newValue == null) | |
- return null; | |
- | |
- if (newValue.Value is < SaturationMinValue or > SaturationMaxValue) { | |
- throw new ArgumentOutOfRangeException( | |
- paramName: paramName, | |
- actualValue: newValue.Value, | |
- message: SaturationValueOutOfRangeExceptionMessage | |
- ); | |
- } | |
- | |
- return newValue; | |
- } | |
- | |
-#if LANG_VERSION_11_OR_GREATER && NULL_STATE_STATIC_ANALYSIS_ATTRIBUTES | |
- [return: NotNullIfNotNull(nameof(newValue))] | |
-#endif | |
- public static int ValidateColorTemperatureValue(int newValue, string paramName) | |
- { | |
-#if false // TODO | |
- if (newValue is < ColorTemperatureMinValue or > ColorTemperatureMaxValue) { | |
- throw new ArgumentOutOfRangeException( | |
- paramName: paramName, | |
- actualValue: newValue, | |
- message: ColorTemperatureValueOutOfRangeExceptionMessage | |
- ); | |
- } | |
-#endif | |
- | |
- return newValue; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/P105.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/P105.cs | |
deleted file mode 100644 | |
index 80b86b7..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/P105.cs | |
+++ /dev/null | |
@@ -1,132 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class P105 : TapoDevice { | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public P105( | |
- string host, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- host: host, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, IServiceProvider)" /> | |
- public P105( | |
- string host, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" /> | |
- public P105( | |
- IPAddress ipAddress, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(IPAddress, IServiceProvider)" /> | |
- public P105( | |
- IPAddress ipAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, string, string, IServiceProvider?)" /> | |
- public P105( | |
- PhysicalAddress macAddress, | |
- string email, | |
- string password, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, IServiceProvider)" /> | |
- public P105( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : base( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="P105"/> class. | |
- /// </summary> | |
- /// <inheritdoc | |
- /// cref="TapoDevice(IDeviceEndPointProvider, ITapoCredentialProvider?, Protocol.TapoClientExceptionHandler?, IServiceProvider?)" | |
- /// path="/exception | /param[@name='deviceEndPointProvider' or @name='credential' or @name='serviceProvider']" | |
- /// /> | |
- public P105( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- ITapoCredentialProvider? credential = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : base( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- credential: credential, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoAuthenticationException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoAuthenticationException.cs | |
deleted file mode 100644 | |
index 6a6b968..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoAuthenticationException.cs | |
+++ /dev/null | |
@@ -1,20 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class TapoAuthenticationException : TapoProtocolException { | |
- public TapoAuthenticationException( | |
- string message, | |
- Uri endPoint, | |
- Exception? innerException = null | |
- ) | |
- : base( | |
- message: message, | |
- endPoint: endPoint, | |
- innerException: innerException | |
- ) | |
- { | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs | |
deleted file mode 100644 | |
index 161d777..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs | |
+++ /dev/null | |
@@ -1,65 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.DependencyInjection.Extensions; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public static class TapoCredentailProviderServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoCredential( | |
- this IServiceCollection services, | |
- string email, | |
- string password | |
- ) | |
- { | |
- if (services is null) | |
- throw new ArgumentNullException(nameof(services)); | |
- | |
- services.TryAdd( | |
- ServiceDescriptor.Singleton( | |
- typeof(ITapoCredentialProvider), | |
- TapoCredentialProviderFactory.CreateFromPlainText(email, password) | |
- ) | |
- ); | |
- | |
- return services; | |
- } | |
- | |
- public static IServiceCollection AddTapoBase64EncodedCredential( | |
- this IServiceCollection services, | |
- string base64UserNameSHA1Digest, | |
- string base64Password | |
- ) | |
- { | |
- if (services is null) | |
- throw new ArgumentNullException(nameof(services)); | |
- | |
- services.TryAdd( | |
- ServiceDescriptor.Singleton( | |
- typeof(ITapoCredentialProvider), | |
- TapoCredentialProviderFactory.CreateFromBase64EncodedText(base64UserNameSHA1Digest, base64Password) | |
- ) | |
- ); | |
- | |
- return services; | |
- } | |
- | |
- public static IServiceCollection AddTapoCredentialProvider( | |
- this IServiceCollection services, | |
- ITapoCredentialProvider credentialProvider | |
- ) | |
- { | |
- if (services is null) | |
- throw new ArgumentNullException(nameof(services)); | |
- if (credentialProvider is null) | |
- throw new ArgumentNullException(nameof(credentialProvider)); | |
- | |
- services.TryAdd( | |
- ServiceDescriptor.Singleton(typeof(ITapoCredentialProvider), credentialProvider) | |
- ); | |
- | |
- return services; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentialProviderFactory.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentialProviderFactory.cs | |
deleted file mode 100644 | |
index 65ef672..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoCredentialProviderFactory.cs | |
+++ /dev/null | |
@@ -1,76 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Text; | |
-using System.Text.Json; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-internal static class TapoCredentialProviderFactory { | |
- public static ITapoCredentialProvider CreateFromPlainText(string email, string password) | |
- => new SingleIdentityStringCredentialProvider( | |
- username: email ?? throw new ArgumentNullException(nameof(email)), | |
- password: password ?? throw new ArgumentNullException(nameof(password)), | |
- isPlainText: true | |
- ); | |
- | |
- public static ITapoCredentialProvider CreateFromBase64EncodedText( | |
- string base64UserNameSHA1Digest, | |
- string base64Password | |
- ) | |
- => new SingleIdentityStringCredentialProvider( | |
- username: base64UserNameSHA1Digest ?? throw new ArgumentNullException(nameof(base64UserNameSHA1Digest)), | |
- password: base64Password ?? throw new ArgumentNullException(nameof(base64Password)), | |
- isPlainText: false | |
- ); | |
- | |
- private sealed class SingleIdentityStringCredentialProvider : ITapoCredentialProvider, ITapoCredential { | |
- private readonly byte[] utf8Username; | |
- private readonly byte[] utf8Password; | |
- private readonly bool isPlainText; | |
- | |
- public SingleIdentityStringCredentialProvider( | |
- string username, | |
- string password, | |
- bool isPlainText | |
- ) | |
- { | |
- utf8Username = Encoding.UTF8.GetBytes(username); | |
- utf8Password = Encoding.UTF8.GetBytes(password); | |
- this.isPlainText = isPlainText; | |
- } | |
- | |
- ITapoCredential ITapoCredentialProvider.GetCredential(ITapoCredentialIdentity? identity) => this; | |
- | |
- void IDisposable.Dispose() { /* nothing to do */ } | |
- | |
- void ITapoCredential.WritePasswordPropertyValue(Utf8JsonWriter writer) | |
- { | |
- if (isPlainText) | |
- writer.WriteBase64StringValue(utf8Password); | |
- else | |
- writer.WriteStringValue(utf8Password); | |
- } | |
- | |
- void ITapoCredential.WriteUsernamePropertyValue(Utf8JsonWriter writer) | |
- { | |
- if (isPlainText) { | |
- Span<byte> buffer = stackalloc byte[TapoCredentialUtils.HexSHA1HashSizeInBytes]; | |
- | |
- try { | |
- if (!TapoCredentialUtils.TryConvertToHexSHA1Hash(utf8Username, buffer, out _)) | |
- throw new InvalidOperationException("failed to encode username property"); | |
- | |
- writer.WriteBase64StringValue(buffer); | |
- } | |
- finally { | |
- buffer.Clear(); | |
- } | |
- } | |
- else { | |
- writer.WriteStringValue(utf8Username); | |
- } | |
- } | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.Create.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.Create.cs | |
deleted file mode 100644 | |
index e6e13e1..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.Create.cs | |
+++ /dev/null | |
@@ -1,99 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-#pragma warning disable IDE0040 | |
-partial class TapoDevice { | |
-#pragma warning restore IDE0040 | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)"/> | |
- public static TapoDevice Create( | |
- string host, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- host: host, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="TapoDevice(string, IServiceProvider)"/> | |
- public static TapoDevice Create( | |
- string host, | |
- IServiceProvider serviceProvider | |
- ) | |
- => new( | |
- host: host, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="TapoDevice(IPAddress, string, string, IServiceProvider?)"/> | |
- public static TapoDevice Create( | |
- IPAddress ipAddress, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- ipAddress: ipAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="TapoDevice(IPAddress, IServiceProvider)"/> | |
- public static TapoDevice Create( | |
- IPAddress ipAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- => new( | |
- ipAddress: ipAddress, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, string, string, IServiceProvider)"/> | |
- public static TapoDevice Create( | |
- PhysicalAddress macAddress, | |
- string email, | |
- string password, | |
- IServiceProvider serviceProvider | |
- ) | |
- => new( | |
- macAddress: macAddress, | |
- email: email, | |
- password: password, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, IServiceProvider)"/> | |
- public static TapoDevice Create( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- => new( | |
- macAddress: macAddress, | |
- serviceProvider: serviceProvider | |
- ); | |
- | |
- /// <inheritdoc | |
- /// cref="TapoDevice(IDeviceEndPointProvider, ITapoCredentialProvider?, Protocol.TapoClientExceptionHandler?, IServiceProvider?)" | |
- /// path="/summary | /exception | /param[@name='deviceEndPointProvider' or @name='credential' or @name='serviceProvider']" | |
- /// /> | |
- public static TapoDevice Create( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- ITapoCredentialProvider? credential = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- => new( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- credential: credential, | |
- serviceProvider: serviceProvider | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.SetDeviceInfo.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.SetDeviceInfo.cs | |
deleted file mode 100644 | |
index 0524214..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.SetDeviceInfo.cs | |
+++ /dev/null | |
@@ -1,46 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Text.Json.Serialization; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-#pragma warning disable IDE0040 | |
-partial class TapoDevice { | |
-#pragma warning restore IDE0040 | |
-#pragma warning disable CA1822 | |
- private readonly struct TurnOnParameter { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => true; | |
- } | |
- | |
- private readonly struct TurnOffParameter { | |
- [JsonPropertyName("device_on")] | |
- public bool DeviceOn => false; | |
- } | |
-#pragma warning restore CA1822 | |
- | |
- public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) | |
- => SetDeviceInfoAsync( | |
- default(TurnOnParameter), | |
- cancellationToken | |
- ); | |
- | |
- public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) | |
- => SetDeviceInfoAsync( | |
- default(TurnOffParameter), | |
- cancellationToken | |
- ); | |
- | |
- /// <summary> | |
- /// Sets the on/off state of the device according to the parameter <paramref name="newOnOffState" />. | |
- /// </summary> | |
- /// <param name="newOnOffState"> | |
- /// The value that indicates new on/off state to be set. <see langword="true"/> for on, otherwise off. | |
- /// </param> | |
- public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) | |
- => newOnOffState | |
- ? TurnOnAsync(cancellationToken) | |
- : TurnOffAsync(cancellationToken); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs | |
deleted file mode 100644 | |
index 96fed05..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs | |
+++ /dev/null | |
@@ -1,509 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-#if SYSTEM_DIAGNOSTICS_UNREACHABLEEXCEPTION | |
-using System.Diagnostics; | |
-#endif | |
-using System.Net; | |
-using System.Net.Http; | |
-using System.Net.NetworkInformation; | |
-using System.Text.Json; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.Logging; | |
-using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public partial class TapoDevice : ITapoCredentialIdentity, IDisposable { | |
- private IDeviceEndPointProvider? deviceEndPointProvider; // if null, it indicates a 'disposed' state. | |
- protected bool IsDisposed => deviceEndPointProvider is null; | |
- | |
- private readonly ITapoCredentialProvider? credential; | |
- private readonly TapoClientExceptionHandler exceptionHandler; | |
- private readonly IServiceProvider? serviceProvider; | |
- public string TerminalUuidString { get; } // must be in the format of 00000000-0000-0000-0000-000000000000 | |
- | |
- private TapoClient? client; | |
- | |
- /// <summary> | |
- /// Gets a current session information represented by <see cref="TapoSession" />. | |
- /// </summary> | |
- /// <value> | |
- /// The <see cref="TapoSession" />. <see langword="null"/> if the session for this instance has not been established or been disposed. | |
- /// </value> | |
- public TapoSession? Session => client?.Session; | |
- | |
- string ITapoCredentialIdentity.Name { | |
- get { | |
- ThrowIfDisposed(); | |
- return $"{GetType().FullName} ({deviceEndPointProvider})"; | |
- } | |
- } | |
- | |
- /// <summary> | |
- /// Gets or sets the timeout value for HTTP requests. | |
- /// </summary> | |
- /// <value> | |
- /// <para>If the value is <see langword="null"/>, the timeout value configured by the <see cref="IHttpClientFactory"/> is used. Otherwise, the specified <see cref="TimeSpan"/> is used for the timeout value for this instance.</para> | |
- /// <para>Therefore, even if the value is <see langword="null"/>, a timeout may still occur. If you do not want to timeout for this instance, specify <see cref="Timeout.InfiniteTimeSpan"/> explicitly.</para> | |
- /// </value> | |
- public TimeSpan? Timeout { get; set; } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="TapoDevice"/> class with specifying the device endpoint by host name. | |
- /// </summary> | |
- /// <param name="host"> | |
- /// A <see cref="string"/> that holds the host name or IP address string, representing the device endpoint. | |
- /// </param> | |
- /// <param name="email"> | |
- /// A <see cref="string"/> that holds the e-mail address of the Tapo account used for authentication. | |
- /// </param> | |
- /// <param name="password"> | |
- /// A <see cref="string"/> that holds the password of the Tapo account used for authentication. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// </param> | |
- protected TapoDevice( | |
- string host, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(host), | |
- credential: TapoCredentialProviderFactory.CreateFromPlainText(email, password), | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <param name="host"> | |
- /// A <see cref="string"/> that holds the host name or IP address string, representing the device endpoint. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// <see cref="ITapoCredentialProvider"/> must be registered in order to retrieve the credentials required for authentication. | |
- /// </param> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" path="/summary"/> | |
- protected TapoDevice( | |
- string host, | |
- IServiceProvider serviceProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(host), | |
- credential: null, | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="TapoDevice"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <param name="ipAddress"> | |
- /// A <see cref="IPAddress"/> that holds the IP address representing the device end point. | |
- /// </param> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" path="/param[@name='email' or @name='password' or @name='serviceProvider']"/> | |
- protected TapoDevice( | |
- IPAddress ipAddress, | |
- string email, | |
- string password, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(ipAddress), | |
- credential: TapoCredentialProviderFactory.CreateFromPlainText(email, password), | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="TapoDevice"/> class with specifying the device endpoint by IP address. | |
- /// </summary> | |
- /// <inheritdoc cref="TapoDevice(IPAddress, string, string, IServiceProvider?)" path="/summary | /param[@name='ipAddress']"/> | |
- /// <inheritdoc cref="TapoDevice(string, IServiceProvider)" path="/param[@name='serviceProvider']"/> | |
- protected TapoDevice( | |
- IPAddress ipAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(ipAddress), | |
- credential: null, | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="TapoDevice"/> class with specifying the device endpoint by MAC address. | |
- /// </summary> | |
- /// <param name="macAddress"> | |
- /// A <see cref="PhysicalAddress"/> that holds the MAC address representing the device end point. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> must be registered to create an end point from the <paramref name="macAddress"/>. | |
- /// </param> | |
- /// <exception cref="InvalidOperationException">No service for type <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> has been registered for <paramref name="serviceProvider"/>.</exception> | |
- /// <inheritdoc cref="TapoDevice(string, string, string, IServiceProvider?)" path="/param[@name='email' or @name='password']"/> | |
- protected TapoDevice( | |
- PhysicalAddress macAddress, | |
- string email, | |
- string password, | |
- IServiceProvider serviceProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(macAddress, serviceProvider), | |
- credential: TapoCredentialProviderFactory.CreateFromPlainText(email, password), | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// <see cref="ITapoCredentialProvider"/> must be registered in order to retrieve the credentials required for authentication. | |
- /// <see cref="IDeviceEndPointFactory<PhysicalAddress>"/> must also be registered to create an <see cref="IDeviceEndPointProvider" />, corresponding to the <paramref name="macAddress"/>. | |
- /// </param> | |
- /// <exception cref="InvalidOperationException">No service for type <see cref="ITapoCredentialProvider"/> and/or <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> has been registered for <paramref name="serviceProvider"/>.</exception> | |
- /// <inheritdoc cref="TapoDevice(PhysicalAddress, string, string, IServiceProvider?)" path="/summary | /param[@name='macAddress']"/> | |
- protected TapoDevice( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: TapoDeviceEndPointProvider.Create(macAddress, serviceProvider), | |
- credential: null, | |
- exceptionHandler: null, | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- /// <summary> | |
- /// Initializes a new instance of the <see cref="TapoDevice"/> class. | |
- /// </summary> | |
- /// <param name="deviceEndPointProvider"> | |
- /// A <see cref="IDeviceEndPointProvider"/> that provides the device end point. | |
- /// </param> | |
- /// <param name="credential"> | |
- /// A <see cref="ITapoCredentialProvider"/> that provides the credentials required for authentication. | |
- /// </param> | |
- /// <param name="exceptionHandler"> | |
- /// A <see cref="TapoClientExceptionHandler"/> that determines the handling of the exception thrown by the <see cref="TapoClient"/>. | |
- /// </param> | |
- /// <param name="serviceProvider"> | |
- /// A <see cref="IServiceProvider"/>. | |
- /// </param> | |
- /// <exception cref="InvalidOperationException">No service for type <see cref="ITapoCredentialProvider"/> and/or <see cref="IDeviceEndPointFactory{PhysicalAddress}"/> has been registered for <paramref name="serviceProvider"/>.</exception> | |
- /// <exception cref="ArgumentNullException"> | |
- /// <paramref name="deviceEndPointProvider"/> is <see langword="null"/>, or both <paramref name="credential"/> and <paramref name="serviceProvider"/> are <see langword="null"/>. | |
- /// </exception> | |
- protected TapoDevice( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- ITapoCredentialProvider? credential = null, | |
- TapoClientExceptionHandler? exceptionHandler = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- { | |
- this.deviceEndPointProvider = deviceEndPointProvider ?? throw new ArgumentNullException(nameof(deviceEndPointProvider)); | |
- this.credential = credential | |
- ?? (serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider))).GetRequiredService<ITapoCredentialProvider>(); | |
- this.exceptionHandler = exceptionHandler | |
- ?? serviceProvider?.GetService<TapoClientExceptionHandler>() | |
- ?? TapoClientExceptionHandler.Default; | |
- this.serviceProvider = serviceProvider; | |
- | |
- TerminalUuidString = Guid.NewGuid().ToString("D", provider: null); // TODO: support DI | |
- } | |
- | |
- public void Dispose() | |
- { | |
- Dispose(true); | |
- GC.SuppressFinalize(this); | |
- } | |
- | |
- protected virtual void Dispose(bool disposing) | |
- { | |
- if (!disposing) | |
- return; | |
- | |
- deviceEndPointProvider = null; // mark as disposed | |
- | |
- client?.Dispose(); | |
- client = null; | |
- } | |
- | |
- private void ThrowIfDisposed() | |
- { | |
- if (IsDisposed) | |
- throw new ObjectDisposedException(GetType().FullName); | |
- } | |
- | |
- public ValueTask<EndPoint> ResolveEndPointAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- ThrowIfDisposed(); | |
- | |
- return cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled<EndPoint>(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled<EndPoint>(cancellationToken) | |
-#endif | |
- : deviceEndPointProvider.ResolveOrThrowAsync( | |
- defaultPort: TapoClient.DefaultPort, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- protected ValueTask EnsureSessionEstablishedAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- { | |
- ThrowIfDisposed(); | |
- | |
- return EnsureSessionEstablishedAsyncCore( | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- private async ValueTask EnsureSessionEstablishedAsyncCore( | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- var endPoint = await ResolveEndPointAsync(cancellationToken).ConfigureAwait(false); | |
- | |
- if (client is not null && !client.EndPoint.Equals(endPoint)) { | |
- // endpoint has changed, recreate client with new endpoint | |
- client.Logger?.LogInformation($"Endpoint has changed: {client.EndPoint} -> {endPoint}"); | |
- client.Dispose(); | |
- client = null; | |
- } | |
- | |
- client ??= new TapoClient( | |
- endPoint: endPoint, | |
- httpClientFactory: serviceProvider?.GetService<IHttpClientFactory>(), | |
- logger: serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger(GenerateLoggerCategoryName()) | |
- ); | |
- | |
- string GenerateLoggerCategoryName() | |
- => deviceEndPointProvider is IDynamicDeviceEndPointProvider | |
- ? $"{nameof(TapoClient)}({endPoint}, {deviceEndPointProvider})" | |
- : $"{nameof(TapoClient)}({endPoint})"; | |
- | |
- if (client.Session is not null) | |
- return; | |
- | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- try { | |
- await client.AuthenticateAsync( | |
- identity: this, | |
- credential: credential, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- } | |
- catch { | |
- client.Dispose(); | |
- client = null; | |
- | |
- throw; | |
- } | |
- } | |
- | |
- protected ValueTask SendRequestAsync<TRequest, TResponse>( | |
- TRequest request, | |
- CancellationToken cancellationToken = default | |
- ) | |
- where TRequest : ITapoPassThroughRequest | |
- where TResponse : ITapoPassThroughResponse | |
- { | |
- if (request is null) | |
- throw new ArgumentNullException(nameof(request)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return SendRequestAsyncCore(request, cancellationToken); | |
- | |
- async ValueTask SendRequestAsyncCore(TRequest req, CancellationToken ct) | |
- => await SendRequestAsync<TRequest, TResponse, None /* as an alternative to System.Void */>( | |
- request: req, | |
- composeResult: static _ => default, | |
- cancellationToken: ct | |
- ).ConfigureAwait(false); | |
- } | |
- | |
- protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>( | |
- TRequest request, | |
- Func<TResponse, TResult> composeResult, | |
- CancellationToken cancellationToken = default | |
- ) | |
- where TRequest : ITapoPassThroughRequest | |
- where TResponse : ITapoPassThroughResponse | |
- { | |
- if (request is null) | |
- throw new ArgumentNullException(nameof(request)); | |
- if (composeResult is null) | |
- throw new ArgumentNullException(nameof(composeResult)); | |
- | |
- ThrowIfDisposed(); | |
- | |
- return SendRequestAsyncCore( | |
- request: request, | |
- composeResult: composeResult, | |
- cancellationToken: cancellationToken | |
- ); | |
- } | |
- | |
- private async ValueTask<TResult> SendRequestAsyncCore<TRequest, TResponse, TResult>( | |
- TRequest request, | |
- Func<TResponse, TResult> composeResult, | |
- CancellationToken cancellationToken = default | |
- ) | |
- where TRequest : ITapoPassThroughRequest | |
- where TResponse : ITapoPassThroughResponse | |
- { | |
- const int maxAttempts = 5; | |
- var delay = TimeSpan.Zero; | |
- | |
- for (var attempt = 0; attempt < maxAttempts; attempt++) { | |
- if (TimeSpan.Zero < delay) | |
- await Task.Delay(delay, cancellationToken).ConfigureAwait(false); | |
- | |
- await EnsureSessionEstablishedAsync(cancellationToken).ConfigureAwait(false); | |
- | |
- cancellationToken.ThrowIfCancellationRequested(); | |
- | |
- client.Timeout = Timeout; | |
- | |
- try { | |
- var response = await client.SendRequestAsync<TRequest, TResponse>( | |
- request: request, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- return composeResult(response); | |
- } | |
- catch (Exception ex) { | |
- // OperationCanceledException and TaskCanceledException due to a cancel | |
- // request triggered by a given CancellationToken must not be handled | |
- // by exception handler, just rethrow instead. | |
- var handling = ( | |
- ex is OperationCanceledException exOperationCanceled && | |
- exOperationCanceled.CancellationToken.Equals(cancellationToken) | |
- ) | |
- ? TapoClientExceptionHandling.Throw | |
- : exceptionHandler.DetermineHandling(this, ex, attempt, client.Logger); | |
- | |
- static void LogRequest(ILogger logger, TRequest req) | |
- => logger.LogError(JsonSerializer.Serialize(req)); | |
- | |
- client.Logger?.LogTrace( | |
- "Exception handling for {TypeOfException}: {ExceptionHandling}", | |
- ex.GetType().FullName, | |
- handling | |
- ); | |
- | |
- if (client.Logger is not null && !handling.ShouldRetry) | |
- LogRequest(client.Logger, request); | |
- | |
- if (handling.ShouldInvalidateEndPoint) { | |
- if (deviceEndPointProvider is IDynamicDeviceEndPointProvider dynamicEndPoint) { | |
- // mark end point as invalid to have the end point refreshed or rescanned | |
- dynamicEndPoint.InvalidateEndPoint(); | |
- } | |
- else { | |
- // disallow retry | |
- handling = handling with { ShouldRetry = false }; | |
- } | |
- } | |
- | |
- if (handling.ShouldRetry) { | |
- /* | |
- * retry | |
- */ | |
- delay = handling.RetryAfter; | |
- | |
- if (handling.ShouldReconnect) { | |
- client.Dispose(); | |
- client = null; | |
- } | |
- | |
- continue; | |
- } | |
- | |
- /* | |
- * rethrow | |
- */ | |
- var endPointUri = client.EndPointUri; | |
- | |
- client.Dispose(); | |
- client = null; | |
- | |
- if (handling.ShouldWrapIntoTapoProtocolException) { | |
- if ( | |
- ex is TaskCanceledException exTaskCanceled && | |
- exTaskCanceled.InnerException is TimeoutException exInnerTimeout | |
- ) { | |
- throw new TapoProtocolException( | |
- message: $"Request timed out; {ex.Message}", | |
- endPoint: endPointUri, | |
- innerException: exInnerTimeout | |
- ); | |
- } | |
- else { | |
- throw new TapoProtocolException( | |
- message: "Unhandled exception", | |
- endPoint: endPointUri, | |
- innerException: ex | |
- ); | |
- } | |
- } | |
- | |
- throw; | |
- } // try | |
- } // for | |
- | |
-#if SYSTEM_DIAGNOSTICS_UNREACHABLEEXCEPTION | |
- throw new UnreachableException(); | |
-#else | |
- throw new NotImplementedException("unreachable"); | |
-#endif | |
- } | |
- | |
- public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync( | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync<GetDeviceInfoRequest, GetDeviceInfoResponse, TapoDeviceInfo>( | |
- request: default, | |
- composeResult: static resp => resp.Result, | |
- cancellationToken: cancellationToken | |
- ); | |
- | |
- public ValueTask SetDeviceInfoAsync<TParameters>( | |
- TParameters parameters, | |
- CancellationToken cancellationToken = default | |
- ) | |
- => SendRequestAsync<SetDeviceInfoRequest<TParameters>, SetDeviceInfoResponse>( | |
- request: new( | |
- terminalUuid: TerminalUuidString, | |
- parameters: parameters | |
- ), | |
- cancellationToken: cancellationToken | |
- ); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceEndPointProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceEndPointProvider.cs | |
deleted file mode 100644 | |
index 876e6e5..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceEndPointProvider.cs | |
+++ /dev/null | |
@@ -1,28 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public static class TapoDeviceEndPointProvider { | |
- public static IDeviceEndPointProvider Create(string host) | |
- => DeviceEndPointProvider.Create(host, TapoClient.DefaultPort); | |
- | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress) | |
- => DeviceEndPointProvider.Create(ipAddress, TapoClient.DefaultPort); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- IServiceProvider serviceProvider | |
- ) | |
- => DeviceEndPointProvider.Create(macAddress, TapoClient.DefaultPort, serviceProvider); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- IDeviceEndPointFactory<PhysicalAddress> endPointFactory | |
- ) | |
- => DeviceEndPointProvider.Create(macAddress, TapoClient.DefaultPort, endPointFactory); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceInfo.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceInfo.cs | |
deleted file mode 100644 | |
index a6a8110..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoDeviceInfo.cs | |
+++ /dev/null | |
@@ -1,110 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Collections.Generic; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Text.Json.Serialization; | |
-using Smdn.TPSmartHomeDevices.Json; | |
-using Smdn.TPSmartHomeDevices.Tapo.Json; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class TapoDeviceInfo { | |
- [JsonIgnore] | |
- public DateTimeOffset TimeStamp { get; } = DateTimeOffset.Now; | |
- | |
- /* | |
- * storage for the device-specific informations | |
- */ | |
- [JsonExtensionData] | |
- internal IDictionary<string, object>? ExtraData { get; init; } | |
- | |
- /* | |
- * properties for the informations common to the devices | |
- */ | |
- [JsonPropertyName("device_id")] | |
- public string? Id { get; init; } | |
- | |
- [JsonPropertyName("type")] | |
- public string? TypeName { get; init; } | |
- | |
- [JsonPropertyName("model")] | |
- public string? ModelName { get; init; } | |
- | |
- [JsonPropertyName("fw_id")] | |
- public string? FirmwareId { get; init; } | |
- | |
- [JsonPropertyName("fw_ver")] | |
- public string? FirmwareVersion { get; init; } | |
- | |
- [JsonPropertyName("hw_id")] | |
- public string? HardwareId { get; init; } | |
- | |
- [JsonPropertyName("hw_ver")] | |
- public string? HardwareVersion { get; init; } | |
- | |
- [JsonPropertyName("oem_id")] | |
- public string? OemId { get; init; } | |
- | |
- [JsonPropertyName("mac")] | |
- [JsonConverter(typeof(MacAddressJsonConverter))] | |
- public PhysicalAddress? MacAddress { get; init; } | |
- | |
- [JsonPropertyName("specs")] | |
- public string? HardwareSpecifications { get; init; } | |
- | |
- [JsonPropertyName("lang")] | |
- public string? Language { get; init; } | |
- | |
- [JsonPropertyName("device_on")] | |
- public bool IsOn { get; init; } | |
- | |
- [JsonPropertyName("on_time")] | |
- [JsonConverter(typeof(TimeSpanInSecondsJsonConverter))] | |
- public TimeSpan? OnTimeDuration { get; init; } | |
- | |
- [JsonPropertyName("overheated")] | |
- public bool IsOverheated { get; init; } | |
- | |
- [JsonPropertyName("nickname")] | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- public string? NickName { get; init; } | |
- | |
- [JsonPropertyName("avatar")] | |
- public string? Avatar { get; init; } | |
- | |
- [JsonPropertyName("time_diff")] | |
- [JsonConverter(typeof(TimeSpanInMinutesJsonConverter))] | |
- public TimeSpan? TimeZoneOffset { get; init; } | |
- | |
- [JsonPropertyName("region")] | |
- public string? TimeZoneRegion { get; init; } | |
- | |
- /// <summary>Gets a value that indicates a longitude in decimal degrees.</summary> | |
- [JsonPropertyName("longitude")] | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- public decimal? GeolocationLongitude { get; init; } | |
- | |
- /// <summary>Gets a value that indicates a latitude in decimal degrees.</summary> | |
- [JsonPropertyName("latitude")] | |
- [JsonConverter(typeof(GeolocationInDecimalDegreesJsonConverter))] | |
- public decimal? GeolocationLatitude { get; init; } | |
- | |
- [JsonPropertyName("has_set_location_info")] | |
- public bool HasGeolocationInfoSet { get; init; } | |
- | |
- [JsonPropertyName("ip")] | |
- [JsonConverter(typeof(TapoIPAddressJsonConverter))] | |
- public IPAddress? IPAddress { get; init; } | |
- | |
- [JsonPropertyName("ssid")] | |
- [JsonConverter(typeof(TapoBase64StringJsonConverter))] | |
- public string? NetworkSsid { get; init; } | |
- | |
- [JsonPropertyName("signal_level")] | |
- public int? NetworkSignalLevel { get; init; } | |
- | |
- [JsonPropertyName("rssi")] | |
- public decimal? NetworkRssi { get; init; } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoErrorResponseException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoErrorResponseException.cs | |
deleted file mode 100644 | |
index 34d207c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoErrorResponseException.cs | |
+++ /dev/null | |
@@ -1,32 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class TapoErrorResponseException : TapoProtocolException { | |
- internal static void ThrowIfError(Uri requestUri, string requestMethod, ErrorCode errorCode) | |
- { | |
- if (errorCode != ErrorCode.Success) | |
- throw new TapoErrorResponseException(requestUri, requestMethod, errorCode); | |
- } | |
- | |
- public string RequestMethod { get; } | |
- public ErrorCode ErrorCode { get; } | |
- | |
- public TapoErrorResponseException( | |
- Uri requestEndPoint, | |
- string requestMethod, | |
- ErrorCode errorCode | |
- ) | |
- : base( | |
- message: $"Request '{requestMethod}' failed with error code {(int)errorCode}. (Request URI: {requestEndPoint})", | |
- endPoint: requestEndPoint, | |
- innerException: null | |
- ) | |
- { | |
- RequestMethod = requestMethod; | |
- ErrorCode = errorCode; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoHttpClientFactoryServiceCollectionExtensions.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoHttpClientFactoryServiceCollectionExtensions.cs | |
deleted file mode 100644 | |
index b07944c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoHttpClientFactoryServiceCollectionExtensions.cs | |
+++ /dev/null | |
@@ -1,31 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net.Http; | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.DependencyInjection.Extensions; | |
-using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public static class TapoHttpClientFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddTapoHttpClient( | |
- this IServiceCollection services, | |
- Action<HttpClient>? configureClient = null | |
- ) | |
- { | |
- if (services is null) | |
- throw new ArgumentNullException(nameof(services)); | |
- | |
- services.TryAdd( | |
- ServiceDescriptor.Singleton( | |
- typeof(IHttpClientFactory), | |
- new TapoHttpClientFactory( | |
- configureClient: configureClient | |
- ) | |
- ) | |
- ); | |
- | |
- return services; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoProtocolException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoProtocolException.cs | |
deleted file mode 100644 | |
index e9f9924..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.Tapo/TapoProtocolException.cs | |
+++ /dev/null | |
@@ -1,22 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices.Tapo; | |
- | |
-public class TapoProtocolException : InvalidOperationException { | |
- public Uri EndPoint { get; } | |
- | |
- protected internal TapoProtocolException( | |
- string message, | |
- Uri endPoint, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- innerException: innerException | |
- ) | |
- { | |
- EndPoint = endPoint; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.csproj b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.csproj | |
index ab416f3..926c695 100644 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.csproj | |
+++ b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices.csproj | |
@@ -5,40 +5,40 @@ SPDX-License-Identifier: MIT | |
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<TargetFrameworks>netstandard2.1;net6.0</TargetFrameworks> | |
- <TargetFrameworks Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '7.0.0'))">net7.0;$(TargetFrameworks)</TargetFrameworks> | |
<VersionPrefix>1.0.0</VersionPrefix> | |
- <VersionSuffix>preview3</VersionSuffix> | |
- <!-- <PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> --> | |
- <Nullable>enable</Nullable> | |
- <DefineConstants | |
- Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '7.0.0'))" | |
- >$(DefineConstants);LANG_VERSION_11_OR_GREATER</DefineConstants> <!-- required to use the UTF-8 string literals in C# 11 --> | |
- <GenerateDocumentationFile>true</GenerateDocumentationFile> | |
+ <VersionSuffix>rc1</VersionSuffix> | |
+ <!-- exclude build output assembly from packing --> | |
+ <IncludeBuildOutput>false</IncludeBuildOutput> | |
+ <!-- ignore warning NU5128 --> | |
+ <NoWarn>NU5128;$(NoWarn)</NoWarn> | |
</PropertyGroup> | |
<PropertyGroup Label="assembly attributes"> | |
<Description> | |
-<![CDATA[.NET implementations for Kasa and Tapo, the TP-Link smart home devices. | |
+<![CDATA[A meta package that addes the dependency of Smdn.TPSmartHomeDevices.Kasa, Smdn.TPSmartHomeDevices.Tapo and Smdn.TPSmartHomeDevices.MacAddressEndPoint. | |
+This package is deprecated, use the individual packages instead. | |
]]> | |
</Description> | |
<CopyrightYear>2022</CopyrightYear> | |
</PropertyGroup> | |
<PropertyGroup Label="package properties"> | |
- <PackageTags>tplink-kasa,tplink-tapo,smarthome,homeautomation,smartdevice</PackageTags> | |
+ <PackageTags>metapackage,tplink-kasa,tplink-tapo,$(PackageCommonTags)</PackageTags> | |
</PropertyGroup> | |
<ItemGroup> | |
- <PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> | |
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> | |
- <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> | |
- <PackageReference Include="Smdn.Fundamental.PrintableEncoding.Hexadecimal" Version="3.0.1" /> | |
- <PackageReference Include="Smdn.Net.AddressResolution" Version="1.0.0-preview5" /> | |
+ <PackageReference Version="[1.0.0-*,1.0.0)" Include="Smdn.TPSmartHomeDevices.Kasa" /> | |
+ <PackageReference Version="[1.0.0-*,1.0.0)" Include="Smdn.TPSmartHomeDevices.Tapo" /> | |
+ <PackageReference Version="[1.0.0-*,1.0.0)" Include="Smdn.TPSmartHomeDevices.MacAddressEndPoint" /> | |
</ItemGroup> | |
- <PropertyGroup> | |
- <NoWarn>CS1573;CS1591;$(NoWarn)</NoWarn> <!-- XML docs--> | |
- <NoWarn>CS8618;SA1027;SA1507;SA1005;$(NoWarn)</NoWarn> | |
- </PropertyGroup> | |
- | |
+ <ItemGroup> | |
+ <!-- Third party notice --> | |
+ <None | |
+ Include="$(MSBuildThisFileDirectory)..\..\ThirdPartyNotices.md" | |
+ Pack="true" | |
+ PackagePath="ThirdPartyNotices.md" | |
+ CopyToOutputDirectory="None" | |
+ /> | |
+ </ItemGroup> | |
</Project> | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs | |
deleted file mode 100644 | |
index 648e9b6..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointFactoryServiceCollectionExtensions.cs | |
+++ /dev/null | |
@@ -1,23 +0,0 @@ | |
-using System; | |
- | |
-using Microsoft.Extensions.DependencyInjection; | |
-using Microsoft.Extensions.DependencyInjection.Extensions; | |
- | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public static class DeviceEndPointFactoryServiceCollectionExtensions { | |
- public static IServiceCollection AddDeviceEndPointFactory<TAddress>( | |
- this IServiceCollection services, | |
- IDeviceEndPointFactory<TAddress> endPointFactory | |
- ) | |
- { | |
- 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/Smdn.TPSmartHomeDevices/DeviceEndPointProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointProvider.cs | |
deleted file mode 100644 | |
index 3270e8f..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointProvider.cs | |
+++ /dev/null | |
@@ -1,104 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Microsoft.Extensions.DependencyInjection; | |
- | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-internal static class DeviceEndPointProvider { | |
- private sealed class StaticDeviceEndPointProvider : IDeviceEndPointProvider { | |
- private readonly ValueTask<EndPoint?> staticEndPointValueTaskResult; | |
- | |
- public StaticDeviceEndPointProvider(EndPoint endPoint) | |
- { | |
- staticEndPointValueTaskResult = new ValueTask<EndPoint?>(endPoint); | |
- } | |
- | |
- public ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken) | |
- => cancellationToken.IsCancellationRequested | |
- ? | |
-#if SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- ValueTask.FromCanceled<EndPoint?>(cancellationToken) | |
-#else | |
- ValueTaskShim.FromCanceled<EndPoint?>(cancellationToken) | |
-#endif | |
- : staticEndPointValueTaskResult; | |
- | |
- public override string ToString() | |
- => staticEndPointValueTaskResult.Result!.ToString(); | |
- } | |
- | |
- public static IDeviceEndPointProvider Create(string host, int port) | |
- => Create( | |
- new DnsEndPoint( | |
- host: host ?? throw new ArgumentNullException(nameof(host)), | |
- port: port | |
- ) | |
- ); | |
- | |
- public static IDeviceEndPointProvider Create(IPAddress ipAddress, int port) | |
- => Create( | |
- new IPEndPoint( | |
- address: ipAddress ?? throw new ArgumentNullException(nameof(ipAddress)), | |
- port: port | |
- ) | |
- ); | |
- | |
- public static IDeviceEndPointProvider Create(EndPoint endPoint) | |
- => new StaticDeviceEndPointProvider( | |
- endPoint ?? throw new ArgumentNullException(nameof(endPoint)) | |
- ); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- int port, | |
- IServiceProvider serviceProvider | |
- ) | |
- => Create( | |
- macAddress: macAddress ?? throw new ArgumentNullException(nameof(macAddress)), | |
- port: port, | |
- endPointFactory: (serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider))) | |
- .GetRequiredService<IDeviceEndPointFactory<PhysicalAddress>>() | |
- ); | |
- | |
- public static IDeviceEndPointProvider Create( | |
- PhysicalAddress macAddress, | |
- int port, | |
- IDeviceEndPointFactory<PhysicalAddress> endPointFactory | |
- ) | |
- => (endPointFactory ?? throw new ArgumentNullException(nameof(endPointFactory))) | |
- .Create( | |
- address: macAddress ?? throw new ArgumentNullException(nameof(macAddress)), | |
- port: port | |
- ); | |
- | |
- internal static async ValueTask<EndPoint> ResolveOrThrowAsync( | |
- this IDeviceEndPointProvider provider, | |
- int defaultPort, | |
- CancellationToken cancellationToken | |
- ) | |
- { | |
- var endPoint = await provider.GetEndPointAsync(cancellationToken); | |
- | |
- if (endPoint is null && provider is IDynamicDeviceEndPointProvider dynamicEndPoint) | |
- dynamicEndPoint.InvalidateEndPoint(); | |
- | |
- 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 ep => ep, | |
- | |
- null => throw new DeviceEndPointResolutionException(provider), | |
- }; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs | |
deleted file mode 100644 | |
index baba5a3..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/DeviceEndPointResolutionException.cs | |
+++ /dev/null | |
@@ -1,33 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
- | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public class DeviceEndPointResolutionException : Exception { | |
- public IDeviceEndPointProvider EndPointProvider { get; } | |
- | |
- public DeviceEndPointResolutionException( | |
- IDeviceEndPointProvider deviceEndPointProvider | |
- ) | |
- : this( | |
- deviceEndPointProvider: deviceEndPointProvider, | |
- message: "Could not get or resolve the device endpoint.", | |
- innerException: null | |
- ) | |
- { | |
- } | |
- | |
- public DeviceEndPointResolutionException( | |
- IDeviceEndPointProvider deviceEndPointProvider, | |
- string message, | |
- Exception? innerException | |
- ) | |
- : base( | |
- message: message, | |
- innerException: innerException | |
- ) | |
- { | |
- EndPointProvider = deviceEndPointProvider; | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs | |
deleted file mode 100644 | |
index 8a4daf9..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointFactory.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public interface IDeviceEndPointFactory<TAddress> { | |
- IDeviceEndPointProvider Create(TAddress address, int port = 0); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointProvider.cs | |
deleted file mode 100644 | |
index d2d77af..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDeviceEndPointProvider.cs | |
+++ /dev/null | |
@@ -1,11 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.Net; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
- | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public interface IDeviceEndPointProvider { | |
- ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken = default); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPointProvider.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPointProvider.cs | |
deleted file mode 100644 | |
index 1f38438..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/IDynamicDeviceEndPointProvider.cs | |
+++ /dev/null | |
@@ -1,7 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public interface IDynamicDeviceEndPointProvider : IDeviceEndPointProvider { | |
- void InvalidateEndPoint(); | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/MacAddressDeviceEndPointFactory.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/MacAddressDeviceEndPointFactory.cs | |
deleted file mode 100644 | |
index 27196b2..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/MacAddressDeviceEndPointFactory.cs | |
+++ /dev/null | |
@@ -1,126 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System; | |
-using System.Net; | |
-using System.Net.NetworkInformation; | |
-using System.Threading; | |
-using System.Threading.Tasks; | |
-using Smdn.Net; | |
-using Smdn.Net.AddressResolution; | |
- | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-public class MacAddressDeviceEndPointFactory : IDeviceEndPointFactory<PhysicalAddress>, IDisposable { | |
- protected class MacAddressDeviceEndPointProvider : IDynamicDeviceEndPointProvider { | |
- private readonly IAddressResolver<PhysicalAddress, IPAddress> resolver; | |
- private readonly PhysicalAddress address; | |
- private readonly int port; | |
- | |
- public MacAddressDeviceEndPointProvider( | |
- IAddressResolver<PhysicalAddress, IPAddress> resolver, | |
- PhysicalAddress address, | |
- int port | |
- ) | |
- { | |
- this.resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); | |
- this.address = address ?? throw new ArgumentNullException(nameof(address)); | |
- this.port = port; | |
- } | |
- | |
- public async ValueTask<EndPoint?> GetEndPointAsync(CancellationToken cancellationToken) | |
- { | |
- var resolvedAddress = await resolver.ResolveAsync( | |
- address: address, | |
- cancellationToken: cancellationToken | |
- ).ConfigureAwait(false); | |
- | |
- return resolvedAddress is null | |
- ? null | |
- : new IPEndPoint( | |
- address: resolvedAddress, | |
- port: port | |
- ); | |
- } | |
- | |
- public void InvalidateEndPoint() | |
- => resolver.Invalidate(address); | |
- | |
- public override string ToString() | |
- => address.ToMacAddressString(); | |
- } | |
- | |
- /* | |
- * instance members | |
- */ | |
- private IAddressResolver<PhysicalAddress, IPAddress>? resolver; // if null, it indicates a 'disposed' state. | |
- | |
- public MacAddressDeviceEndPointFactory( | |
- MacAddressResolverOptions? options = null, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- resolver: (IAddressResolver<PhysicalAddress, IPAddress>)MacAddressResolver.Create( | |
- options: options, | |
- serviceProvider: serviceProvider | |
- ), | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
- public MacAddressDeviceEndPointFactory( | |
- MacAddressResolver resolver, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
- : this( | |
- resolver: (IAddressResolver<PhysicalAddress, IPAddress>)(resolver ?? throw new ArgumentNullException(nameof(resolver))), | |
- serviceProvider: serviceProvider | |
- ) | |
- { | |
- } | |
- | |
-#pragma warning disable IDE0060 | |
- protected MacAddressDeviceEndPointFactory( | |
- IAddressResolver<PhysicalAddress, IPAddress> resolver, | |
- IServiceProvider? serviceProvider = null | |
- ) | |
-#pragma warning restore IDE0060 | |
- { | |
- this.resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); | |
- } | |
- | |
- public void Dispose() | |
- { | |
- Dispose(true); | |
- GC.SuppressFinalize(this); | |
- } | |
- | |
- protected virtual void Dispose(bool disposing) | |
- { | |
- if (!disposing) | |
- return; | |
- | |
- (resolver as IDisposable)?.Dispose(); | |
- resolver = null; // mark as disposed | |
- } | |
- | |
- protected void ThrowIfDisposed() | |
- { | |
- if (resolver is null) | |
- throw new ObjectDisposedException(GetType().FullName); | |
- } | |
- | |
- public virtual IDeviceEndPointProvider Create( | |
- PhysicalAddress address, | |
- int port = 0 | |
- ) | |
- { | |
- ThrowIfDisposed(); | |
- | |
- return new MacAddressDeviceEndPointProvider( | |
- resolver: resolver, | |
- address: address ?? throw new ArgumentNullException(nameof(address)), | |
- port: port | |
- ); | |
- } | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/None.cs b/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/None.cs | |
deleted file mode 100644 | |
index e7d21e2..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/Smdn.TPSmartHomeDevices/None.cs | |
+++ /dev/null | |
@@ -1,5 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace Smdn.TPSmartHomeDevices; | |
- | |
-internal readonly struct None { } | |
diff --git a/src/Smdn.TPSmartHomeDevices/System.Runtime.CompilerServices/IsExternalInit.cs b/src/Smdn.TPSmartHomeDevices/System.Runtime.CompilerServices/IsExternalInit.cs | |
deleted file mode 100644 | |
index 5971d2c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/System.Runtime.CompilerServices/IsExternalInit.cs | |
+++ /dev/null | |
@@ -1,8 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
- | |
-#if !SYSTEM_RUNTIME_COMPILERSERVICES_ISEXTERNALINIT | |
-namespace System.Runtime.CompilerServices; | |
- | |
-internal sealed class IsExternalInit { } | |
-#endif | |
diff --git a/src/Smdn.TPSmartHomeDevices/System.Security.Cryptography/AsymmetricAlgorithmShim.cs b/src/Smdn.TPSmartHomeDevices/System.Security.Cryptography/AsymmetricAlgorithmShim.cs | |
deleted file mode 100644 | |
index 743537c..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/System.Security.Cryptography/AsymmetricAlgorithmShim.cs | |
+++ /dev/null | |
@@ -1,50 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-using System.IO; | |
-using System.Text; | |
- | |
-namespace System.Security.Cryptography; | |
- | |
-internal static class AsymmetricAlgorithmShim { | |
-#if !SYSTEM_SECURITY_CRYPTOGRAPHY_ASYMMETRICALGORITHM_EXPORTSUBJECTPUBLICKEYINFOPEM | |
- public static string ExportSubjectPublicKeyInfoPem(AsymmetricAlgorithm algorithm) | |
- => ExportPem( | |
- header: "-----BEGIN PUBLIC KEY-----", | |
- footer: "-----END PUBLIC KEY-----", | |
- key: (algorithm ?? throw new ArgumentNullException(nameof(algorithm))).ExportSubjectPublicKeyInfo() | |
- ); | |
- | |
- private static string ExportPem(string header, string footer, byte[] key) | |
- { | |
- using var stream = new MemoryStream(capacity: header.Length + footer.Length + (key.Length * 2)); | |
- var writer = new StreamWriter(stream: stream, encoding: Encoding.ASCII, leaveOpen: true, bufferSize: 1024) { | |
- NewLine = "\n", | |
- }; | |
- | |
- writer.WriteLine(header); | |
- writer.Flush(); | |
- | |
- using var base64Stream = new CryptoStream( | |
- stream, | |
- new ToBase64Transform(), | |
- CryptoStreamMode.Write, | |
- leaveOpen: true | |
- ); | |
- | |
- // TODO: write newline per 48 bytes | |
- base64Stream.Write(key, 0, key.Length); | |
- base64Stream.Close(); | |
- | |
- writer.WriteLine(); | |
- | |
- writer.WriteLine(footer); | |
- writer.Close(); | |
- | |
- stream.Position = 0L; | |
- | |
- using var reader = new StreamReader(stream, Encoding.ASCII); | |
- | |
- return reader.ReadToEnd(); | |
- } | |
-#endif | |
-} | |
diff --git a/src/Smdn.TPSmartHomeDevices/System.Threading.Tasks/ValueTaskShim.cs b/src/Smdn.TPSmartHomeDevices/System.Threading.Tasks/ValueTaskShim.cs | |
deleted file mode 100644 | |
index 36d6b7a..0000000 | |
--- a/src/Smdn.TPSmartHomeDevices/System.Threading.Tasks/ValueTaskShim.cs | |
+++ /dev/null | |
@@ -1,17 +0,0 @@ | |
-// SPDX-FileCopyrightText: 2022 smdn <smdn@smdn.jp> | |
-// SPDX-License-Identifier: MIT | |
-namespace System.Threading.Tasks; | |
- | |
-internal static class ValueTaskShim { | |
-#if !SYSTEM_THREADING_TASKS_VALUETASK_FROMCANCELED | |
- public static ValueTask FromCanceled(CancellationToken cancellationToken) | |
- => new(Task.FromCanceled(cancellationToken)); | |
- | |
- public static ValueTask<TResult> FromCanceled<TResult>(CancellationToken cancellationToken) | |
- => new(Task.FromCanceled<TResult>(cancellationToken)); | |
-#endif | |
- | |
-#if !SYSTEM_THREADING_TASKS_VALUETASK_FROMRESULT | |
- public static ValueTask<TResult> FromResult<TResult>(TResult result) => new(result); | |
-#endif | |
-} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment