From 566390dbb3c428e8f4dcc1c5d81a27d8578cafcd Mon Sep 17 00:00:00 2001 From: Ben Hutchison Date: Fri, 1 Nov 2024 17:41:36 -0700 Subject: [PATCH] #11: Added all missing XML documentation --- Kasa.sln.DotSettings | 1 + Kasa/.editorconfig | 2 +- Kasa/AsyncLazy.cs | 8 ++ Kasa/Data/PowerUsage.cs | 2 +- Kasa/Data/Timer.cs | 2 +- Kasa/IKasaOutlet.cs | 134 ++++++++++++++++++++++++++------- Kasa/Kasa.csproj | 2 +- Kasa/KasaMultiOutlet.cs | 43 ++++++++--- Kasa/KasaOutlet.Cloud.cs | 4 +- Kasa/KasaOutlet.EnergyMeter.cs | 8 +- Kasa/KasaOutlet.Schedule.cs | 22 ++++-- Kasa/KasaOutlet.System.cs | 21 +++--- Kasa/KasaOutlet.Time.cs | 6 +- Kasa/KasaOutlet.Timer.cs | 12 ++- Kasa/KasaOutlet.cs | 23 +++--- 15 files changed, 207 insertions(+), 83 deletions(-) diff --git a/Kasa.sln.DotSettings b/Kasa.sln.DotSettings index d1879f0..879f199 100644 --- a/Kasa.sln.DotSettings +++ b/Kasa.sln.DotSettings @@ -41,6 +41,7 @@ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True + True True True diff --git a/Kasa/.editorconfig b/Kasa/.editorconfig index df43a14..64ff900 100644 --- a/Kasa/.editorconfig +++ b/Kasa/.editorconfig @@ -1,5 +1,5 @@ [*] -dotnet_ignored_exceptions = System.OutOfMemoryException, System.OverflowException +dotnet_ignored_exceptions = System.OutOfMemoryException, System.OverflowException, Kasa.FeatureUnavailable, System.Threading.SemaphoreFullException dotnet_diagnostic.Ex0100.severity = warning # Exception Analyzers: Member may throw undocumented exception dotnet_diagnostic.Ex0101.severity = warning # Exception Analyzers: Member accessor may throw undocumented exception diff --git a/Kasa/AsyncLazy.cs b/Kasa/AsyncLazy.cs index b1a84ee..6152630 100644 --- a/Kasa/AsyncLazy.cs +++ b/Kasa/AsyncLazy.cs @@ -1,5 +1,10 @@ namespace Kasa; +/// +/// Like , but asynchronous. +/// +/// type of the value to store +/// generator for the value, can be asynchronous internal class AsyncLazy(Func> valueFactory): IDisposable { private readonly SemaphoreSlim _mutex = new(1); @@ -45,6 +50,9 @@ public bool TrySetValue(T value, bool impatient = false) { public void Dispose() { _mutex.Dispose(); + if (IsValueCreated && _value is IDisposable disposableValue) { + disposableValue.Dispose(); + } } } \ No newline at end of file diff --git a/Kasa/Data/PowerUsage.cs b/Kasa/Data/PowerUsage.cs index f0d5cc5..9dda3fb 100644 --- a/Kasa/Data/PowerUsage.cs +++ b/Kasa/Data/PowerUsage.cs @@ -28,7 +28,7 @@ public struct PowerUsage { [JsonProperty("total_wh")] public int CumulativeEnergySinceBoot { get; internal set; } /// - /// Create a fake response object from , useful for mocking. + /// Create a fake response object from , useful for mocking. /// /// How much current is being used, in milliamperes (mA). /// How much voltage is being used, in millivolts (mV). diff --git a/Kasa/Data/Timer.cs b/Kasa/Data/Timer.cs index 2557522..5023637 100644 --- a/Kasa/Data/Timer.cs +++ b/Kasa/Data/Timer.cs @@ -30,7 +30,7 @@ public struct Timer { /// /// How long the timer should wait, since it was started, before turning on or off. /// When the timer elapses and is no longer running, this value will become . - /// To get the duration remaining from the current time, call and check the property. + /// To get the duration remaining from the current time, call and check the property. /// [JsonProperty("delay")] [JsonConverter(typeof(TimeSpanConverter))] diff --git a/Kasa/IKasaOutlet.cs b/Kasa/IKasaOutlet.cs index a76a000..0de1383 100644 --- a/Kasa/IKasaOutlet.cs +++ b/Kasa/IKasaOutlet.cs @@ -1,14 +1,14 @@ namespace Kasa; /// -/// A TP-Link Kasa outlet or plug. This interface is the main entry point of the Kasa library. The corresponding implementation is . +/// A TP-Link Kasa outlet or plug. This is a super-interface for the main entry points to this library. To create an instance, construct a new if your device has exactly one outlet (like an EP10), or a new if your device has multiple outlets (like an EP40). /// You may optionally call on each instance before using it. If you don't, it will connect automatically when sending the first command. /// Remember to Dispose each instance when you're done using it in order to close the TCP connection with the device. Disposed instances may not be reused, even if you call again. /// To communicate with multiple Kasa devices, construct multiple instances, one per device. /// Example usage: /// using IKasaOutlet outlet = new KasaOutlet("192.168.1.100"); /// bool isOutletOn = await outlet.System.IsOutletOn(); -/// if(!isOutletOn){ +/// if (!isOutletOn) { /// await outlet.System.SetOutletOn(true); /// } /// @@ -89,7 +89,7 @@ public interface ISystemCommands { /// Restart the device. /// Rebooting will interrupt power to any connected consumers for roughly 108 milliseconds. /// It takes about 8 seconds for a KP125 to completely reboot and resume responding to API requests, and about 14 seconds for an EP10. - /// The existing outlet power state will be retained after rebooting, so if it was on before rebooting, it will turn on again after rebooting, and there is no need to explicitly call to reestablish the previous state. + /// The existing outlet power state will be retained after rebooting, so if it was on before rebooting, it will turn on again after rebooting, and there is no need to explicitly call to reestablish the previous state. /// By default, this client will automatically reconnect to the outlet after it reboots, which can be tuned using the and properties. /// /// How long to wait before rebooting. If not specified, the device reboots immediately. @@ -114,12 +114,22 @@ public interface ISystemCommands { /// if the JSON received from the outlet contained unexpected data Task SetName(string name); + /// + /// Count how many outlets this Kasa device has. + /// This only includes the main three-prong 15A AC NEMA 5-15-R sockets. It excludes all USB-A ports, such as those that appear on the KP303 or HS300 power strips, and which are not individually switched. + /// + /// The number of AC outlets on this device. For instances, this will always return 1. For instances, this will always return a number greater than 1. + /// if the TCP connection to the outlet failed and could not automatically reconnect + /// if the JSON received from the outlet contained unexpected data Task CountOutlets(); - public interface SingleOutlet: ISystemCommands { + /// + /// Commands that get or set system properties, like status, name, and whether the outlet is on or off. Includes commands that apply to devices with exactly one outlet. + /// + public interface ISingleOutlet: ISystemCommands { /// - /// Get whether the outlet on the device can supply power to any connected electrical consumers or not. + /// Get whether the outlet on the device is energized. /// This is unrelated to whether the entire Kasa device is running. If you can connect to it, it's running. /// /// true if the outlet's internal relay is on, or false if it's off @@ -128,11 +138,11 @@ public interface SingleOutlet: ISystemCommands { Task IsOutletOn(); /// - /// Turn on or off the device's outlet so it can supply power to any connected electrical consumers or not. + /// Energizes or deenergizes the device's outlet so it can supply power to any connected electrical consumers or not. /// You can also toggle the outlet by pressing the physical button on the device. /// This call is idempotent: if you try to turn the outlet on and it's already on, the call will have no effect. /// The state is persisted across restarts. If the device loses power, it will restore the previous outlet power state when it turns on again. - /// This call is unrelated to turning the entire Kasa device on or off. To reboot the device, use . + /// This call is unrelated to turning the entire Kasa device on or off. To reboot the device, use . /// /// true to supply power to the outlet, or false to switch if off. /// if the TCP connection to the outlet failed and could not automatically reconnect @@ -141,32 +151,56 @@ public interface SingleOutlet: ISystemCommands { } - public interface MultiOutlet: ISystemCommands { + /// + /// Commands that get or set system properties, like status, name, and whether the outlet is on or off. Includes commands that apply to devices with more than one outlet. + /// + public interface IMultiOutlet: ISystemCommands { /// - /// Get whether the outlet on the device can supply power to any connected electrical consumers or not. + /// Get whether an outlet on the device is energized. /// This is unrelated to whether the entire Kasa device is running. If you can connect to it, it's running. /// + /// The index, increasing from 0, of the outlet on the device. For example, to query the outlet with the physical label "plug 1," pass 0 to this parameter. /// true if the outlet's internal relay is on, or false if it's off + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task IsOutletOn(int outletId); /// - /// Turn on or off the device's outlet so it can supply power to any connected electrical consumers or not. - /// You can also toggle the outlet by pressing the physical button on the device. + /// Energizes or deenergizes one of the device's outlets so it can supply power to any connected electrical consumers or not. + /// You can also toggle the outlet by pressing a physical button on the device. /// This call is idempotent: if you try to turn the outlet on and it's already on, the call will have no effect. - /// The state is persisted across restarts. If the device loses power, it will restore the previous outlet power state when it turns on again. + /// The state is persisted across restarts. If the device loses power, it will restore the previous outlet power states when it turns on again. /// This call is unrelated to turning the entire Kasa device on or off. To reboot the device, use . /// - /// + /// The index, increasing from 0, of the outlet on the device. For example, to control the outlet with the physical label "plug 1," pass 0 to this parameter. /// true to supply power to the outlet, or false to switch if off. + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task SetOutletOn(int outletId, bool turnOn); + /// + /// The name or alias of an individual socket that you chose during setup. + /// To get the alias of the entire device, see . + /// + /// The index, increasing from 0, of the outlet on the device. For example, to specify the outlet with the physical label "plug 1," pass 0 to this parameter. + /// The name of the individual socket. + /// if is less than 0 or greater than or equal to the number of outlets on this device + /// if the TCP connection to the outlet failed and could not automatically reconnect + /// if the JSON received from the outlet contained unexpected data Task GetName(int outletId); + /// + /// Change the alias of an individual socket. This will appear in the Kasa mobile app. + /// To change the alias of the entire device, see . + /// + /// The index, increasing from 0, of the outlet on the device. For example, to specify the outlet with the physical label "plug 1," pass 0 to this parameter. + /// The new name of the device. The maximum length is 31 characters. + /// if the new name is empty or longer than 31 characters; or if is less than 0 or greater than or equal to the number of outlets on this device + /// if the TCP connection to the outlet failed and could not automatically reconnect + /// if the JSON received from the outlet contained unexpected data Task SetName(int outletId, string name); } @@ -309,10 +343,32 @@ public interface ITimerCommandsSingleOutlet { } + /// public interface ITimerCommandsMultiOutlet { + /// + /// The index, increasing from 0, of the outlet on the device. For example, to specify the outlet with the physical label "plug 1," pass 0 to this parameter. + /// if is less than 0 or greater than or equal to the number of outlets on this device Task Get(int outletId); + + /// + /// Save a new, enabled countdown to the device. + /// There can be at most one timer on the device at once, so any existing timers will first be deleted, even if they had not elapsed yet. + /// The created timer will be returned, which is useful if you want to inspect the newly-populated property. To refresh in the future, call . + /// + /// How long the timer should wait, since being started, before turning on or off. + /// Whether to turn the outlet on (true) or off (false) when the timer elapses. + /// The index, increasing from 0, of the outlet on the device. For example, to specify the outlet with the physical label "plug 1," pass 0 to this parameter. + /// The created timer rule, which will have the same and as the and you passed in, but with a populated . + /// if is less than 0 or greater than or equal to the number of outlets on this device + /// If the device does not have a timer. To check this, you can call (await kasaOutlet.System.GetInfo()).Features.Contains(Feature.Timer). + /// if the TCP connection to the outlet failed and could not automatically reconnect + /// if the JSON received from the outlet contained unexpected data Task Start(int outletId, TimeSpan duration, bool setOutletOnWhenComplete); + + /// + /// The index, increasing from 0, of the outlet on the device. For example, to specify the outlet with the physical label "plug 1," pass 0 to this parameter. + /// if is less than 0 or greater than or equal to the number of outlets on this device Task Clear(int outletId); } @@ -370,12 +426,14 @@ public interface IScheduleCommandsSingleOutlet { } + /// public interface IScheduleCommandsMultiOutlet { /// /// Fetch all of the existing schedules from the outlet. /// /// A enumerable of rules, or the empty enumerable if the device has no schedules on it. + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task> GetAll(int outletId); @@ -388,6 +446,7 @@ public interface IScheduleCommandsMultiOutlet { /// /// A new or existing schedule to insert or update. /// The persisted instance, which always has a non-null value. + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task Save(int outletId, Schedule schedule); @@ -398,7 +457,7 @@ public interface IScheduleCommandsMultiOutlet { /// /// The existing schedule to delete. /// Returns successfully when a schedule with the given doesn't exist on the outlet, even if this method invocation didn't delete it. - /// If the parameter has a null value for the property, possibly because it was newly constructed instead of being fetched from or . + /// if the parameter has a null value for the property, possibly because it was newly constructed instead of being fetched from or ; or if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task Delete(int outletId, Schedule schedule); @@ -409,6 +468,7 @@ public interface IScheduleCommandsMultiOutlet { /// /// The of an existing schedule to delete. /// Returns successfully when a schedule with the given doesn't exist on the outlet, even if this method invocation didn't delete it. + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task Delete(int outletId, string scheduleId); @@ -416,6 +476,7 @@ public interface IScheduleCommandsMultiOutlet { /// /// Clear all existing schedules from the outlet. /// + /// if is less than 0 or greater than or equal to the number of outlets on this device /// if the TCP connection to the outlet failed and could not automatically reconnect /// if the JSON received from the outlet contained unexpected data Task DeleteAll(int outletId); @@ -437,7 +498,7 @@ public interface ICloudCommands { Task IsConnectedToCloudAccount(); /// - /// Logs the smart outlet out of the TP-Link hosted servers, disconnecting it so it can't be remotely controlled over the Internet, and making it so it can only be controlled on the LAN. + /// Logs the smart outlet out of the TP-Link hosted servers, disconnecting it so it can't be remotely controlled over the Internet, and making it so it can only be controlled on the LAN. May make the device disappear from your Kasa Smart mobile app as well. /// If the device was already not logged into the TP-Link servers, the returned by this method will still resolve successfully — it is idempotent. /// /// if the TCP connection to the outlet failed and could not automatically reconnect @@ -448,18 +509,30 @@ public interface ICloudCommands { } +/// +/// A TP-Link Kasa outlet or plug. This interface is the main entry point to this library for devices with exactly one outlet, like the EP10. For devices like the EP40 with multiple outlets, see instead. To create an instance, construct a new . +/// You may optionally call on each instance before using it. If you don't, it will connect automatically when sending the first command. +/// Remember to Dispose each instance when you're done using it in order to close the TCP connection with the device. Disposed instances may not be reused, even if you call again. +/// To communicate with multiple Kasa devices, construct multiple instances, one per device. +/// Example usage: +/// using IKasaOutlet outlet = new KasaOutlet("192.168.1.100"); +/// bool isOutletOn = await outlet.System.IsOutletOn(); +/// if (!isOutletOn) { +/// await outlet.System.SetOutletOn(true); +/// } +/// public interface IKasaOutlet: IKasaOutletBase { /// /// Commands that get or set system properties, like status, name, and whether the outlet is on or off. /// - ISystemCommands.SingleOutlet System { get; } + ISystemCommands.ISingleOutlet System { get; } /// /// Commands that deal with countdown timers. /// Timers allow you to set the outlet to turn on or off once after a delay of configurable duration. /// Outlets can handle at most one timer at once. - /// This is unrelated to the current time of the device's internal clock, see . + /// This is unrelated to the current time of the device's internal clock, see . /// ITimerCommandsSingleOutlet Timer { get; } @@ -472,24 +545,27 @@ public interface IKasaOutlet: IKasaOutletBase { } +/// +/// A TP-Link Kasa device with multiple outlets. This interface is the main entry point to this library for devices with more than one outlet, like the EP40. For devices like the EP10 with exactly one outlet, see instead. To create an instance, construct a new . Individual outlets IDs used in method parameters are 0-indexed. +/// You may optionally call on each instance before using it. If you don't, it will connect automatically when sending the first command. +/// Remember to Dispose each instance when you're done using it in order to close the TCP connection with the device. Disposed instances may not be reused, even if you call again. +/// To communicate with multiple Kasa devices, construct multiple instances, one per device. +/// Example usage: +/// using IKasaMultiOutlet outlet = new KasaMultiOutlet("192.168.1.100"); +/// bool isOutletOn = await outlet.System.IsOutletOn(0); +/// if (!isOutletOn) { +/// await outlet.System.SetOutletOn(0, true); +/// } +/// public interface IKasaMultiOutlet: IKasaOutletBase { /// - ISystemCommands.MultiOutlet System { get; } + ISystemCommands.IMultiOutlet System { get; } - /// - /// Commands that deal with countdown timers. - /// Timers allow you to set the outlet to turn on or off once after a delay of configurable duration. - /// Outlets can handle at most one timer at once. - /// This is unrelated to the current time of the device's internal clock, see . - /// + /// ITimerCommandsMultiOutlet Timer { get; } - /// - /// Commands that deal with schedules. - /// Schedules allow you to set the outlet to turn on or off once on a specific day and time, or multiple times with a weekly recurrence pattern. Times can be relative to the start of the day, sunrise, or sunset. - /// Outlets can handle multiple schedules at once. - /// + /// IScheduleCommandsMultiOutlet Schedule { get; } } \ No newline at end of file diff --git a/Kasa/Kasa.csproj b/Kasa/Kasa.csproj index 7d0604d..190a310 100644 --- a/Kasa/Kasa.csproj +++ b/Kasa/Kasa.csproj @@ -1,7 +1,7 @@ - 1.1.0 + 1.1.0-beta1 Kasa Kasa Kasa diff --git a/Kasa/KasaMultiOutlet.cs b/Kasa/KasaMultiOutlet.cs index fb03d73..18e2b13 100644 --- a/Kasa/KasaMultiOutlet.cs +++ b/Kasa/KasaMultiOutlet.cs @@ -1,6 +1,19 @@ namespace Kasa; -public class KasaMultiOutlet: KasaOutlet, IKasaMultiOutlet, IKasaOutletBase.ISystemCommands.MultiOutlet, IKasaOutletBase.ITimerCommandsMultiOutlet, IKasaOutletBase.IScheduleCommandsMultiOutlet { +/// +/// A TP-Link Kasa device with multiple outlets. This class is the main entry point of the Kasa library for devices with more than one outlet, like the EP40. The corresponding interface is . Individual outlet IDs used in method parameters are 0-indexed. +/// For devices with exactly one outlet like the EP10, you should instead construct a new instance of , rather than this class. +/// You may optionally call on each instance before using it. If you don't, it will connect automatically when sending the first command. +/// Remember to Dispose each instance when you're done using it in order to close the TCP connection with the device. Disposed instances may not be reused, even if you call again. +/// To communicate with multiple Kasa devices, construct multiple instances, one per device. +/// Example usage: +/// using IKasaMultiOutlet outlet = new KasaMultiOutlet("192.168.1.100"); +/// bool isOutletOn = await outlet.System.IsOutletOn(0); +/// if (!isOutletOn) { +/// await outlet.System.SetOutletOn(0, true); +/// } +/// +public class KasaMultiOutlet: KasaOutlet, IKasaMultiOutlet, IKasaOutletBase.ISystemCommands.IMultiOutlet, IKasaOutletBase.ITimerCommandsMultiOutlet, IKasaOutletBase.IScheduleCommandsMultiOutlet { private readonly AsyncLazy> _childIds; @@ -16,7 +29,7 @@ internal KasaMultiOutlet(IKasaClient client): base(client) { private AsyncLazy> InitChildIds() => new(async () => SystemInfoToChildIds(await System.GetInfo().ConfigureAwait(false))); /// - public new IKasaOutletBase.ISystemCommands.MultiOutlet System => this; + public new IKasaOutletBase.ISystemCommands.IMultiOutlet System => this; /// public new IKasaOutletBase.ITimerCommandsMultiOutlet Timer => this; @@ -24,11 +37,11 @@ internal KasaMultiOutlet(IKasaClient client): base(client) { /// public new IKasaOutletBase.IScheduleCommandsMultiOutlet Schedule => this; - /// is outside the range of 0-indexed outlets on the device private async Task GetChildContext(int outletId) => new((await _childIds.GetValue().ConfigureAwait(false))[outletId]); private static IList SystemInfoToChildIds(SystemInfo systemInfo) => systemInfo.Children?.Select(child => child.Id).ToList() ?? []; + /// protected override async Task GetInfo() { SystemInfo systemInfo = await base.GetInfo().ConfigureAwait(false); if (!_childIds.IsValueCreated) { @@ -39,67 +52,75 @@ protected override async Task GetInfo() { async Task IKasaOutletBase.ISystemCommands.CountOutlets() => (await _childIds.GetValue().ConfigureAwait(false)).Count; - /// is outside the range of 0-indexed outlets on the device - async Task IKasaOutletBase.ISystemCommands.MultiOutlet.IsOutletOn(int outletId) { + /// + async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.IsOutletOn(int outletId) { ChildOutlet childOutlet = (await System.GetInfo().ConfigureAwait(false)).Children?.ElementAt(outletId) ?? throw new ArgumentOutOfRangeException(nameof(outletId), outletId, "Kasa device does not have multiple outlets"); return childOutlet.IsOutletOn; } - /// is outside the range of 0-indexed outlets on the device - async Task IKasaOutletBase.ISystemCommands.MultiOutlet.SetOutletOn(int outletId, bool turnOn) { + /// + async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.SetOutletOn(int outletId, bool turnOn) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await SetOutletOn(turnOn, context).ConfigureAwait(false); } - /// is outside the range of 0-indexed outlets on the device - async Task IKasaOutletBase.ISystemCommands.MultiOutlet.GetName(int outletId) { + /// + async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.GetName(int outletId) { ChildOutlet childOutlet = (await System.GetInfo().ConfigureAwait(false)).Children?.ElementAt(outletId) ?? throw new ArgumentOutOfRangeException(nameof(outletId), outletId, "Kasa device does not have multiple outlets"); return childOutlet.Name; } - /// is outside the range of 0-indexed outlets on the device - async Task IKasaOutletBase.ISystemCommands.MultiOutlet.SetName(int outletId, string name) { + /// + async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.SetName(int outletId, string name) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await SetName(name, context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.ITimerCommandsMultiOutlet.Get(int outletId) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); return await GetTimer(context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.ITimerCommandsMultiOutlet.Start(int outletId, TimeSpan duration, bool setOutletOnWhenComplete) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); return await StartTimer(duration, setOutletOnWhenComplete, context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.ITimerCommandsMultiOutlet.Clear(int outletId) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await ClearTimers(context).ConfigureAwait(false); } + /// async Task> IKasaOutletBase.IScheduleCommandsMultiOutlet.GetAll(int outletId) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); return await GetAllSchedules(context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.Save(int outletId, Schedule schedule) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); return await SaveSchedule(schedule, context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.Delete(int outletId, Schedule schedule) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await DeleteSchedule(schedule, context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.Delete(int outletId, string scheduleId) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await DeleteSchedule(scheduleId, context).ConfigureAwait(false); } + /// async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.DeleteAll(int outletId) { ChildContext context = await GetChildContext(outletId).ConfigureAwait(false); await DeleteAllSchedules(context).ConfigureAwait(false); diff --git a/Kasa/KasaOutlet.Cloud.cs b/Kasa/KasaOutlet.Cloud.cs index 3b8026f..dfec647 100644 --- a/Kasa/KasaOutlet.Cloud.cs +++ b/Kasa/KasaOutlet.Cloud.cs @@ -9,13 +9,13 @@ public partial class KasaOutlet { /// async Task IKasaOutletBase.ICloudCommands.IsConnectedToCloudAccount() { - JObject response = await Client.Send(CommandFamily.Cloud, "get_info").ConfigureAwait(false); + JObject response = await _client.Send(CommandFamily.Cloud, "get_info").ConfigureAwait(false); return response.Value("binded") == 1; } /// Task IKasaOutletBase.ICloudCommands.DisconnectFromCloudAccount() { - return Client.Send(CommandFamily.Cloud, "unbind"); + return _client.Send(CommandFamily.Cloud, "unbind"); } } \ No newline at end of file diff --git a/Kasa/KasaOutlet.EnergyMeter.cs b/Kasa/KasaOutlet.EnergyMeter.cs index 3f87ea4..fda7576 100644 --- a/Kasa/KasaOutlet.EnergyMeter.cs +++ b/Kasa/KasaOutlet.EnergyMeter.cs @@ -9,12 +9,12 @@ public partial class KasaOutlet { /// Task IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage() { - return Client.Send(CommandFamily.EnergyMeter, "get_realtime"); + return _client.Send(CommandFamily.EnergyMeter, "get_realtime"); } /// async Task?> IKasaOutletBase.IEnergyMeterCommands.GetDailyEnergyUsage(int year, int month) { - JArray response = (JArray) (await Client.Send(CommandFamily.EnergyMeter, "get_daystat", new { year, month }).ConfigureAwait(false))["day_list"]!; + JArray response = (JArray) (await _client.Send(CommandFamily.EnergyMeter, "get_daystat", new { year, month }).ConfigureAwait(false))["day_list"]!; List? results = null; if (response.Count > 0) { @@ -32,7 +32,7 @@ Task IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage /// async Task?> IKasaOutletBase.IEnergyMeterCommands.GetMonthlyEnergyUsage(int year) { - JArray response = (JArray) (await Client.Send(CommandFamily.EnergyMeter, "get_monthstat", new { year }).ConfigureAwait(false))["month_list"]!; + JArray response = (JArray) (await _client.Send(CommandFamily.EnergyMeter, "get_monthstat", new { year }).ConfigureAwait(false))["month_list"]!; List? results = null; if (response.Count > 0) { @@ -49,7 +49,7 @@ Task IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage /// Task IKasaOutletBase.IEnergyMeterCommands.DeleteHistoricalUsage() { - return Client.Send(CommandFamily.EnergyMeter, "erase_emeter_stat"); + return _client.Send(CommandFamily.EnergyMeter, "erase_emeter_stat"); } } \ No newline at end of file diff --git a/Kasa/KasaOutlet.Schedule.cs b/Kasa/KasaOutlet.Schedule.cs index 4bafd8d..f9b39fe 100644 --- a/Kasa/KasaOutlet.Schedule.cs +++ b/Kasa/KasaOutlet.Schedule.cs @@ -12,8 +12,10 @@ async Task> IKasaOutletBase.IScheduleCommandsSingleOutlet. return await GetAllSchedules(null).ConfigureAwait(false); } + /// + /// internal async Task> GetAllSchedules(ChildContext? context) { - JObject response = await Client.Send(CommandFamily.Schedule, "get_rules", null, context).ConfigureAwait(false); + JObject response = await _client.Send(CommandFamily.Schedule, "get_rules", null, context).ConfigureAwait(false); return response["rule_list"]!.ToObject>(KasaClient.JsonSerializer)!; } @@ -22,13 +24,15 @@ async Task IKasaOutletBase.IScheduleCommandsSingleOutlet.Save(Schedule return await SaveSchedule(schedule, null).ConfigureAwait(false); } + /// + /// internal async Task SaveSchedule(Schedule schedule, ChildContext? context) { string id; if (schedule.Id is null) { - JObject created = await Client.Send(CommandFamily.Schedule, "add_rule", schedule, context).ConfigureAwait(false); + JObject created = await _client.Send(CommandFamily.Schedule, "add_rule", schedule, context).ConfigureAwait(false); id = created["id"]!.ToObject(KasaClient.JsonSerializer)!; } else { - await Client.Send(CommandFamily.Schedule, "edit_rule", schedule, context).ConfigureAwait(false); + await _client.Send(CommandFamily.Schedule, "edit_rule", schedule, context).ConfigureAwait(false); id = schedule.Id; } @@ -40,7 +44,9 @@ Task IKasaOutletBase.IScheduleCommandsSingleOutlet.Delete(Schedule schedule) { return DeleteSchedule(schedule, null); } - /// If the parameter has a null value for the property, possibly because it was newly constructed instead of being fetched from or . + /// If the parameter has a null value for the property, possibly because it was newly constructed instead of being fetched from or . + /// + /// internal Task DeleteSchedule(Schedule schedule, ChildContext? context) { if (schedule.Id is not null) { return DeleteSchedule(schedule.Id, context); @@ -55,8 +61,10 @@ Task IKasaOutletBase.IScheduleCommandsSingleOutlet.Delete(string scheduleId) { return DeleteSchedule(scheduleId, null); } + /// + /// internal Task DeleteSchedule(string id, ChildContext? context) { - return Client.Send(CommandFamily.Schedule, "delete_rule", new { id }, context); + return _client.Send(CommandFamily.Schedule, "delete_rule", new { id }, context); } /// @@ -64,8 +72,10 @@ Task IKasaOutletBase.IScheduleCommandsSingleOutlet.DeleteAll() { return DeleteAllSchedules(null); } + /// + /// internal Task DeleteAllSchedules(ChildContext? context) { - return Client.Send(CommandFamily.Schedule, "delete_all_rules", null, context); + return _client.Send(CommandFamily.Schedule, "delete_all_rules", null, context); } } \ No newline at end of file diff --git a/Kasa/KasaOutlet.System.cs b/Kasa/KasaOutlet.System.cs index 33b589b..ece92f2 100644 --- a/Kasa/KasaOutlet.System.cs +++ b/Kasa/KasaOutlet.System.cs @@ -5,26 +5,28 @@ namespace Kasa; public partial class KasaOutlet { /// - public IKasaOutletBase.ISystemCommands.SingleOutlet System => this; + public IKasaOutletBase.ISystemCommands.ISingleOutlet System => this; /// - async Task IKasaOutletBase.ISystemCommands.SingleOutlet.IsOutletOn() { + async Task IKasaOutletBase.ISystemCommands.ISingleOutlet.IsOutletOn() { SystemInfo systemInfo = await ((IKasaOutletBase.ISystemCommands) this).GetInfo().ConfigureAwait(false); return systemInfo.IsOutletOn; } /// - Task IKasaOutletBase.ISystemCommands.SingleOutlet.SetOutletOn(bool turnOn) => SetOutletOn(turnOn, null); + Task IKasaOutletBase.ISystemCommands.ISingleOutlet.SetOutletOn(bool turnOn) => SetOutletOn(turnOn, null); + /// internal Task SetOutletOn(bool turnOn, ChildContext? context) { - return Client.Send(CommandFamily.System, "set_relay_state", new { state = Convert.ToInt32(turnOn) }, context); + return _client.Send(CommandFamily.System, "set_relay_state", new { state = Convert.ToInt32(turnOn) }, context); } /// Task IKasaOutletBase.ISystemCommands.GetInfo() => GetInfo(); + /// protected virtual Task GetInfo() { - return Client.Send(CommandFamily.System, "get_sysinfo"); + return _client.Send(CommandFamily.System, "get_sysinfo"); } /// @@ -35,12 +37,12 @@ async Task IKasaOutletBase.ISystemCommands.IsIndicatorLightOn() { /// Task IKasaOutletBase.ISystemCommands.SetIndicatorLightOn(bool turnOn) { - return Client.Send(CommandFamily.System, "set_led_off", new { off = Convert.ToInt32(!turnOn) }); + return _client.Send(CommandFamily.System, "set_led_off", new { off = Convert.ToInt32(!turnOn) }); } /// Task IKasaOutletBase.ISystemCommands.Reboot(TimeSpan afterDelay) { - return Client.Send(CommandFamily.System, "reboot", new { delay = (int) afterDelay.TotalSeconds }); + return _client.Send(CommandFamily.System, "reboot", new { delay = (int) afterDelay.TotalSeconds }); } /// @@ -52,15 +54,16 @@ async Task IKasaOutletBase.ISystemCommands.GetName() { /// Task IKasaOutletBase.ISystemCommands.SetName(string name) => SetName(name, null); - /// if the new name is empty or longer than 31 characters + /// internal Task SetName(string name, ChildContext? context) { if (string.IsNullOrWhiteSpace(name) || name.Length > 31) { throw new ArgumentOutOfRangeException(nameof(name), name, "name must be between 1 and 31 characters long (inclusive), and cannot be only whitespace"); } - return Client.Send(CommandFamily.System, "set_dev_alias", new { alias = name }, context); + return _client.Send(CommandFamily.System, "set_dev_alias", new { alias = name }, context); } + /// Task IKasaOutletBase.ISystemCommands.CountOutlets() { return Task.FromResult(1); } diff --git a/Kasa/KasaOutlet.Time.cs b/Kasa/KasaOutlet.Time.cs index 5f50ae1..c279240 100644 --- a/Kasa/KasaOutlet.Time.cs +++ b/Kasa/KasaOutlet.Time.cs @@ -9,7 +9,7 @@ public partial class KasaOutlet { /// async Task IKasaOutletBase.ITimeCommands.GetTime() { - JObject response = await Client.Send(CommandFamily.Time, "get_time").ConfigureAwait(false); + JObject response = await _client.Send(CommandFamily.Time, "get_time").ConfigureAwait(false); return new DateTime( response["year"]!.ToObject(KasaClient.JsonSerializer), response["month"]!.ToObject(KasaClient.JsonSerializer), @@ -29,7 +29,7 @@ async Task IKasaOutletBase.ITimeCommands.GetTimeWithZoneOffset() /// // ExceptionAdjustment: M:System.TimeZoneInfo.FindSystemTimeZoneById(System.String) -T:System.Security.SecurityException async Task> IKasaOutletBase.ITimeCommands.GetTimeZones() { - JObject response = await Client.Send(CommandFamily.Time, "get_timezone").ConfigureAwait(false); + JObject response = await _client.Send(CommandFamily.Time, "get_timezone").ConfigureAwait(false); int deviceTimezoneId = response["index"]!.ToObject(KasaClient.JsonSerializer); IEnumerable windowsZoneIds = TimeZones.KasaIndicesToWindowsZoneIds[deviceTimezoneId]; @@ -46,7 +46,7 @@ async Task> IKasaOutletBase.ITimeCommands.GetTimeZones Task IKasaOutletBase.ITimeCommands.SetTimeZone(TimeZoneInfo timeZone) { try { int deviceTimezoneId = TimeZones.WindowsZoneIdsToKasaIndices[timeZone.Id]; - return Client.Send(CommandFamily.Time, "set_timezone", new { index = deviceTimezoneId }); + return _client.Send(CommandFamily.Time, "set_timezone", new { index = deviceTimezoneId }); } catch (KeyNotFoundException e) { throw new TimeZoneNotFoundException($"Kasa devices don't have a built-in time zone that matches {timeZone.Id}." + $"Consult Kasa.{nameof(TimeZones)}.{nameof(TimeZones.WindowsZoneIdsToKasaIndices)} for supported time zones.", e); diff --git a/Kasa/KasaOutlet.Timer.cs b/Kasa/KasaOutlet.Timer.cs index c15f6ab..c94b786 100644 --- a/Kasa/KasaOutlet.Timer.cs +++ b/Kasa/KasaOutlet.Timer.cs @@ -12,8 +12,10 @@ public partial class KasaOutlet { return await GetTimer(null).ConfigureAwait(false); } + /// + /// internal async Task GetTimer(ChildContext? context) { - JArray jToken = (JArray) (await Client.Send(CommandFamily.Timer, "get_rules", null, context).ConfigureAwait(false))["rule_list"]!; + JArray jToken = (JArray) (await _client.Send(CommandFamily.Timer, "get_rules", null, context).ConfigureAwait(false))["rule_list"]!; return jToken.ToObject>(KasaClient.JsonSerializer)!.FirstOrDefault() is { IsEnabled: true } timer ? timer : null; } @@ -22,10 +24,12 @@ Task IKasaOutletBase.ITimerCommandsSingleOutlet.Start(TimeSpan duration, return StartTimer(duration, setOutletOnWhenComplete, null); } + /// + /// internal async Task StartTimer(TimeSpan duration, bool setOutletOnWhenComplete, ChildContext? context) { await ClearTimers(context).ConfigureAwait(false); Timer timer = new(duration, setOutletOnWhenComplete); - await Client.Send(CommandFamily.Timer, "add_rule", timer, context).ConfigureAwait(false); + await _client.Send(CommandFamily.Timer, "add_rule", timer, context).ConfigureAwait(false); return (await GetTimer(context).ConfigureAwait(false))!.Value; } @@ -34,8 +38,10 @@ Task IKasaOutletBase.ITimerCommandsSingleOutlet.Clear() { return ClearTimers(null); } + /// + /// internal Task ClearTimers(ChildContext? context) { - return Client.Send(CommandFamily.Timer, "delete_all_rules", context); + return _client.Send(CommandFamily.Timer, "delete_all_rules", context); } } \ No newline at end of file diff --git a/Kasa/KasaOutlet.cs b/Kasa/KasaOutlet.cs index d145952..c7c6b26 100644 --- a/Kasa/KasaOutlet.cs +++ b/Kasa/KasaOutlet.cs @@ -1,7 +1,8 @@ namespace Kasa; /// -/// A TP-Link Kasa outlet or plug. This class is the main entry point of the Kasa library. The corresponding interface is . +/// A TP-Link Kasa outlet or plug. This class is the main entry point of the Kasa library for devices with exactly one outlet, like the EP10. The corresponding interface is . +/// For devices with multiple outlets like the EP40, you should instead construct a new instance of , rather than this class. /// You may optionally call on each instance before using it. If you don't, it will connect automatically when sending the first command. /// Remember to Dispose each instance when you're done using it in order to close the TCP connection with the device. Disposed instances may not be reused, even if you call again. /// To communicate with multiple Kasa devices, construct multiple instances, one per device. @@ -12,20 +13,18 @@ namespace Kasa; /// await outlet.System.SetOutletOn(true); /// } /// -public partial class KasaOutlet: IKasaOutlet, IKasaOutletBase.ISystemCommands.SingleOutlet, IKasaOutletBase.ITimeCommands, IKasaOutletBase.IEnergyMeterCommands, - IKasaOutletBase.ITimerCommandsSingleOutlet, - IKasaOutletBase.IScheduleCommandsSingleOutlet, - IKasaOutletBase.ICloudCommands { +public partial class KasaOutlet: IKasaOutlet, IKasaOutletBase.ISystemCommands.ISingleOutlet, IKasaOutletBase.ITimeCommands, IKasaOutletBase.IEnergyMeterCommands, + IKasaOutletBase.ITimerCommandsSingleOutlet, IKasaOutletBase.IScheduleCommandsSingleOutlet, IKasaOutletBase.ICloudCommands { - internal readonly IKasaClient Client; + private readonly IKasaClient _client; /// - public string Hostname => Client.Hostname; + public string Hostname => _client.Hostname; /// public Options Options { - get => Client.Options; - set => Client.Options = value; + get => _client.Options; + set => _client.Options = value; } /// @@ -39,12 +38,12 @@ public Options Options { public KasaOutlet(string hostname, Options? options = null): this(new KasaClient(hostname) { Options = options ?? new Options() }) { } internal KasaOutlet(IKasaClient client) { - Client = client; + _client = client; } /// public Task Connect() { - return Client.Connect(); + return _client.Connect(); } /// @@ -53,7 +52,7 @@ public Task Connect() { /// true to dispose the TCP client, false if you're running in a finalizer and it's already been disposed protected virtual void Dispose(bool disposing) { if (disposing) { - Client.Dispose(); + _client.Dispose(); } }