main/Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview3
Created
January 8, 2024 07:08
-
-
Save smdn/3c7395edf3047a57c17d5bea92fe647e to your computer and use it in GitHub Desktop.
Smdn.TPSmartHomeDevices.Tapo 2.0.0-preview3 Release Notes
- 2024-01-08 update CompatibilitySuppressions.xml
- 2024-01-08 bump Smdn.TPSmartHomeDevices.Primitives up to 1.1.0-preview1
- 2024-01-08 update package version
- 2024-01-08 improve XML comment docs
- 2024-01-08 add TapoCredentailProviderServiceCollectionExtensions.AddTapoBase64EncodedKlapCredentialFromEnvironmentVariable
- 2024-01-08 add ITapoKlapCredential so that ITapoCredentail can be implemented and be obtaineded separately
- 2024-01-08 throw InvalidOperationException when the credential for KLAP authentication is not found as well as authentication in secure pass through
- 2024-01-07 disable warning IDE0060 for the explicit interface member implementations
- 2024-01-07 make optional arguments mandatory for interface members and their implementations
- 2024-01-07 add ColorModelUtils.ConvertColorTemperatureToRgb, ported from the pseudo code described in https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html, CC BY-SA 4.0
- 2024-01-06 define and implement interfaces that provide access to common device functionalities
- 2024-01-04 fix typo
- 2024-01-04 use target framework net8.0 instead of net7.0
- 2024-01-04 fix or suppress warning CA1859
- 2024-01-04 fix or suppress warning IDE0055
- 2024-01-04 use SHA256.TryHashData if available
- 2024-01-04 fix warning IDE1006: Naming rule violation
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.Tapo/Smdn.TPSmartHomeDevices.Tapo-net6.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net6.0.apilist.cs | |
index 44adf8f..916c0ac 100644 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net6.0.apilist.cs | |
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net6.0.apilist.cs | |
@@ -1,534 +1,554 @@ | |
-// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview2) | |
+// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview3) | |
// Name: Smdn.TPSmartHomeDevices.Tapo | |
// AssemblyVersion: 2.0.0.0 | |
-// InformationalVersion: 2.0.0-preview2+297c36b69acac89037a580a95eda0233f9529f71 | |
+// InformationalVersion: 2.0.0-preview3+431906bdfe9a3cf559bfddbf5593f2ab1e266f1b | |
// 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 | |
// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.1.0, Culture=neutral | |
-// Smdn.TPSmartHomeDevices.Primitives, Version=1.0.0.0, Culture=neutral | |
+// Smdn.TPSmartHomeDevices.Primitives, Version=1.1.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.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.TPSmartHomeDevices; | |
using Smdn.TPSmartHomeDevices.Tapo; | |
using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
+ public class L530 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L530 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L530(IDeviceEndPoint deviceEndPoint, 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) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class L900 : TapoDevice { | |
+ public class L900 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L900 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L900(IDeviceEndPoint deviceEndPoint, 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 ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class P105 : TapoDevice { | |
+ public class P105 : | |
+ TapoDevice, | |
+ ISmartPlug | |
+ { | |
public static P105 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public P105(IDeviceEndPoint deviceEndPoint, 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 AddTapoBase64EncodedKlapCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarBase64KlapLocalAuthHash) {} | |
public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
public static IServiceCollection AddTapoCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarUsername, string envVarPassword) {} | |
public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
} | |
public class TapoDevice : | |
IDisposable, | |
ITapoCredentialIdentity | |
{ | |
public static TapoDevice Create(IDeviceEndPoint deviceEndPoint, 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) {} | |
public static TapoDevice Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, TapoDeviceExceptionHandler? exceptionHandler, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider) {} | |
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) {} | |
[MemberNotNullWhen(false, "deviceEndPoint")] | |
protected bool IsDisposed { [MemberNotNullWhen(false, "deviceEndPoint")] get; } | |
public TapoSession? Session { 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<TDeviceInfo> GetDeviceInfoAsync<TDeviceInfo>(CancellationToken cancellationToken = default) {} | |
public ValueTask<TResult> GetDeviceInfoAsync<TDeviceInfo, TResult>(Func<TDeviceInfo, TResult> composeResult, CancellationToken cancellationToken = default) {} | |
public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
public ValueTask SetDeviceInfoAsync<TDeviceInfo>(TDeviceInfo deviceInfo, CancellationToken cancellationToken = default) {} | |
public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
public override string? ToString() {} | |
public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
} | |
public abstract class TapoDeviceExceptionHandler { | |
internal protected static readonly TapoDeviceExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.TapoDeviceDefaultExceptionHandler" | |
protected TapoDeviceExceptionHandler() {} | |
public abstract TapoDeviceExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
} | |
public static class TapoDeviceExceptionHandlerServiceCollectionExtensions { | |
public static IServiceCollection AddTapoDeviceExceptionHandler(this IServiceCollection services, TapoDeviceExceptionHandler exceptionHandler) {} | |
} | |
public class TapoDeviceInfo { | |
public TapoDeviceInfo() {} | |
[JsonPropertyName("avatar")] | |
public string? Avatar { get; init; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("fw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("hw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("device_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("oem_id")] | |
public byte[]? 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, int rawErrorCode) {} | |
public int RawErrorCode { 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; } | |
} | |
public static class TapoSessionProtocolSelectorServiceCollectionExtensions { | |
public static IServiceCollection AddTapoProtocolSelector(this IServiceCollection services, Func<TapoDevice, TapoSessionProtocol?> selectProtocol) {} | |
} | |
public readonly struct TapoDeviceExceptionHandling { | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling RetryAfterReestablishSession; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
public static TapoDeviceExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReestablishSession = false) {} | |
public TimeSpan RetryAfter { get; init; } | |
public bool ShouldInvalidateEndPoint { get; init; } | |
public bool ShouldReestablishSession { get; init; } | |
public bool ShouldRetry { get; init; } | |
public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
public override string ToString() {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
public interface ITapoCredential : IDisposable { | |
- int HashPassword(HashAlgorithm algorithm, Span<byte> destination); | |
- int HashUsername(HashAlgorithm algorithm, Span<byte> destination); | |
void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
} | |
public interface ITapoCredentialIdentity { | |
} | |
public interface ITapoCredentialProvider { | |
ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
+ ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity); | |
+ } | |
+ | |
+ public interface ITapoKlapCredential : IDisposable { | |
+ void WriteLocalAuthHash(Span<byte> destination); | |
} | |
public static class TapoCredentials { | |
public const int HexSHA1HashSizeInBytes = 40; | |
public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryComputeKlapAuthHash(ITapoCredential credential, Span<byte> destination, out int bytesWritten) {} | |
+ public static bool TryComputeKlapLocalAuthHash(ReadOnlySpan<byte> username, ReadOnlySpan<byte> password, Span<byte> destination, out int bytesWritten) {} | |
public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Json { | |
public sealed class TapoBase16ByteArrayJsonConverter : JsonConverter<byte[]> { | |
public TapoBase16ByteArrayJsonConverter() {} | |
public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, byte[]? @value, JsonSerializerOptions options) {} | |
} | |
public sealed class TapoBase64StringJsonConverter : JsonConverter<string> { | |
public TapoBase64StringJsonConverter() {} | |
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, string? @value, JsonSerializerOptions options) {} | |
} | |
public sealed class TapoIPAddressJsonConverter : JsonConverter<IPAddress> { | |
public TapoIPAddressJsonConverter() {} | |
public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, IPAddress @value, JsonSerializerOptions options) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
public interface ITapoPassThroughRequest : ITapoRequest { | |
} | |
public interface ITapoPassThroughResponse : ITapoResponse { | |
} | |
public interface ITapoRequest { | |
string Method { get; } | |
} | |
public interface ITapoResponse { | |
int ErrorCode { get; } | |
} | |
public enum TapoSessionProtocol : int { | |
Klap = 1, | |
SecurePassThrough = 0, | |
} | |
public static class HashAlgorithmExtensions { | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, ReadOnlySpan<byte> source3, out int bytesWritten) {} | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, out int bytesWritten) {} | |
} | |
public class KlapEncryptionAlgorithm { | |
public KlapEncryptionAlgorithm(ReadOnlySpan<byte> localSeed, ReadOnlySpan<byte> remoteSeed, ReadOnlySpan<byte> userHash) {} | |
public ReadOnlySpan<byte> IV { get; } | |
public ReadOnlySpan<byte> Key { get; } | |
public int SequenceNumber { get; } | |
public ReadOnlySpan<byte> Signature { get; } | |
public void Decrypt(ReadOnlySpan<byte> encryptedText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
public int Encrypt(ReadOnlySpan<byte> rawText, IBufferWriter<byte> destination) {} | |
public void Encrypt(ReadOnlySpan<byte> rawText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
} | |
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 static IHttpClientFactory DefaultHttpClientFactory { get; } | |
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 ValueTask AuthenticateAsync(TapoSessionProtocol protocol, 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 : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
} | |
public abstract class TapoSession : IDisposable { | |
public DateTime ExpiresOn { get; } | |
public bool HasExpired { get; } | |
public string? SessionId { get; } | |
public abstract string? Token { get; } | |
protected virtual void Dispose(bool disposing) {} | |
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 abstract class TapoSessionProtocolSelector { | |
protected TapoSessionProtocolSelector() {} | |
public abstract TapoSessionProtocol? SelectProtocol(TapoDevice device); | |
} | |
public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
[JsonPropertyName("method")] | |
[JsonPropertyOrder(0)] | |
public string Method { get; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
public readonly struct GetDeviceInfoResponse<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
public readonly struct HandshakeRequest : ITapoRequest { | |
public readonly struct RequestParameters { | |
[JsonPropertyName("key")] | |
public string Key { get; init; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
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 { | |
[JsonPropertyName("key")] | |
public string? Key { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int 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 { | |
[JsonPropertyName("token")] | |
public string Token { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public LoginDeviceResponse.ResponseResult Result { get; init; } | |
} | |
public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
public readonly struct RequestParams where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
[JsonPropertyName("request")] | |
public TPassThroughRequest PassThroughRequest { get; init; } | |
} | |
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 where TPassThroughResponse : notnull, ITapoPassThroughResponse { | |
[JsonPropertyName("response")] | |
public TPassThroughResponse PassThroughResponse { get; init; } | |
} | |
public SecurePassThroughResponse(int errorCode, TPassThroughResponse passThroughResponse) {} | |
[JsonPropertyName("error_code")] | |
public int 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<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
} | |
-// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0. | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.3.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.Tapo/Smdn.TPSmartHomeDevices.Tapo-net7.0.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net8.0.apilist.cs | |
similarity index 90% | |
rename from doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net7.0.apilist.cs | |
rename to doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net8.0.apilist.cs | |
index a79ac2d..83bbce3 100644 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net7.0.apilist.cs | |
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-net8.0.apilist.cs | |
@@ -1,532 +1,552 @@ | |
-// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview2) | |
+// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview3) | |
// Name: Smdn.TPSmartHomeDevices.Tapo | |
// AssemblyVersion: 2.0.0.0 | |
-// InformationalVersion: 2.0.0-preview2+297c36b69acac89037a580a95eda0233f9529f71 | |
-// TargetFramework: .NETCoreApp,Version=v7.0 | |
+// InformationalVersion: 2.0.0-preview3+431906bdfe9a3cf559bfddbf5593f2ab1e266f1b | |
+// TargetFramework: .NETCoreApp,Version=v8.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 | |
// Smdn.Fundamental.PrintableEncoding.Hexadecimal, Version=3.0.1.0, Culture=neutral | |
-// Smdn.TPSmartHomeDevices.Primitives, 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.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 | |
+// Smdn.TPSmartHomeDevices.Primitives, Version=1.1.0.0, Culture=neutral | |
+// System.Collections, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.ComponentModel, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Linq, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Memory, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Net.Http, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Net.Http.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Net.NetworkInformation, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Net.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Security.Cryptography, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a | |
+// System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51 | |
+// System.Text.Json, Version=8.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.TPSmartHomeDevices; | |
using Smdn.TPSmartHomeDevices.Tapo; | |
using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
+ public class L530 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L530 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L530(IDeviceEndPoint deviceEndPoint, 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) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class L900 : TapoDevice { | |
+ public class L900 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L900 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L900(IDeviceEndPoint deviceEndPoint, 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 ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class P105 : TapoDevice { | |
+ public class P105 : | |
+ TapoDevice, | |
+ ISmartPlug | |
+ { | |
public static P105 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public P105(IDeviceEndPoint deviceEndPoint, 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 AddTapoBase64EncodedKlapCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarBase64KlapLocalAuthHash) {} | |
public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
public static IServiceCollection AddTapoCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarUsername, string envVarPassword) {} | |
public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
} | |
public class TapoDevice : | |
IDisposable, | |
ITapoCredentialIdentity | |
{ | |
public static TapoDevice Create(IDeviceEndPoint deviceEndPoint, 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) {} | |
public static TapoDevice Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, TapoDeviceExceptionHandler? exceptionHandler, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider) {} | |
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) {} | |
[MemberNotNullWhen(false, "deviceEndPoint")] | |
protected bool IsDisposed { [MemberNotNullWhen(false, "deviceEndPoint")] get; } | |
public TapoSession? Session { 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<TDeviceInfo> GetDeviceInfoAsync<TDeviceInfo>(CancellationToken cancellationToken = default) {} | |
public ValueTask<TResult> GetDeviceInfoAsync<TDeviceInfo, TResult>(Func<TDeviceInfo, TResult> composeResult, CancellationToken cancellationToken = default) {} | |
public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
public ValueTask SetDeviceInfoAsync<TDeviceInfo>(TDeviceInfo deviceInfo, CancellationToken cancellationToken = default) {} | |
public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
public override string? ToString() {} | |
public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
} | |
public abstract class TapoDeviceExceptionHandler { | |
internal protected static readonly TapoDeviceExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.TapoDeviceDefaultExceptionHandler" | |
protected TapoDeviceExceptionHandler() {} | |
public abstract TapoDeviceExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
} | |
public static class TapoDeviceExceptionHandlerServiceCollectionExtensions { | |
public static IServiceCollection AddTapoDeviceExceptionHandler(this IServiceCollection services, TapoDeviceExceptionHandler exceptionHandler) {} | |
} | |
public class TapoDeviceInfo { | |
public TapoDeviceInfo() {} | |
[JsonPropertyName("avatar")] | |
public string? Avatar { get; init; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("fw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("hw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("device_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("oem_id")] | |
public byte[]? 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, int rawErrorCode) {} | |
public int RawErrorCode { 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; } | |
} | |
public static class TapoSessionProtocolSelectorServiceCollectionExtensions { | |
public static IServiceCollection AddTapoProtocolSelector(this IServiceCollection services, Func<TapoDevice, TapoSessionProtocol?> selectProtocol) {} | |
} | |
public readonly struct TapoDeviceExceptionHandling { | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling RetryAfterReestablishSession; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
public static TapoDeviceExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReestablishSession = false) {} | |
public TimeSpan RetryAfter { get; init; } | |
public bool ShouldInvalidateEndPoint { get; init; } | |
public bool ShouldReestablishSession { get; init; } | |
public bool ShouldRetry { get; init; } | |
public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
public override string ToString() {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
public interface ITapoCredential : IDisposable { | |
- int HashPassword(HashAlgorithm algorithm, Span<byte> destination); | |
- int HashUsername(HashAlgorithm algorithm, Span<byte> destination); | |
void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
} | |
public interface ITapoCredentialIdentity { | |
} | |
public interface ITapoCredentialProvider { | |
ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
+ ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity); | |
+ } | |
+ | |
+ public interface ITapoKlapCredential : IDisposable { | |
+ void WriteLocalAuthHash(Span<byte> destination); | |
} | |
public static class TapoCredentials { | |
public const int HexSHA1HashSizeInBytes = 40; | |
public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryComputeKlapAuthHash(ITapoCredential credential, Span<byte> destination, out int bytesWritten) {} | |
+ public static bool TryComputeKlapLocalAuthHash(ReadOnlySpan<byte> username, ReadOnlySpan<byte> password, Span<byte> destination, out int bytesWritten) {} | |
public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Json { | |
public sealed class TapoBase16ByteArrayJsonConverter : JsonConverter<byte[]> { | |
public TapoBase16ByteArrayJsonConverter() {} | |
public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, byte[]? @value, JsonSerializerOptions options) {} | |
} | |
public sealed class TapoBase64StringJsonConverter : JsonConverter<string> { | |
public TapoBase64StringJsonConverter() {} | |
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, string? @value, JsonSerializerOptions options) {} | |
} | |
public sealed class TapoIPAddressJsonConverter : JsonConverter<IPAddress> { | |
public TapoIPAddressJsonConverter() {} | |
public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
public override void Write(Utf8JsonWriter writer, IPAddress @value, JsonSerializerOptions options) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
public interface ITapoPassThroughRequest : ITapoRequest { | |
} | |
public interface ITapoPassThroughResponse : ITapoResponse { | |
} | |
public interface ITapoRequest { | |
string Method { get; } | |
} | |
public interface ITapoResponse { | |
int ErrorCode { get; } | |
} | |
public enum TapoSessionProtocol : int { | |
Klap = 1, | |
SecurePassThrough = 0, | |
} | |
public static class HashAlgorithmExtensions { | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, ReadOnlySpan<byte> source3, out int bytesWritten) {} | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, out int bytesWritten) {} | |
} | |
public class KlapEncryptionAlgorithm { | |
public KlapEncryptionAlgorithm(ReadOnlySpan<byte> localSeed, ReadOnlySpan<byte> remoteSeed, ReadOnlySpan<byte> userHash) {} | |
public ReadOnlySpan<byte> IV { get; } | |
public ReadOnlySpan<byte> Key { get; } | |
public int SequenceNumber { get; } | |
public ReadOnlySpan<byte> Signature { get; } | |
public void Decrypt(ReadOnlySpan<byte> encryptedText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
public int Encrypt(ReadOnlySpan<byte> rawText, IBufferWriter<byte> destination) {} | |
public void Encrypt(ReadOnlySpan<byte> rawText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
} | |
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 static IHttpClientFactory DefaultHttpClientFactory { get; } | |
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 ValueTask AuthenticateAsync(TapoSessionProtocol protocol, 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 : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
} | |
public abstract class TapoSession : IDisposable { | |
public DateTime ExpiresOn { get; } | |
public bool HasExpired { get; } | |
public string? SessionId { get; } | |
public abstract string? Token { get; } | |
protected virtual void Dispose(bool disposing) {} | |
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 abstract class TapoSessionProtocolSelector { | |
protected TapoSessionProtocolSelector() {} | |
public abstract TapoSessionProtocol? SelectProtocol(TapoDevice device); | |
} | |
public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
[JsonPropertyName("method")] | |
[JsonPropertyOrder(0)] | |
public string Method { get; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
public readonly struct GetDeviceInfoResponse<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
public readonly struct HandshakeRequest : ITapoRequest { | |
public readonly struct RequestParameters { | |
[JsonPropertyName("key")] | |
public string Key { get; init; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
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 { | |
[JsonPropertyName("key")] | |
public string? Key { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int 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 { | |
[JsonPropertyName("token")] | |
public string Token { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public LoginDeviceResponse.ResponseResult Result { get; init; } | |
} | |
public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
public readonly struct RequestParams where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
[JsonPropertyName("request")] | |
public TPassThroughRequest PassThroughRequest { get; init; } | |
} | |
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 where TPassThroughResponse : notnull, ITapoPassThroughResponse { | |
[JsonPropertyName("response")] | |
public TPassThroughResponse PassThroughResponse { get; init; } | |
} | |
public SecurePassThroughResponse(int errorCode, TPassThroughResponse passThroughResponse) {} | |
[JsonPropertyName("error_code")] | |
public int 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<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
} | |
-// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0. | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.3.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.Tapo/Smdn.TPSmartHomeDevices.Tapo-netstandard2.1.apilist.cs b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-netstandard2.1.apilist.cs | |
index c66384e..83d87d9 100644 | |
--- a/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-netstandard2.1.apilist.cs | |
+++ b/doc/api-list/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo-netstandard2.1.apilist.cs | |
@@ -1,522 +1,542 @@ | |
-// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview2) | |
+// Smdn.TPSmartHomeDevices.Tapo.dll (Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview3) | |
// Name: Smdn.TPSmartHomeDevices.Tapo | |
// AssemblyVersion: 2.0.0.0 | |
-// InformationalVersion: 2.0.0-preview2+297c36b69acac89037a580a95eda0233f9529f71 | |
+// InformationalVersion: 2.0.0-preview3+431906bdfe9a3cf559bfddbf5593f2ab1e266f1b | |
// 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.TPSmartHomeDevices.Primitives, Version=1.0.0.0, Culture=neutral | |
+// Smdn.TPSmartHomeDevices.Primitives, Version=1.1.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.TPSmartHomeDevices; | |
using Smdn.TPSmartHomeDevices.Tapo; | |
using Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
using Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
namespace Smdn.TPSmartHomeDevices.Tapo { | |
- public class L530 : TapoDevice { | |
+ public class L530 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L530 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L530(IDeviceEndPoint deviceEndPoint, 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) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class L900 : TapoDevice { | |
+ public class L900 : | |
+ TapoDevice, | |
+ IMulticolorSmartLight | |
+ { | |
public static L900 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public L900(IDeviceEndPoint deviceEndPoint, 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 ValueTask SetColorTemperatureAsync(int colorTemperature, int? brightness = null, CancellationToken cancellationToken = default) {} | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync(int brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorAsync(int hue, int saturation, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync(int colorTemperature, int? brightness, TimeSpan transitionPeriod, CancellationToken cancellationToken) {} | |
} | |
- public class P105 : TapoDevice { | |
+ public class P105 : | |
+ TapoDevice, | |
+ ISmartPlug | |
+ { | |
public static P105 Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
public P105(IDeviceEndPoint deviceEndPoint, 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 AddTapoBase64EncodedKlapCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarBase64KlapLocalAuthHash) {} | |
public static IServiceCollection AddTapoCredential(this IServiceCollection services, string email, string password) {} | |
public static IServiceCollection AddTapoCredentialFromEnvironmentVariable(this IServiceCollection services, string envVarUsername, string envVarPassword) {} | |
public static IServiceCollection AddTapoCredentialProvider(this IServiceCollection services, ITapoCredentialProvider credentialProvider) {} | |
} | |
public class TapoDevice : | |
IDisposable, | |
ITapoCredentialIdentity | |
{ | |
public static TapoDevice Create(IDeviceEndPoint deviceEndPoint, 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) {} | |
public static TapoDevice Create<TAddress>(TAddress deviceAddress, IServiceProvider serviceProvider, ITapoCredentialProvider? credential = null) where TAddress : notnull {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IDeviceEndPoint deviceEndPoint, ITapoCredentialProvider? credential, TapoDeviceExceptionHandler? exceptionHandler, IServiceProvider? serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, IServiceProvider serviceProvider) {} | |
protected TapoDevice(IPAddress ipAddress, string email, string password, IServiceProvider? serviceProvider) {} | |
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) {} | |
protected bool IsDisposed { get; } | |
public TapoSession? Session { 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<TDeviceInfo> GetDeviceInfoAsync<TDeviceInfo>(CancellationToken cancellationToken = default) {} | |
public ValueTask<TResult> GetDeviceInfoAsync<TDeviceInfo, TResult>(Func<TDeviceInfo, TResult> composeResult, CancellationToken cancellationToken = default) {} | |
public ValueTask<TapoDeviceInfo> GetDeviceInfoAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<bool> GetOnOffStateAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask<EndPoint> ResolveEndPointAsync(CancellationToken cancellationToken = default) {} | |
protected ValueTask SendRequestAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
protected ValueTask<TResult> SendRequestAsync<TRequest, TResponse, TResult>(TRequest request, Func<TResponse, TResult> composeResult, CancellationToken cancellationToken = default) where TRequest : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
public ValueTask SetDeviceInfoAsync<TDeviceInfo>(TDeviceInfo deviceInfo, CancellationToken cancellationToken = default) {} | |
public ValueTask SetOnOffStateAsync(bool newOnOffState, CancellationToken cancellationToken = default) {} | |
public override string? ToString() {} | |
public ValueTask TurnOffAsync(CancellationToken cancellationToken = default) {} | |
public ValueTask TurnOnAsync(CancellationToken cancellationToken = default) {} | |
} | |
public abstract class TapoDeviceExceptionHandler { | |
internal protected static readonly TapoDeviceExceptionHandler Default; // = "Smdn.TPSmartHomeDevices.Tapo.TapoDeviceDefaultExceptionHandler" | |
protected TapoDeviceExceptionHandler() {} | |
public abstract TapoDeviceExceptionHandling DetermineHandling(TapoDevice device, Exception exception, int attempt, ILogger? logger); | |
} | |
public static class TapoDeviceExceptionHandlerServiceCollectionExtensions { | |
public static IServiceCollection AddTapoDeviceExceptionHandler(this IServiceCollection services, TapoDeviceExceptionHandler exceptionHandler) {} | |
} | |
public class TapoDeviceInfo { | |
public TapoDeviceInfo() {} | |
[JsonPropertyName("avatar")] | |
public string? Avatar { get; init; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("fw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("hw_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("device_id")] | |
public byte[]? 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; } | |
[JsonConverter(typeof(TapoBase16ByteArrayJsonConverter))] | |
[JsonPropertyName("oem_id")] | |
public byte[]? 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, int rawErrorCode) {} | |
public int RawErrorCode { 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; } | |
} | |
public static class TapoSessionProtocolSelectorServiceCollectionExtensions { | |
public static IServiceCollection AddTapoProtocolSelector(this IServiceCollection services, Func<TapoDevice, TapoSessionProtocol?> selectProtocol) {} | |
} | |
public readonly struct TapoDeviceExceptionHandling { | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndRetry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling InvalidateEndPointAndThrow; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=True}" | |
public static readonly TapoDeviceExceptionHandling Retry; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling RetryAfterReestablishSession; // = "{ShouldRetry=True, RetryAfter=00:00:00, ShouldReestablishSession=True, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling Throw; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=False, ShouldInvalidateEndPoint=False}" | |
public static readonly TapoDeviceExceptionHandling ThrowAsTapoProtocolException; // = "{ShouldRetry=False, RetryAfter=00:00:00, ShouldReestablishSession=False, ShouldWrapIntoTapoProtocolException=True, ShouldInvalidateEndPoint=False}" | |
public static TapoDeviceExceptionHandling CreateRetry(TimeSpan retryAfter, bool shouldReestablishSession = false) {} | |
public TimeSpan RetryAfter { get; init; } | |
public bool ShouldInvalidateEndPoint { get; init; } | |
public bool ShouldReestablishSession { get; init; } | |
public bool ShouldRetry { get; init; } | |
public bool ShouldWrapIntoTapoProtocolException { get; init; } | |
public override string ToString() {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Credentials { | |
public interface ITapoCredential : IDisposable { | |
- int HashPassword(HashAlgorithm algorithm, Span<byte> destination); | |
- int HashUsername(HashAlgorithm algorithm, Span<byte> destination); | |
[...] <unknown> WritePasswordPropertyValue(...); | |
[...] <unknown> WriteUsernamePropertyValue(...); | |
} | |
public interface ITapoCredentialIdentity { | |
} | |
public interface ITapoCredentialProvider { | |
ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
+ ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity); | |
+ } | |
+ | |
+ public interface ITapoKlapCredential : IDisposable { | |
+ void WriteLocalAuthHash(Span<byte> destination); | |
} | |
public static class TapoCredentials { | |
public const int HexSHA1HashSizeInBytes = 40; | |
public static string ToBase64EncodedSHA1DigestString(ReadOnlySpan<char> str) {} | |
public static string ToBase64EncodedString(ReadOnlySpan<char> str) {} | |
- public static bool TryComputeKlapAuthHash(ITapoCredential credential, Span<byte> destination, out int bytesWritten) {} | |
+ public static bool TryComputeKlapLocalAuthHash(ReadOnlySpan<byte> username, ReadOnlySpan<byte> password, Span<byte> destination, out int bytesWritten) {} | |
public static bool TryConvertToHexSHA1Hash(ReadOnlySpan<byte> input, Span<byte> destination, out int bytesWritten) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Json { | |
public sealed class TapoBase16ByteArrayJsonConverter : JsonConverter<byte[]> { | |
public TapoBase16ByteArrayJsonConverter() {} | |
public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
[...] public override <unknown> Write(...) {} | |
} | |
public sealed class TapoBase64StringJsonConverter : JsonConverter<string> { | |
public TapoBase64StringJsonConverter() {} | |
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
[...] public override <unknown> Write(...) {} | |
} | |
public sealed class TapoIPAddressJsonConverter : JsonConverter<IPAddress> { | |
public TapoIPAddressJsonConverter() {} | |
public override IPAddress? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {} | |
[...] public override <unknown> Write(...) {} | |
} | |
} | |
namespace Smdn.TPSmartHomeDevices.Tapo.Protocol { | |
public interface ITapoPassThroughRequest : ITapoRequest { | |
} | |
public interface ITapoPassThroughResponse : ITapoResponse { | |
} | |
public interface ITapoRequest { | |
string Method { get; } | |
} | |
public interface ITapoResponse { | |
int ErrorCode { get; } | |
} | |
public enum TapoSessionProtocol : int { | |
Klap = 1, | |
SecurePassThrough = 0, | |
} | |
public static class HashAlgorithmExtensions { | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, ReadOnlySpan<byte> source3, out int bytesWritten) {} | |
public static bool TryComputeHash(this HashAlgorithm algorithm, Span<byte> destination, ReadOnlySpan<byte> source0, ReadOnlySpan<byte> source1, ReadOnlySpan<byte> source2, out int bytesWritten) {} | |
} | |
public class KlapEncryptionAlgorithm { | |
public KlapEncryptionAlgorithm(ReadOnlySpan<byte> localSeed, ReadOnlySpan<byte> remoteSeed, ReadOnlySpan<byte> userHash) {} | |
public ReadOnlySpan<byte> IV { get; } | |
public ReadOnlySpan<byte> Key { get; } | |
public int SequenceNumber { get; } | |
public ReadOnlySpan<byte> Signature { get; } | |
public void Decrypt(ReadOnlySpan<byte> encryptedText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
public int Encrypt(ReadOnlySpan<byte> rawText, IBufferWriter<byte> destination) {} | |
public void Encrypt(ReadOnlySpan<byte> rawText, int sequenceNumber, IBufferWriter<byte> destination) {} | |
} | |
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 static IHttpClientFactory DefaultHttpClientFactory { get; } | |
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 ValueTask AuthenticateAsync(TapoSessionProtocol protocol, 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 : notnull, ITapoPassThroughRequest where TResponse : ITapoPassThroughResponse {} | |
} | |
public abstract class TapoSession : IDisposable { | |
public DateTime ExpiresOn { get; } | |
public bool HasExpired { get; } | |
public string? SessionId { get; } | |
public abstract string? Token { get; } | |
protected virtual void Dispose(bool disposing) {} | |
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 abstract class TapoSessionProtocolSelector { | |
protected TapoSessionProtocolSelector() {} | |
public abstract TapoSessionProtocol? SelectProtocol(TapoDevice device); | |
} | |
public readonly struct GetDeviceInfoRequest : ITapoPassThroughRequest { | |
[JsonPropertyName("method")] | |
[JsonPropertyOrder(0)] | |
public string Method { get; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
public readonly struct GetDeviceInfoResponse<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
public readonly struct HandshakeRequest : ITapoRequest { | |
public readonly struct RequestParameters { | |
[JsonPropertyName("key")] | |
public string Key { get; init; } | |
[JsonPropertyName("requestTimeMils")] | |
public long RequestTimeMilliseconds { get; } | |
} | |
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 { | |
[JsonPropertyName("key")] | |
public string? Key { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int 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 { | |
[JsonPropertyName("token")] | |
public string Token { get; init; } | |
} | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public LoginDeviceResponse.ResponseResult Result { get; init; } | |
} | |
public readonly struct SecurePassThroughRequest<TPassThroughRequest> : ITapoRequest where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
public readonly struct RequestParams where TPassThroughRequest : notnull, ITapoPassThroughRequest { | |
[JsonPropertyName("request")] | |
public TPassThroughRequest PassThroughRequest { get; init; } | |
} | |
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 where TPassThroughResponse : notnull, ITapoPassThroughResponse { | |
[JsonPropertyName("response")] | |
public TPassThroughResponse PassThroughResponse { get; init; } | |
} | |
public SecurePassThroughResponse(int errorCode, TPassThroughResponse passThroughResponse) {} | |
[JsonPropertyName("error_code")] | |
public int 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<TResult> : ITapoPassThroughResponse { | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
[JsonPropertyName("result")] | |
public TResult Result { get; init; } | |
} | |
} | |
-// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.2.2.0. | |
+// API list generated by Smdn.Reflection.ReverseGenerating.ListApi.MSBuild.Tasks v1.3.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.Tapo.latest.nuspec | |
+++ Smdn.TPSmartHomeDevices.Tapo.2.0.0-preview3.nuspec | |
@@ -1,39 +1,39 @@ | |
<?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.Tapo</id> | |
- <version>2.0.0-preview2</version> | |
+ <version>2.0.0-preview3</version> | |
<title>Smdn.TPSmartHomeDevices.Tapo</title> | |
<authors>smdn</authors> | |
<license type="expression">GPL-3.0-or-later</license> | |
<licenseUrl>https://licenses.nuget.org/GPL-3.0-or-later</licenseUrl> | |
<icon>Smdn.TPSmartHomeDevices.Tapo.png</icon> | |
<readme>README.md</readme> | |
<projectUrl>https://github.com/smdn/Smdn.TPSmartHomeDevices/</projectUrl> | |
<description>Provides APIs for operating Tapo devices, the TP-Link smart home devices.</description> | |
- <releaseNotes>https://github.com/smdn/Smdn.TPSmartHomeDevices/releases/tag/releases%2FSmdn.TPSmartHomeDevices.Tapo-2.0.0-preview2</releaseNotes> | |
+ <releaseNotes>https://github.com/smdn/Smdn.TPSmartHomeDevices/releases/tag/releases%2FSmdn.TPSmartHomeDevices.Tapo-2.0.0-preview3</releaseNotes> | |
<copyright>Copyright © 2023 smdn</copyright> | |
<tags>smdn.jp tplink-tapo,tapo,L530,L900,P105,smarthome,homeautomation,smartdevice</tags> | |
- <repository type="git" url="https://github.com/smdn/Smdn.TPSmartHomeDevices" branch="main" commit="297c36b69acac89037a580a95eda0233f9529f71" /> | |
+ <repository type="git" url="https://github.com/smdn/Smdn.TPSmartHomeDevices" commit="431906bdfe9a3cf559bfddbf5593f2ab1e266f1b" /> | |
<dependencies> | |
<group targetFramework="net6.0"> | |
<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.TPSmartHomeDevices.Primitives" version="[1.0.0, 2.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Primitives" version="1.1.0-preview1" exclude="Build,Analyzers" /> | |
<dependency id="System.Net.Http.Json" version="6.0.0" exclude="Build,Analyzers" /> | |
</group> | |
- <group targetFramework="net7.0"> | |
+ <group targetFramework="net8.0"> | |
<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.TPSmartHomeDevices.Primitives" version="[1.0.0, 2.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Primitives" version="1.1.0-preview1" exclude="Build,Analyzers" /> | |
<dependency id="System.Net.Http.Json" version="6.0.0" exclude="Build,Analyzers" /> | |
</group> | |
<group targetFramework=".NETStandard2.1"> | |
<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.TPSmartHomeDevices.Primitives" version="[1.0.0, 2.0.0)" exclude="Build,Analyzers" /> | |
+ <dependency id="Smdn.TPSmartHomeDevices.Primitives" version="1.1.0-preview1" exclude="Build,Analyzers" /> | |
<dependency id="System.Net.Http.Json" version="6.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.Tapo/CompatibilitySuppressions.xml b/src/Smdn.TPSmartHomeDevices.Tapo/CompatibilitySuppressions.xml | |
index 22b8a0e..331f091 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/CompatibilitySuppressions.xml | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/CompatibilitySuppressions.xml | |
@@ -1,4 +1,5 @@ | |
<?xml version="1.0" encoding="utf-8"?> | |
+<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> | |
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> | |
<Suppression> | |
<DiagnosticId>CP0002</DiagnosticId> | |
@@ -8,57 +9,36 @@ | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
</Suppression> | |
<Suppression> | |
- <DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashPassword(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
- <Left>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
- <Right>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
- <IsBaselineSuppression>true</IsBaselineSuppression> | |
- </Suppression> | |
- <Suppression> | |
- <DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashUsername(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
- <Left>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
+ <DiagnosticId>CP0002</DiagnosticId> | |
+ <Target>M:Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoSession.get_RequestPathAndQuery</Target> | |
+ <Left>lib/net7.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
<Right>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
</Suppression> | |
<Suppression> | |
<DiagnosticId>CP0002</DiagnosticId> | |
<Target>M:Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoSession.get_RequestPathAndQuery</Target> | |
- <Left>lib/net7.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
- <Right>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
+ <Left>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
+ <Right>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
</Suppression> | |
<Suppression> | |
<DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashPassword(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
- <Left>lib/net7.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
+ <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialProvider.GetKlapCredential(Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialIdentity)</Target> | |
+ <Left>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
<Right>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
</Suppression> | |
<Suppression> | |
<DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashUsername(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
+ <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialProvider.GetKlapCredential(Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialIdentity)</Target> | |
<Left>lib/net7.0/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
<Right>lib/net6.0/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
</Suppression> | |
- <Suppression> | |
- <DiagnosticId>CP0002</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Protocol.TapoSession.get_RequestPathAndQuery</Target> | |
- <Left>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
- <Right>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
- <IsBaselineSuppression>true</IsBaselineSuppression> | |
- </Suppression> | |
- <Suppression> | |
- <DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashPassword(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
- <Left>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
- <Right>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
- <IsBaselineSuppression>true</IsBaselineSuppression> | |
- </Suppression> | |
<Suppression> | |
<DiagnosticId>CP0006</DiagnosticId> | |
- <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredential.HashUsername(System.Security.Cryptography.HashAlgorithm,System.Span{System.Byte})</Target> | |
+ <Target>M:Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialProvider.GetKlapCredential(Smdn.TPSmartHomeDevices.Tapo.Credentials.ITapoCredentialIdentity)</Target> | |
<Left>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Left> | |
<Right>lib/netstandard2.1/Smdn.TPSmartHomeDevices.Tapo.dll</Right> | |
<IsBaselineSuppression>true</IsBaselineSuppression> | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs | |
index 17695dd..b9a7834 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredential.cs | |
@@ -1,7 +1,6 @@ | |
// SPDX-FileCopyrightText: 2023 smdn <smdn@smdn.jp> | |
// SPDX-License-Identifier: MIT | |
using System; | |
-using System.Security.Cryptography; | |
using System.Text.Json; | |
namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
@@ -9,6 +8,8 @@ namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
/// <summary> | |
/// Provides a mechanism for abstracting credentials used for authentication in Tapo's communication protocol. | |
/// </summary> | |
+/// <seealso cref="ITapoCredentialProvider"/> | |
+/// <seealso cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> | |
public interface ITapoCredential : IDisposable { | |
/// <summary> | |
/// Writes the password corresponding to this credential into a JSON property. | |
@@ -18,6 +19,7 @@ public interface ITapoCredential : IDisposable { | |
/// Also, the password must be written as a BASE64-encoded value. | |
/// </remarks> | |
/// <param name="writer">The <see cref="Utf8JsonWriter"/> currently pointing to where the property value is to be written.</param> | |
+ /// <seealso cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> | |
/// <seealso cref="Protocol.LoginDeviceRequest"/> | |
/// <seealso cref="TapoCredentials.ToBase64EncodedString(ReadOnlySpan{char})"/> | |
void WritePasswordPropertyValue(Utf8JsonWriter writer); | |
@@ -30,17 +32,8 @@ public interface ITapoCredential : IDisposable { | |
/// Also, the user name must be written as a BASE64-encoded value of the its SHA-1 digest. | |
/// </remarks> | |
/// <param name="writer">The <see cref="Utf8JsonWriter"/> currently pointing to where the property value is to be written.</param> | |
+ /// <seealso cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> | |
/// <seealso cref="Protocol.LoginDeviceRequest"/> | |
/// <seealso cref="TapoCredentials.ToBase64EncodedSHA1DigestString(ReadOnlySpan{char})"/> | |
void WriteUsernamePropertyValue(Utf8JsonWriter writer); | |
- | |
- /// <summary> | |
- /// Computes the hash of the password corresponding to this credential with the specific hash algorithm. | |
- /// </summary> | |
- int HashPassword(HashAlgorithm algorithm, Span<byte> destination); | |
- | |
- /// <summary> | |
- /// Computes the hash of the user name corresponding to this credential with the specific hash algorithm. | |
- /// </summary> | |
- int HashUsername(HashAlgorithm algorithm, Span<byte> destination); | |
} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs | |
index b74011d..c9db70e 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoCredentialProvider.cs | |
@@ -3,8 +3,8 @@ | |
namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
/// <summary> | |
-/// Provides a mechanism to select the <see cref="ITapoCredential"/> corresponding to the <see cref="ITapoCredentialIdentity"/> and | |
-/// provide it to the authentication process in Tapo's communication protocol. | |
+/// Provides a mechanism to select the <see cref="ITapoCredential"/> or <see cref="ITapoKlapCredential"/> corresponding to the <see cref="ITapoCredentialIdentity"/> and | |
+/// provide it to the authentication process in Tapo's protocol. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
@@ -16,10 +16,14 @@ namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
/// the <see cref="ITapoCredentialIdentity"/> specified with its parameter is used. | |
/// </para> | |
/// </remarks> | |
-/// <seealso cref="Protocol.TapoClient.AuthenticateAsync(ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
+/// <seealso cref="ITapoCredential"/> | |
+/// <seealso cref="ITapoKlapCredential"/> | |
+/// <seealso cref="ITapoCredentialIdentity"/> | |
+/// <seealso cref="Protocol.TapoSessionProtocol"/> | |
+/// <seealso cref="Protocol.TapoClient.AuthenticateAsync(Protocol.TapoSessionProtocol, ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
public interface ITapoCredentialProvider { | |
/// <summary> | |
- /// Gets the credential corresponding to the specified identity. | |
+ /// Gets the <see cref="ITapoCredential"/> corresponding to the specified identity, used for authentication process in 'secure pass through' protocol, or <c>login_device</c> command. | |
/// </summary> | |
/// <remarks> | |
/// The concrete implementation of this method should return the <see cref="ITapoCredential"/> | |
@@ -31,10 +35,30 @@ public interface ITapoCredentialProvider { | |
/// The <see cref="ITapoCredentialIdentity"/> that is requesting to obtain the | |
/// corresponding <see cref="ITapoCredential"/> from this <see cref="ITapoCredentialProvider"/>. | |
/// </param> | |
+ /// <seealso cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> | |
/// <seealso cref="Protocol.TapoClient.AuthenticateAsync(ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
/// <seealso cref="Protocol.TapoClient.AuthenticateAsync(Protocol.TapoSessionProtocol, ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
/// <seealso cref="Protocol.LoginDeviceRequest"/> | |
/// <seealso cref="Protocol.SecurePassThroughJsonConverterFactory"/> | |
/// <seealso cref="TapoDevice"/> | |
ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
+ | |
+ /// <summary> | |
+ /// Gets the <see cref="ITapoKlapCredential"/> corresponding to the specified identity, used for authentication process in Tapo's KLAP protocol. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// The concrete implementation of this method should return the <see cref="ITapoKlapCredential"/> | |
+ /// selected by <paramref name="identity"/> from the this <see cref="ITapoCredentialProvider"/>. | |
+ /// If <paramref name="identity"/> is <see langword="null"/>, a default credential | |
+ /// that is not specific to a particular <see cref="ITapoCredentialIdentity"/> must be returned. | |
+ /// </remarks> | |
+ /// <param name="identity"> | |
+ /// The <see cref="ITapoCredentialIdentity"/> that is requesting to obtain the | |
+ /// corresponding <see cref="ITapoKlapCredential"/> from this <see cref="ITapoCredentialProvider"/>. | |
+ /// </param> | |
+ /// <seealso cref="Protocol.TapoSessionProtocol.Klap"/> | |
+ /// <seealso cref="Protocol.TapoClient.AuthenticateAsync(ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
+ /// <seealso cref="Protocol.TapoClient.AuthenticateAsync(Protocol.TapoSessionProtocol, ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
+ /// <seealso cref="TapoDevice"/> | |
+ ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity); | |
} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoKlapCredential.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoKlapCredential.cs | |
new file mode 100644 | |
index 0000000..c88da08 | |
--- /dev/null | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/ITapoKlapCredential.cs | |
@@ -0,0 +1,29 @@ | |
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+using System; | |
+ | |
+namespace Smdn.TPSmartHomeDevices.Tapo.Credentials; | |
+ | |
+/// <summary> | |
+/// Provides a mechanism for abstracting credentials used for authentication in Tapo's KLAP protocol. | |
+/// </summary> | |
+/// <seealso cref="ITapoCredentialProvider"/> | |
+/// <seealso cref="Protocol.TapoSessionProtocol.Klap"/> | |
+public interface ITapoKlapCredential : IDisposable { | |
+ /// <summary> | |
+ /// Writes the <c>local_auth_hash</c> corresponding to this credential into a <see cref="Span{Byte}"/>. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// <para> | |
+ /// The <c>local_auth_hash</c> represents a hash value based on the username and password corresponding with the credential, calculated by the following pseudo expression. | |
+ /// <code> | |
+ /// local_auth_hash = SHA256(SHA1(username) + SHA1(password)) | |
+ /// </code> | |
+ /// </para> | |
+ /// </remarks> | |
+ /// <param name="destination">The <see cref="Span{Byte}"/> to where the credential will be written.</param> | |
+ /// <seealso cref="Protocol.TapoSessionProtocol.Klap"/> | |
+ /// <seealso cref="Protocol.TapoClient.AuthenticateAsync(Protocol.TapoSessionProtocol, ITapoCredentialIdentity?, ITapoCredentialProvider, System.Threading.CancellationToken)"/> | |
+ // <seealso cref="TapoCredentials.ToBase64EncodedString(ReadOnlySpan{char})"/> | |
+ void WriteLocalAuthHash(Span<byte> destination); | |
+} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.KLAP.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.KLAP.cs | |
index 529df49..06f1f54 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.KLAP.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.KLAP.cs | |
@@ -26,17 +26,15 @@ partial class TapoCredentials { | |
/// forked from <see href="https://github.com/K4CZP3R/tapo-p100-python">K4CZP3R/tapo-p100-python</see>. | |
/// </remarks> | |
#pragma warning disable CA5350 | |
- public static bool TryComputeKlapAuthHash( | |
- ITapoCredential credential, | |
+ public static bool TryComputeKlapLocalAuthHash( | |
+ ReadOnlySpan<byte> username, | |
+ ReadOnlySpan<byte> password, | |
Span<byte> destination, | |
out int bytesWritten | |
) | |
{ | |
bytesWritten = 0; | |
- if (credential is null) | |
- return false; | |
- | |
if (destination.Length < SHA256HashSizeInBytes) | |
return false; // destination too short | |
@@ -49,35 +47,65 @@ partial class TapoCredentials { | |
authHashInput = ArrayPool<byte>.Shared.Rent(2 * SHA1HashSizeInBytes); | |
// SHA1(username) | |
- using (var sha1 = SHA1.Create()) { | |
- bytesWrittenAuthHashInput += credential.HashUsername( | |
- sha1, | |
- authHashInput.AsSpan(bytesWrittenAuthHashInput, SHA1HashSizeInBytes) | |
+#pragma warning disable SA1114 | |
+#if SYSTEM_SECURITY_CRYPTOGRAPHY_SHA1_TRYHASHDATA | |
+ { | |
+ var retUsernameHash = SHA1.TryHashData( | |
+#else | |
+ using (var sha1ForUsername = SHA1.Create()) { | |
+ var retUsernameHash = sha1ForUsername.TryComputeHash( | |
+#endif | |
+ username, | |
+ authHashInput.AsSpan(bytesWrittenAuthHashInput, SHA1HashSizeInBytes), | |
+ out var bytesWrittenByUsernameHash | |
); | |
+#pragma warning restore SA1114 | |
- if (bytesWrittenAuthHashInput != SHA1HashSizeInBytes) | |
+ if (!retUsernameHash) | |
+ return false; | |
+ if (bytesWrittenByUsernameHash != SHA1HashSizeInBytes) | |
return false; | |
+ | |
+ bytesWrittenAuthHashInput += bytesWrittenByUsernameHash; | |
} | |
// SHA1(password) | |
- using (var sha1 = SHA1.Create()) { | |
- bytesWrittenAuthHashInput += credential.HashPassword( | |
- sha1, | |
- authHashInput.AsSpan(bytesWrittenAuthHashInput, SHA1HashSizeInBytes) | |
+#pragma warning disable SA1114 | |
+#if SYSTEM_SECURITY_CRYPTOGRAPHY_SHA1_TRYHASHDATA | |
+ { | |
+ var retPasswordHash = SHA1.TryHashData( | |
+#else | |
+ using (var sha1ForPassword = SHA1.Create()) { | |
+ var retPasswordHash = sha1ForPassword.TryComputeHash( | |
+#endif | |
+ password, | |
+ authHashInput.AsSpan(bytesWrittenAuthHashInput, SHA1HashSizeInBytes), | |
+ out var bytesWrittenByPasswordHash | |
); | |
+#pragma warning restore SA1114 | |
- if (bytesWrittenAuthHashInput != SHA1HashSizeInBytes * 2) | |
+ if (!retPasswordHash) | |
return false; | |
+ if (bytesWrittenByPasswordHash != SHA1HashSizeInBytes) | |
+ return false; | |
+ | |
+ bytesWrittenAuthHashInput += bytesWrittenByPasswordHash; | |
} | |
+ // SHA256(authHashInput) = SHA256(SHA1(username) + SHA1(password)) | |
+#pragma warning disable SA1114 | |
+#if SYSTEM_SECURITY_CRYPTOGRAPHY_SHA256_TRYHASHDATA | |
+ return SHA256.TryHashData( | |
+#else | |
using var sha256 = SHA256.Create(); | |
- // SHA256(authHashInput) = SHA256(SHA1(username) + SHA1(password)) | |
return sha256.TryComputeHash( | |
+#endif | |
authHashInput.AsSpan(0, bytesWrittenAuthHashInput), | |
destination, | |
out bytesWritten | |
); | |
+#pragma warning restore SA1114 | |
} | |
finally { | |
if (authHashInput is not null) | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.cs | |
index a2e5eb0..c3b8941 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Credentials/TapoCredentials.cs | |
@@ -2,6 +2,7 @@ | |
// SPDX-License-Identifier: MIT | |
using System; | |
using System.Buffers; | |
+using System.Buffers.Text; | |
using System.Security.Cryptography; | |
using System.Text; | |
using System.Text.Json; | |
@@ -126,6 +127,9 @@ public static partial class TapoCredentials { | |
} | |
} | |
+ internal static InvalidOperationException CreateExceptionNoCredentialForIdentity(ITapoCredentialIdentity? identity) | |
+ => new($"Could not get a credential for an identity '{identity?.ToString() ?? "(null)"}'"); | |
+ | |
internal static ITapoCredentialProvider CreateProviderFromPlainText(string email, string password) | |
=> new SingleIdentityStringCredentialProvider( | |
username: email ?? throw new ArgumentNullException(nameof(email)), | |
@@ -160,7 +164,19 @@ public static partial class TapoCredentials { | |
); | |
} | |
- private sealed class SingleIdentityStringCredentialProvider : ITapoCredentialProvider, ITapoCredential { | |
+ internal static ITapoCredentialProvider CreateProviderFromEnvironmentVariables( | |
+ string envVarBase64KlapLocalAuthHash | |
+ ) | |
+ { | |
+ if (string.IsNullOrEmpty(envVarBase64KlapLocalAuthHash)) | |
+ throw new ArgumentException(message: "must be non-empty string", paramName: nameof(envVarBase64KlapLocalAuthHash)); | |
+ | |
+ return new SingleIdentityBase64EncodedKlapLocalAuthHashEnvVarCredentialProvider( | |
+ envVarBase64KlapLocalAuthHash: envVarBase64KlapLocalAuthHash | |
+ ); | |
+ } | |
+ | |
+ private sealed class SingleIdentityStringCredentialProvider : ITapoCredentialProvider, ITapoCredential, ITapoKlapCredential { | |
private readonly byte[] utf8Username; | |
private readonly byte[] utf8Password; | |
private readonly bool isPlainText; | |
@@ -178,6 +194,9 @@ public static partial class TapoCredentials { | |
ITapoCredential ITapoCredentialProvider.GetCredential(ITapoCredentialIdentity? identity) => this; | |
+ ITapoKlapCredential ITapoCredentialProvider.GetKlapCredential(ITapoCredentialIdentity? identity) | |
+ => isPlainText ? this : throw new NotSupportedException("KLAP protocol does not support base64 encoded username and password"); | |
+ | |
void IDisposable.Dispose() { /* nothing to do */ } | |
void ITapoCredential.WritePasswordPropertyValue(Utf8JsonWriter writer) | |
@@ -208,18 +227,49 @@ public static partial class TapoCredentials { | |
} | |
} | |
- int ITapoCredential.HashPassword(HashAlgorithm algorithm, Span<byte> destination) | |
- => algorithm.TryComputeHash(utf8Password, destination, out var bytesWritten) | |
- ? bytesWritten | |
- : 0; | |
+ void ITapoKlapCredential.WriteLocalAuthHash(Span<byte> destination) | |
+ => _ = TryComputeKlapLocalAuthHash(utf8Username, utf8Password, destination, out _); | |
+ } | |
+ | |
+ private abstract class EnvVarCredentialProvider : ITapoCredentialProvider { | |
+ ITapoCredential ITapoCredentialProvider.GetCredential(ITapoCredentialIdentity? identity) => GetCredential(identity); | |
+ | |
+ private protected abstract ITapoCredential GetCredential(ITapoCredentialIdentity? identity); | |
+ | |
+ ITapoKlapCredential ITapoCredentialProvider.GetKlapCredential(ITapoCredentialIdentity? identity) => GetKlapCredential(identity); | |
+ | |
+ private protected abstract ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity); | |
+ | |
+ private protected delegate TResult ReadEnvVarFunc<TResult>(ReadOnlySpan<byte> span, Span<byte> destination); | |
+ | |
+ private protected static TResult ReadEnvVar<TResult>( | |
+ string envVar, | |
+ Span<byte> destination, | |
+ ReadEnvVarFunc<TResult> func | |
+ ) | |
+ { | |
+ byte[]? utf8EncodedEnvVarValue = null; | |
+ | |
+ try { | |
+ var envVarValue = Environment.GetEnvironmentVariable(envVar); | |
+ | |
+ if (string.IsNullOrEmpty(envVarValue)) | |
+ throw new InvalidOperationException($"envvar '{envVar}' not set"); | |
+ | |
+ utf8EncodedEnvVarValue = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetByteCount(envVarValue)); | |
- int ITapoCredential.HashUsername(HashAlgorithm algorithm, Span<byte> destination) | |
- => algorithm.TryComputeHash(utf8Username, destination, out var bytesWritten) | |
- ? bytesWritten | |
- : 0; | |
+ var len = Encoding.UTF8.GetBytes(envVarValue, utf8EncodedEnvVarValue); | |
+ | |
+ return func(utf8EncodedEnvVarValue.AsSpan(0, len), destination); | |
+ } | |
+ finally { | |
+ if (utf8EncodedEnvVarValue is not null) | |
+ ArrayPool<byte>.Shared.Return(utf8EncodedEnvVarValue, clearArray: true); | |
+ } | |
+ } | |
} | |
- private sealed class SingleIdentityEnvVarCredentialProvider : ITapoCredentialProvider, ITapoCredential { | |
+ private sealed class SingleIdentityEnvVarCredentialProvider : EnvVarCredentialProvider, ITapoCredential, ITapoKlapCredential { | |
private readonly string envVarUsername; | |
private readonly string envVarPassword; | |
@@ -232,15 +282,17 @@ public static partial class TapoCredentials { | |
this.envVarPassword = envVarPassword; | |
} | |
- ITapoCredential ITapoCredentialProvider.GetCredential(ITapoCredentialIdentity? identity) => this; | |
+ private protected override ITapoCredential GetCredential(ITapoCredentialIdentity? identity) => this; | |
+ | |
+ private protected override ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity) => this; | |
void IDisposable.Dispose() { /* nothing to do */ } | |
void ITapoCredential.WritePasswordPropertyValue(Utf8JsonWriter writer) | |
=> ReadEnvVar( | |
envVarPassword, | |
- output: Span<None>.Empty, | |
- (utf8Password, output) => { | |
+ destination: default, | |
+ (utf8Password, _) => { | |
writer.WriteBase64StringValue(utf8Password); | |
return default(None); | |
} | |
@@ -249,8 +301,8 @@ public static partial class TapoCredentials { | |
void ITapoCredential.WriteUsernamePropertyValue(Utf8JsonWriter writer) | |
=> ReadEnvVar( | |
envVarUsername, | |
- output: Span<None>.Empty, | |
- (utf8Username, output) => { | |
+ destination: default, | |
+ (utf8Username, discard) => { | |
Span<byte> buffer = stackalloc byte[HexSHA1HashSizeInBytes]; | |
try { | |
@@ -267,50 +319,69 @@ public static partial class TapoCredentials { | |
} | |
); | |
- int ITapoCredential.HashPassword(HashAlgorithm algorithm, Span<byte> destination) | |
- => ReadEnvVar( | |
- envVarPassword, | |
- destination, | |
- (utf8Password, dest) => algorithm.TryComputeHash(utf8Password, dest, out var bytesWritten) | |
- ? bytesWritten | |
- : 0 | |
- ); | |
+ void ITapoKlapCredential.WriteLocalAuthHash(Span<byte> destination) | |
+ { | |
+ byte[]? utf8EncodedUsername = null; | |
+ byte[]? utf8EncodedPassword = null; | |
- int ITapoCredential.HashUsername(HashAlgorithm algorithm, Span<byte> destination) | |
- => ReadEnvVar( | |
- envVarUsername, | |
+ try { | |
+ static (byte[], int) CopyToRentArrayPool(ReadOnlySpan<byte> val, Span<byte> discard) | |
+ { | |
+ var buffer = ArrayPool<byte>.Shared.Rent(val.Length); | |
+ | |
+ val.CopyTo(buffer.AsSpan(0, val.Length)); | |
+ | |
+ return (buffer, val.Length); | |
+ } | |
+ | |
+ (utf8EncodedUsername, var usernameLength) = ReadEnvVar(envVarUsername, default, CopyToRentArrayPool); | |
+ (utf8EncodedPassword, var passwordLength) = ReadEnvVar(envVarPassword, default, CopyToRentArrayPool); | |
+ | |
+ _ = TryComputeKlapLocalAuthHash( | |
+ username: utf8EncodedUsername.AsSpan(0, usernameLength), | |
+ password: utf8EncodedPassword.AsSpan(0, passwordLength), | |
destination, | |
- (utf8Username, dest) => algorithm.TryComputeHash(utf8Username, dest, out var bytesWritten) | |
- ? bytesWritten | |
- : 0 | |
+ out _ | |
); | |
+ } | |
+ finally { | |
+ if (utf8EncodedUsername is not null) | |
+ ArrayPool<byte>.Shared.Return(utf8EncodedUsername, clearArray: true); | |
+ if (utf8EncodedPassword is not null) | |
+ ArrayPool<byte>.Shared.Return(utf8EncodedPassword, clearArray: true); | |
+ } | |
+ } | |
+ } | |
- private delegate TResult ReadEnvVarFunc<T, TOut, TResult>(ReadOnlySpan<T> span, Span<TOut> output); | |
+ private sealed class SingleIdentityBase64EncodedKlapLocalAuthHashEnvVarCredentialProvider : EnvVarCredentialProvider, ITapoKlapCredential { | |
+ private readonly string envVarBase64KlapLocalAuthHash; | |
- private static TResult ReadEnvVar<TOut, TResult>( | |
- string envVar, | |
- Span<TOut> output, | |
- ReadEnvVarFunc<byte, TOut, TResult> func | |
+ public SingleIdentityBase64EncodedKlapLocalAuthHashEnvVarCredentialProvider( | |
+ string envVarBase64KlapLocalAuthHash | |
) | |
{ | |
- byte[]? utf8EncodedEnvVarValue = null; | |
+ this.envVarBase64KlapLocalAuthHash = envVarBase64KlapLocalAuthHash; | |
+ } | |
- try { | |
- var envVarValue = Environment.GetEnvironmentVariable(envVar); | |
+ private protected override ITapoCredential GetCredential(ITapoCredentialIdentity? identity) | |
+ => throw new NotSupportedException($"{nameof(ITapoCredential)} cannot be obtained from this provider."); | |
- if (string.IsNullOrEmpty(envVarValue)) | |
- throw new InvalidOperationException($"envvar '{envVar}' not set"); | |
+ private protected override ITapoKlapCredential GetKlapCredential(ITapoCredentialIdentity? identity) => this; | |
- utf8EncodedEnvVarValue = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetByteCount(envVarValue)); | |
+ void IDisposable.Dispose() { /* nothing to do */ } | |
- var len = Encoding.UTF8.GetBytes(envVarValue, utf8EncodedEnvVarValue); | |
+ void ITapoKlapCredential.WriteLocalAuthHash(Span<byte> destination) | |
+ { | |
+ var ret = ReadEnvVar( | |
+ envVarBase64KlapLocalAuthHash, | |
+ destination, | |
+ static (val, dest) => | |
+ OperationStatus.Done == Base64.DecodeFromUtf8(val, dest, out _, out var bytesWritten, isFinalBlock: true) && | |
+ bytesWritten == SHA256HashSizeInBytes | |
+ ); | |
- return func(utf8EncodedEnvVarValue.AsSpan(0, len), output); | |
- } | |
- finally { | |
- if (utf8EncodedEnvVarValue is not null) | |
- ArrayPool<byte>.Shared.Return(utf8EncodedEnvVarValue, clearArray: true); | |
- } | |
+ if (!ret) | |
+ throw new InvalidOperationException($"The value of the environment variable '{envVarBase64KlapLocalAuthHash}' is either an invalid BASE64 or an invalid length."); | |
} | |
} | |
} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs | |
index 9a021a0..acb0dd8 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/LoginDeviceRequest.TapoCredentialJsonConverter.cs | |
@@ -50,7 +50,7 @@ partial struct LoginDeviceRequest { | |
) | |
{ | |
using var credential = value.GetCredential(identity) | |
- ?? throw new InvalidOperationException($"Could not get a credential for an identity '{identity?.ToString() ?? "(null)"}'"); | |
+ ?? throw TapoCredentials.CreateExceptionNoCredentialForIdentity(identity); | |
writer.WriteStartObject(); | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs | |
index 42b9c6f..b5a42ac 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughJsonConverterFactory.cs | |
@@ -24,10 +24,12 @@ namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
/// <see href="https://github.com/fishbigger/TapoP100">fishbigger/TapoP100</see>, published under the MIT License. | |
/// </remarks> | |
/// <seealso cref="SecurePassThroughInvalidPaddingException"/> | |
+#pragma warning disable IDE0055 | |
public sealed class SecurePassThroughJsonConverterFactory : | |
JsonConverterFactory, | |
IDisposable, | |
SecurePassThroughJsonConverterFactory.IPassThroughObjectJsonConverter | |
+#pragma warning restore IDE0055 | |
{ | |
private interface IPassThroughObjectJsonConverter { | |
void WriteEncryptedValue<TValue>( | |
@@ -90,7 +92,7 @@ public sealed class SecurePassThroughJsonConverterFactory : | |
disposed = true; | |
} | |
- private Stream CreateEncryptingStream(Stream stream) | |
+ private CryptoStream CreateEncryptingStream(Stream stream) | |
=> disposed | |
? throw new ObjectDisposedException(GetType().FullName) | |
: new CryptoStream( | |
@@ -100,7 +102,7 @@ public sealed class SecurePassThroughJsonConverterFactory : | |
leaveOpen: true | |
); | |
- private Stream CreateDecryptingStream(Stream stream) | |
+ private CryptoStream CreateDecryptingStream(Stream stream) | |
=> disposed | |
? throw new ObjectDisposedException(GetType().FullName) | |
: new CryptoStream( | |
@@ -217,12 +219,12 @@ public sealed class SecurePassThroughJsonConverterFactory : | |
} | |
} | |
-#pragma warning disable CA1812 | |
+#pragma warning disable IDE0055, CA1812 | |
private sealed class PassThroughObjectJsonConverter<TPassThroughObject> | |
: JsonConverter<TPassThroughObject> | |
// where TPassThroughObject : ITapoPassThroughRequest or ITapoPassThroughResponse | |
+#pragma warning restore IDE0055, CA1812 | |
{ | |
-#pragma warning restore CA1812 | |
private readonly IPassThroughObjectJsonConverter converter; | |
public PassThroughObjectJsonConverter(IPassThroughObjectJsonConverter converter) | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs | |
index 67d2b2b..73899ab 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughRequest.cs | |
@@ -8,9 +8,11 @@ namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
/// The type that reflects <c>securePassthrough</c> JSON request. | |
/// </summary> | |
/// <typeparam name="TPassThroughRequest">A type that will be serialized to the value of the encapsulated <c>request</c> JSON property.</typeparam> | |
+#pragma warning disable IDE0055 | |
public readonly struct SecurePassThroughRequest<TPassThroughRequest> : | |
ITapoRequest | |
where TPassThroughRequest : notnull, ITapoPassThroughRequest | |
+#pragma warning restore IDE0055 | |
{ | |
[JsonPropertyName("method")] | |
[JsonPropertyOrder(0)] | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs | |
index 337a847..b894063 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/SecurePassThroughResponse.cs | |
@@ -8,9 +8,11 @@ namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
/// The type that reflects <c>securePassthrough</c> JSON response. | |
/// </summary> | |
/// <typeparam name="TPassThroughResponse">A type that will be deserialized from the value of the encapsulated <c>response</c> JSON property.</typeparam> | |
+#pragma warning disable IDE0055 | |
public readonly struct SecurePassThroughResponse<TPassThroughResponse> : | |
ITapoResponse | |
where TPassThroughResponse : ITapoPassThroughResponse | |
+#pragma warning restore IDE0055 | |
{ | |
[JsonPropertyName("error_code")] | |
public int ErrorCode { get; init; } | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.KLAP.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.KLAP.cs | |
index 768b286..8ffedb9 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.KLAP.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.KLAP.cs | |
@@ -50,21 +50,12 @@ partial class TapoClient { | |
// local_auth_hash = SHA256(SHA1(username) + SHA1(password)) | |
var localAuthHash = new byte[SHA256HashSizeInBytes]; | |
- using (var credential = credentialProvider.GetCredential(identity)) { | |
- _ = TapoCredentials.TryComputeKlapAuthHash( | |
- credential, | |
- localAuthHash.AsSpan(0, SHA256HashSizeInBytes), | |
-#if DEBUG | |
- out var bytesWritten | |
-#else | |
- out _ | |
-#endif | |
- ); | |
- | |
-#if DEBUG | |
- if (bytesWritten != SHA256HashSizeInBytes) | |
- throw new InvalidOperationException("invalid legnth of local_auth_hash"); | |
-#endif | |
+ using ( | |
+ var credential = | |
+ credentialProvider.GetKlapCredential(identity) | |
+ ?? throw TapoCredentials.CreateExceptionNoCredentialForIdentity(identity) | |
+ ) { | |
+ credential.WriteLocalAuthHash(localAuthHash.AsSpan(0, SHA256HashSizeInBytes)); | |
} | |
var baseTimeForExpiration = DateTime.Now; | |
@@ -112,8 +103,8 @@ partial class TapoClient { | |
); | |
} | |
- private static readonly Uri requestUriKlapHandshake1 = new("/app/handshake1", UriKind.Relative); | |
- private static readonly Uri requestUriKlapHandshake2 = new("/app/handshake2", UriKind.Relative); | |
+ private static readonly Uri RequestUriKlapHandshake1 = new("/app/handshake1", UriKind.Relative); | |
+ private static readonly Uri RequestUriKlapHandshake2 = new("/app/handshake2", UriKind.Relative); | |
private async | |
ValueTask<( | |
@@ -130,7 +121,7 @@ partial class TapoClient { | |
using var content = new ReadOnlyMemoryContent(localSeed); | |
var (_, (responseHandshake1, sessionId, sessionTimeout)) = await PostAsync( | |
- requestUri: requestUriKlapHandshake1, | |
+ requestUri: RequestUriKlapHandshake1, | |
requestContent: content, | |
processHttpResponseAsync: async response => { | |
var responseHandshake1 = await response | |
@@ -247,7 +238,7 @@ partial class TapoClient { | |
try { | |
_ = await PostAsync<HttpStatusCode?>( | |
- requestUri: requestUriKlapHandshake2, | |
+ requestUri: RequestUriKlapHandshake2, | |
requestContent: content, | |
processHttpResponseAsync: static httpResponse => new(result: httpResponse.StatusCode), | |
cancellationToken: cancellationToken | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.HTTP.SecurePassThrough.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.HTTP.SecurePassThrough.cs | |
index 709781a..c89fff1 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.HTTP.SecurePassThrough.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.HTTP.SecurePassThrough.cs | |
@@ -73,7 +73,7 @@ partial class TapoClient { | |
using var requestContent = JsonContent.Create( | |
inputValue: request, | |
- mediaType: mediaTypeJson, | |
+ mediaType: MediaTypeJson, | |
options: jsonSerializerOptions | |
); | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs | |
index f757364..0145dba 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Http.cs | |
@@ -21,7 +21,7 @@ partial class TapoClient { | |
configureClient: null | |
); | |
- private static readonly MediaTypeHeaderValue mediaTypeJson = new(mediaType: "application/json"); | |
+ private static readonly MediaTypeHeaderValue MediaTypeJson = new(mediaType: "application/json"); | |
public TimeSpan? Timeout { get; set; } | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs | |
index 28d48ea..9aee292 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoHttpClientFactory.cs | |
@@ -7,6 +7,7 @@ using System.Net.Http; | |
namespace Smdn.TPSmartHomeDevices.Tapo.Protocol; | |
internal sealed class TapoHttpClientFactory : IHttpClientFactory { | |
+#pragma warning disable CA1859 | |
private static HttpMessageHandler CreateHandler() | |
=> | |
#if SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER | |
@@ -20,6 +21,7 @@ internal sealed class TapoHttpClientFactory : IHttpClientFactory { | |
MaxConnectionsPerServer = 1, | |
UseCookies = false, | |
}; | |
+#pragma warning restore CA1859 | |
private static HttpMessageHandler DefaultHandler { get; } = CreateHandler(); | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs | |
index 7939400..e7ed4bc 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoSessionCookieUtils.cs | |
@@ -102,9 +102,9 @@ public static class TapoSessionCookieUtils { | |
else | |
timeoutAttributeValue = timeoutAttributeValue.TrimEnd(); // for the case of "TIMEOUT=XXXXXXXXXX" | |
- const NumberStyles timeoutNumberStyles = NumberStyles.AllowTrailingWhite; | |
+ const NumberStyles TimeoutNumberStyles = NumberStyles.AllowTrailingWhite; | |
- if (int.TryParse(timeoutAttributeValue, timeoutNumberStyles, provider: null, out var to)) | |
+ if (int.TryParse(timeoutAttributeValue, TimeoutNumberStyles, provider: null, out var to)) | |
timeout = to; | |
} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.csproj b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.csproj | |
index 4b70b7d..fec4441 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.csproj | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.csproj | |
@@ -4,10 +4,9 @@ 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> | |
+ <TargetFrameworks>net8.0;net6.0;netstandard2.1</TargetFrameworks> | |
<VersionPrefix>2.0.0</VersionPrefix> | |
- <VersionSuffix>preview2</VersionSuffix> | |
+ <VersionSuffix>preview3</VersionSuffix> | |
<PackageValidationBaselineVersion>1.0.0</PackageValidationBaselineVersion> | |
<RootNamespace/> <!-- empty the root namespace so that the namespace is determined only by the directory name, for code style rule IDE0030 --> | |
<Nullable>enable</Nullable> | |
@@ -43,7 +42,7 @@ SPDX-License-Identifier: MIT | |
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> | |
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> | |
<PackageReference Include="Smdn.Fundamental.PrintableEncoding.Hexadecimal" Version="3.0.1" /> | |
- <ProjectOrPackageReference ReferencePackageVersion="[1.0.0,2.0.0)" Include="..\Smdn.TPSmartHomeDevices.Primitives\Smdn.TPSmartHomeDevices.Primitives.csproj" /> | |
+ <ProjectOrPackageReference ReferencePackageVersion="1.1.0-preview1" Include="..\Smdn.TPSmartHomeDevices.Primitives\Smdn.TPSmartHomeDevices.Primitives.csproj" /> | |
</ItemGroup> | |
<ItemGroup> | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.ConvertColorTemperatureToRgb.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.ConvertColorTemperatureToRgb.cs | |
new file mode 100644 | |
index 0000000..0d0d6dc | |
--- /dev/null | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.ConvertColorTemperatureToRgb.cs | |
@@ -0,0 +1,81 @@ | |
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: CC-BY-SA-4.0 | |
+#pragma warning disable IDE0045 // 'if' statement can be simplified | |
+ | |
+using System; | |
+ | |
+namespace Smdn.TPSmartHomeDevices.Tapo; | |
+ | |
+#pragma warning disable IDE0040 | |
+partial class ColorModelUtils { | |
+#pragma warning restore IDE0040 | |
+ /// <summary> | |
+ /// Converts a color that is represented by color temperature to the equivalent color in the RGB model. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// This implementation is based on and ported from the pseudo code described in the following document by <see href="https://tannerhelland.com/about.html">Tanner Helland</see>: | |
+ /// <see href="https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html">How to Convert Temperature (K) to RGB: Algorithm and Sample Code</see>, published under the <see href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</see>. | |
+ /// </remarks> | |
+ private static (byte R, byte G, byte B) ConvertColorTemperatureToRgb(int colorTemperatureInKelvin) | |
+ { | |
+ const int MinKelvin = 1000; | |
+ const int MaxKelvin = 40000; | |
+ | |
+ if (colorTemperatureInKelvin is < MinKelvin or > MaxKelvin) { | |
+ throw new ArgumentOutOfRangeException( | |
+ paramName: nameof(colorTemperatureInKelvin), | |
+ actualValue: colorTemperatureInKelvin, | |
+ message: $"The value for color temperature must be in range of {MinKelvin}~{MaxKelvin}" | |
+ ); | |
+ } | |
+ | |
+ byte red, green, blue; | |
+ | |
+ var temperature = colorTemperatureInKelvin / 100; | |
+ | |
+ // Calculate Red | |
+ if (temperature <= 66) { | |
+ red = 255; | |
+ } | |
+ else { | |
+ red = (byte)Math.Clamp( | |
+ 329.698727446 * Math.Pow(temperature - 60, -0.1332047592), | |
+ 0.0, | |
+ 255.0 | |
+ ); | |
+ } | |
+ | |
+ // Calculate Green | |
+ if (temperature <= 66) { | |
+ green = (byte)Math.Clamp( | |
+ (99.4708025861 * Math.Log(temperature)) - 161.1195681661, | |
+ 0.0, | |
+ 255.0 | |
+ ); | |
+ } | |
+ else { | |
+ green = (byte)Math.Clamp( | |
+ 288.1221695283 * Math.Pow(temperature - 60, -0.0755148492), | |
+ 0.0, | |
+ 255.0 | |
+ ); | |
+ } | |
+ | |
+ // Calculate Blue | |
+ if (66 <= temperature) { | |
+ blue = 255; | |
+ } | |
+ else if (temperature <= 19) { | |
+ blue = 0; | |
+ } | |
+ else { | |
+ blue = (byte)Math.Clamp( | |
+ (138.5177312231 * Math.Log(temperature - 10)) - 305.0447927307, | |
+ 0.0, | |
+ 255.0 | |
+ ); | |
+ } | |
+ | |
+ return (red, green, blue); | |
+ } | |
+} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.cs | |
new file mode 100644 | |
index 0000000..9ae4e6e | |
--- /dev/null | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/ColorModelUtils.cs | |
@@ -0,0 +1,73 @@ | |
+// SPDX-FileCopyrightText: 2024 smdn <smdn@smdn.jp> | |
+// SPDX-License-Identifier: MIT | |
+#pragma warning disable IDE0045 // 'if' statement can be simplified | |
+ | |
+using System; | |
+ | |
+namespace Smdn.TPSmartHomeDevices.Tapo; | |
+ | |
+internal static partial class ColorModelUtils { | |
+ /// <summary> | |
+ /// Converts a color that is represented by color temperature to the equivalent color in the HSV model. | |
+ /// </summary> | |
+ public static (int Hue, int Saturation, byte Value) ConvertColorTemperatureToHsv(int colorTemperatureInKelvin) | |
+ { | |
+ var (r, g, b) = ConvertColorTemperatureToRgb(colorTemperatureInKelvin); | |
+ | |
+ var (h, s, v) = ConvertRgbToConicalObjectModelHsv(r / 255.0f, g / 255.0f, b / 255.0f); | |
+ | |
+ return ( | |
+ Math.Clamp((int)(h * 360.0f), 0, 359), | |
+ Math.Clamp((int)(s * 100.0f), 0, 100), | |
+ (byte)(v * byte.MaxValue) | |
+ ); | |
+ } | |
+ | |
+ private static ( | |
+ float H, | |
+ float S, | |
+ float V | |
+ ) | |
+ ConvertRgbToConicalObjectModelHsv( | |
+ float r, | |
+ float g, | |
+ float b | |
+ ) | |
+ { | |
+ if (r < 0.0f || 1.0f < r) | |
+ throw new ArgumentOutOfRangeException(nameof(r)); | |
+ if (g < 0.0f || 1.0f < g) | |
+ throw new ArgumentOutOfRangeException(nameof(g)); | |
+ if (b < 0.0f || 1.0f < b) | |
+ throw new ArgumentOutOfRangeException(nameof(b)); | |
+ | |
+ var min = MathF.Min(r, MathF.Min(g, b)); | |
+ var max = MathF.Max(r, MathF.Max(g, b)); | |
+ var v = max; | |
+ var d = max - min; | |
+ // var s = d / max; // 円柱モデル | |
+ var s = d; // 円錐モデル | |
+ | |
+ if (min == max) | |
+ return (0.0f, s, v); | |
+ | |
+ float h; | |
+ | |
+ if (min == b) { | |
+ h = (1.0f / 6.0f * (g - r) / d) + (1.0f / 6.0f); | |
+ } | |
+ else if (min == r) { | |
+ h = (1.0f / 6.0f * (b - g) / d) + (3.0f / 6.0f); | |
+ } | |
+ else { // if (min == b) | |
+ h = (1.0f / 6.0f * (r - b) / d) + (5.0f / 6.0f); | |
+ } | |
+ | |
+ if (1.0f <= h) | |
+ h -= 1.0f; | |
+ else if (h < 0.0f) | |
+ h += 1.0f; | |
+ | |
+ return (h, s, v); | |
+ } | |
+} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L530.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L530.cs | |
index 7bcd405..c98cba1 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L530.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L530.cs | |
@@ -21,7 +21,7 @@ namespace Smdn.TPSmartHomeDevices.Tapo; | |
/// </remarks> | |
/// <seealso href="https://www.tp-link.com/jp/smart-home/tapo/tapo-l530e/">Tapo L530 product information (ja)</seealso> | |
/// <seealso href="https://www.tapo.com/en/product/smart-light-bulb/tapo-l530e/">Tapo L530 product information (en)</seealso> | |
-public class L530 : TapoDevice { | |
+public class L530 : TapoDevice, IMulticolorSmartLight { | |
/// <summary> | |
/// Initializes a new instance of the <see cref="L530"/> class with specifying the device endpoint by host name. | |
/// </summary> | |
@@ -211,6 +211,18 @@ public class L530 : TapoDevice { | |
cancellationToken | |
); | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync( | |
+ int brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetBrightnessAsync( | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
+ | |
/// <summary> | |
/// Turns the light on and sets the light color to the specified color temperature. | |
/// </summary> | |
@@ -237,8 +249,22 @@ public class L530 : TapoDevice { | |
cancellationToken | |
); | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync( | |
+ int colorTemperature, | |
+ int? brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetColorTemperatureAsync( | |
+ colorTemperature, | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
+ | |
/// <summary> | |
- /// Turns the light on and sets the light color to the specified color represented by hue and satulation. | |
+ /// Turns the light on and sets the light color to the specified color represented by hue and saturation. | |
/// </summary> | |
/// <param name="hue"> | |
/// The hue of the color in degree, in range of 0~360[°]. | |
@@ -268,6 +294,22 @@ public class L530 : TapoDevice { | |
cancellationToken | |
); | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetColorAsync( | |
+ int hue, | |
+ int saturation, | |
+ int? brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetColorAsync( | |
+ hue, | |
+ saturation, | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
+ | |
/// <summary> | |
/// Turns the light on and sets the light color to the specified hue. | |
/// </summary> | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L900.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L900.cs | |
index 600d4c4..60c5a59 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L900.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/L900.cs | |
@@ -23,7 +23,7 @@ namespace Smdn.TPSmartHomeDevices.Tapo; | |
/// <seealso href="https://www.tp-link.com/jp/smart-home/tapo/tapo-l900-10/">Tapo L900-10 product information (ja)</seealso> | |
/// <seealso href="https://www.tapo.com/en/product/smart-light-bulb/tapo-l900-5/">Tapo Tapo L900-5 product information (en)</seealso> | |
/// <seealso href="https://www.tapo.com/en/product/smart-light-bulb/tapo-l900-10/">Tapo Tapo L900-10 product information (en)</seealso> | |
-public class L900 : TapoDevice { | |
+public class L900 : TapoDevice, IMulticolorSmartLight { | |
/// <summary> | |
/// Initializes a new instance of the <see cref="L900"/> class with specifying the device endpoint by host name. | |
/// </summary> | |
@@ -212,8 +212,20 @@ public class L900 : TapoDevice { | |
cancellationToken | |
); | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetBrightnessAsync( | |
+ int brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetBrightnessAsync( | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
+ | |
/// <summary> | |
- /// Turns the light on and sets the light color to the specified color represented by hue and satulation. | |
+ /// Turns the light on and sets the light color to the specified color represented by hue and saturation. | |
/// </summary> | |
/// <param name="hue"> | |
/// The hue of the color in degree, in range of 0~360[°]. | |
@@ -243,6 +255,22 @@ public class L900 : TapoDevice { | |
cancellationToken | |
); | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetColorAsync( | |
+ int hue, | |
+ int saturation, | |
+ int? brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetColorAsync( | |
+ hue, | |
+ saturation, | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
+ | |
/// <summary> | |
/// Turns the light on and sets the light color to the specified hue. | |
/// </summary> | |
@@ -296,4 +324,49 @@ public class L900 : TapoDevice { | |
), | |
cancellationToken | |
); | |
+ | |
+ /// <summary> | |
+ /// Turns the light on and sets the light color to the specified color temperature. | |
+ /// </summary> | |
+ /// <param name="colorTemperature"> | |
+ /// The color temperature in kelvin [K]. | |
+ /// Available color temperatures depend on the device model. | |
+ /// </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> | |
+ /// <param name="cancellationToken"> | |
+ /// The <see cref="CancellationToken" /> to monitor for cancellation requests. | |
+ /// The default value is <see langword="default" />. | |
+ /// </param> | |
+ public ValueTask SetColorTemperatureAsync( | |
+ int colorTemperature, | |
+ int? brightness = null, | |
+ CancellationToken cancellationToken = default | |
+ ) | |
+ { | |
+ var (hue, saturation, _) = ColorModelUtils.ConvertColorTemperatureToHsv(colorTemperature); | |
+ | |
+ return SetColorAsync( | |
+ hue: hue, | |
+ saturation: saturation, | |
+ brightness: brightness, | |
+ cancellationToken: cancellationToken | |
+ ); | |
+ } | |
+ | |
+#pragma warning disable IDE0060 | |
+ ValueTask IMulticolorSmartLight.SetColorTemperatureAsync( | |
+ int colorTemperature, | |
+ int? brightness, | |
+ TimeSpan transitionPeriod, // not supported | |
+ CancellationToken cancellationToken | |
+ ) | |
+ => SetColorTemperatureAsync( | |
+ colorTemperature, | |
+ brightness, | |
+ cancellationToken | |
+ ); | |
+#pragma warning restore IDE0060 | |
} | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/P105.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/P105.cs | |
index 08a9285..3408b7b 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/P105.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/P105.cs | |
@@ -18,7 +18,7 @@ namespace Smdn.TPSmartHomeDevices.Tapo; | |
/// </remarks> | |
/// <seealso href="https://www.tp-link.com/jp/smart-home/tapo/tapo-p105/">Tapo P105 product information (ja)</seealso> | |
/// <seealso href="https://www.tapo.com/en/product/smart-plug/tapo-p105/">Tapo P105 product information (en)</seealso> | |
-public class P105 : TapoDevice { | |
+public class P105 : TapoDevice, ISmartPlug { | |
/// <summary> | |
/// Initializes a new instance of the <see cref="P105"/> class with specifying the device endpoint by host name. | |
/// </summary> | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs | |
index a46623c..a829324 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoCredentailProviderServiceCollectionExtensions.cs | |
@@ -14,6 +14,9 @@ public static class TapoCredentailProviderServiceCollectionExtensions { | |
/// Adds <see cref="ITapoCredentialProvider"/> to <see cref="IServiceCollection"/>. | |
/// This overload creates <see cref="ITapoCredentialProvider"/> that holds email address and password in plaintext. | |
/// </summary> | |
+ /// <remarks> | |
+ /// The <see cref="ITapoCredentialProvider"/> added by this overload can be used for both <see cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> and <see cref="Protocol.TapoSessionProtocol.Klap"/> authentication process. | |
+ /// </remarks> | |
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param> | |
/// <param name="email">A plaintext email address used for authentication to the Tapo device.</param> | |
/// <param name="password">A plaintext password used for authentication to the Tapo device.</param> | |
@@ -40,6 +43,9 @@ public static class TapoCredentailProviderServiceCollectionExtensions { | |
/// Adds <see cref="ITapoCredentialProvider"/> to <see cref="IServiceCollection"/>. | |
/// This overload creates <see cref="ITapoCredentialProvider"/> that holds user name (email address) and password in base64 encoded text. | |
/// </summary> | |
+ /// <remarks> | |
+ /// The <see cref="ITapoCredentialProvider"/> added by this overload can only be used for <see cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> authentication process. | |
+ /// </remarks> | |
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param> | |
/// <param name="base64UserNameSHA1Digest">A base64 encoded SHA1 digest of user name (email address) text, used for authentication to the Tapo device.</param> | |
/// <param name="base64Password">A base64 encoded password text, used for authentication to the Tapo device.</param> | |
@@ -68,6 +74,9 @@ public static class TapoCredentailProviderServiceCollectionExtensions { | |
/// Adds <see cref="ITapoCredentialProvider"/> to <see cref="IServiceCollection"/>. | |
/// This overload creates <see cref="ITapoCredentialProvider"/> that retrieve user name (email address) and password from environment variables. | |
/// </summary> | |
+ /// <remarks> | |
+ /// The <see cref="ITapoCredentialProvider"/> added by this overload can be used for both <see cref="Protocol.TapoSessionProtocol.SecurePassThrough"/> and <see cref="Protocol.TapoSessionProtocol.Klap"/> authentication process. | |
+ /// </remarks> | |
/// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param> | |
/// <param name="envVarUsername">The name of environment variable that holds the user name (email address) for authentication to the Tapo device.</param> | |
/// <param name="envVarPassword">The name of environment variable that holds the password for authentication to the Tapo device.</param> | |
@@ -93,6 +102,36 @@ public static class TapoCredentailProviderServiceCollectionExtensions { | |
return services; | |
} | |
+ /// <summary> | |
+ /// Adds <see cref="ITapoCredentialProvider"/> to <see cref="IServiceCollection"/>. | |
+ /// This overload creates <see cref="ITapoCredentialProvider"/> that retrieves the base64-encoded KLAP <c>local_auth_hash</c> from environment variables. | |
+ /// To authenticate using this method, the device firmware must support KLAP. | |
+ /// </summary> | |
+ /// <remarks> | |
+ /// The <see cref="ITapoCredentialProvider"/> added by this overload can only be used for <see cref="Protocol.TapoSessionProtocol.Klap"/> authentication process. | |
+ /// </remarks> | |
+ /// <param name="services">The <see cref="IServiceCollection"/> to add services to.</param> | |
+ /// <param name="envVarBase64KlapLocalAuthHash">The name of environment variable that holds the <c>local_auth_hash</c> for authentication to the Tapo device.</param> | |
+ public static IServiceCollection AddTapoBase64EncodedKlapCredentialFromEnvironmentVariable( | |
+ this IServiceCollection services, | |
+ string envVarBase64KlapLocalAuthHash | |
+ ) | |
+ { | |
+ if (services is null) | |
+ throw new ArgumentNullException(nameof(services)); | |
+ | |
+ services.TryAdd( | |
+ ServiceDescriptor.Singleton( | |
+ typeof(ITapoCredentialProvider), | |
+ TapoCredentials.CreateProviderFromEnvironmentVariables( | |
+ envVarBase64KlapLocalAuthHash: envVarBase64KlapLocalAuthHash | |
+ ) | |
+ ) | |
+ ); | |
+ | |
+ return services; | |
+ } | |
+ | |
/// <summary> | |
/// Adds <see cref="ITapoCredentialProvider"/> to <see cref="IServiceCollection"/>. | |
/// </summary> | |
diff --git a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs | |
index 7f43a0a..b3759bd 100644 | |
--- a/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs | |
+++ b/src/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo/TapoDevice.cs | |
@@ -485,10 +485,10 @@ public partial class TapoDevice : ITapoCredentialIdentity, IDisposable { | |
where TRequest : notnull, ITapoPassThroughRequest | |
where TResponse : ITapoPassThroughResponse | |
{ | |
- const int maxAttempts = 5; | |
+ const int MaxAttempts = 5; | |
var delay = TimeSpan.Zero; | |
- for (var attempt = 0; attempt < maxAttempts; attempt++) { | |
+ for (var attempt = 0; attempt < MaxAttempts; attempt++) { | |
if (TimeSpan.Zero < delay) | |
await Task.Delay(delay, cancellationToken).ConfigureAwait(false); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment