Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save smdn/3c7395edf3047a57c17d5bea92fe647e to your computer and use it in GitHub Desktop.
Save smdn/3c7395edf3047a57c17d5bea92fe647e to your computer and use it in GitHub Desktop.
Smdn.TPSmartHomeDevices.Tapo 2.0.0-preview3 Release Notes

main/Smdn.TPSmartHomeDevices.Tapo-2.0.0-preview3

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)
--- 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
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