Skip to content

Commit

Permalink
#11: Added all missing XML documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Aldaviva committed Nov 2, 2024
1 parent 29941bf commit 566390d
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 83 deletions.
1 change: 1 addition & 0 deletions Kasa.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=f9fce829_002De6f4_002D4cb2_002D80f1_002D5497c44f51df/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=TimezoneGenerator_003B_002A_003B_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=deenergizes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=FQDN/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kasa/@EntryIndexedValue">True</s:Boolean>

Expand Down
2 changes: 1 addition & 1 deletion Kasa/.editorconfig
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 8 additions & 0 deletions Kasa/AsyncLazy.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
namespace Kasa;

/// <summary>
/// Like <see cref="Lazy{T}"/>, but asynchronous.
/// </summary>
/// <typeparam name="T">type of the value to store</typeparam>
/// <param name="valueFactory">generator for the value, can be asynchronous</param>
internal class AsyncLazy<T>(Func<ValueTask<T>> valueFactory): IDisposable {

private readonly SemaphoreSlim _mutex = new(1);
Expand Down Expand Up @@ -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();
}
}

}
2 changes: 1 addition & 1 deletion Kasa/Data/PowerUsage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct PowerUsage {
[JsonProperty("total_wh")] public int CumulativeEnergySinceBoot { get; internal set; }

/// <summary>
/// Create a fake response object from <see cref="IKasaOutlet.IEnergyMeterCommands.GetInstantaneousPowerUsage"/>, useful for mocking.
/// Create a fake response object from <see cref="IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage"/>, useful for mocking.
/// </summary>
/// <param name="current">How much current is being used, in milliamperes (mA).</param>
/// <param name="voltage">How much voltage is being used, in millivolts (mV).</param>
Expand Down
2 changes: 1 addition & 1 deletion Kasa/Data/Timer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public struct Timer {
/// <summary>
/// <para>How long the timer should wait, since it was started, before turning on or off.</para>
/// <para>When the timer elapses and is no longer running, this value will become <see cref="TimeSpan.Zero"/>.</para>
/// <para>To get the duration remaining from the current time, call <see cref="IKasaOutlet.ITimerCommands.Get"/> and check the <see cref="RemainingDuration"/> property.</para>
/// <para>To get the duration remaining from the current time, call <see cref="IKasaOutletBase.ITimerCommandsSingleOutlet.Get"/> and check the <see cref="RemainingDuration"/> property.</para>
/// </summary>
[JsonProperty("delay")]
[JsonConverter(typeof(TimeSpanConverter))]
Expand Down
134 changes: 105 additions & 29 deletions Kasa/IKasaOutlet.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Kasa/Kasa.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>1.1.0</Version>
<Version>1.1.0-beta1</Version>
<Title>Kasa</Title>
<PackageId>Kasa</PackageId>
<Product>Kasa</Product>
Expand Down
43 changes: 32 additions & 11 deletions Kasa/KasaMultiOutlet.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
namespace Kasa;

public class KasaMultiOutlet: KasaOutlet, IKasaMultiOutlet, IKasaOutletBase.ISystemCommands.MultiOutlet, IKasaOutletBase.ITimerCommandsMultiOutlet, IKasaOutletBase.IScheduleCommandsMultiOutlet {
/// <summary>
/// <para>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 <see cref="IKasaMultiOutlet"/>. Individual outlet IDs used in method parameters are 0-indexed.</para>
/// <para>For devices with exactly one outlet like the EP10, you should instead construct a new instance of <see cref="KasaOutlet"/>, rather than this class.</para>
/// <para>You may optionally call <see cref="KasaOutlet.Connect"/> on each instance before using it. If you don't, it will connect automatically when sending the first command.</para>
/// <para>Remember to <c>Dispose</c> 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 <see cref="KasaOutlet.Connect"/> again.</para>
/// <para>To communicate with multiple Kasa devices, construct multiple <see cref="KasaMultiOutlet"/> instances, one per device.</para>
/// <para>Example usage:</para>
/// <code>using IKasaMultiOutlet outlet = new KasaMultiOutlet("192.168.1.100");
/// bool isOutletOn = await outlet.System.IsOutletOn(0);
/// if (!isOutletOn) {
/// await outlet.System.SetOutletOn(0, true);
/// }</code>
/// </summary>
public class KasaMultiOutlet: KasaOutlet, IKasaMultiOutlet, IKasaOutletBase.ISystemCommands.IMultiOutlet, IKasaOutletBase.ITimerCommandsMultiOutlet, IKasaOutletBase.IScheduleCommandsMultiOutlet {

private readonly AsyncLazy<IList<string>> _childIds;

Expand All @@ -16,19 +29,19 @@ internal KasaMultiOutlet(IKasaClient client): base(client) {
private AsyncLazy<IList<string>> InitChildIds() => new(async () => SystemInfoToChildIds(await System.GetInfo().ConfigureAwait(false)));

/// <inheritdoc />
public new IKasaOutletBase.ISystemCommands.MultiOutlet System => this;
public new IKasaOutletBase.ISystemCommands.IMultiOutlet System => this;

/// <inheritdoc />
public new IKasaOutletBase.ITimerCommandsMultiOutlet Timer => this;

/// <inheritdoc />
public new IKasaOutletBase.IScheduleCommandsMultiOutlet Schedule => this;

/// <exception cref="ArgumentOutOfRangeException"><paramref name="outletId"/> is outside the range of 0-indexed outlets on the device</exception>
private async Task<ChildContext> GetChildContext(int outletId) => new((await _childIds.GetValue().ConfigureAwait(false))[outletId]);

private static IList<string> SystemInfoToChildIds(SystemInfo systemInfo) => systemInfo.Children?.Select(child => child.Id).ToList() ?? [];

/// <inheritdoc />
protected override async Task<SystemInfo> GetInfo() {
SystemInfo systemInfo = await base.GetInfo().ConfigureAwait(false);
if (!_childIds.IsValueCreated) {
Expand All @@ -39,67 +52,75 @@ protected override async Task<SystemInfo> GetInfo() {

async Task<int> IKasaOutletBase.ISystemCommands.CountOutlets() => (await _childIds.GetValue().ConfigureAwait(false)).Count;

/// <exception cref="ArgumentOutOfRangeException"><paramref name="outletId"/> is outside the range of 0-indexed outlets on the device</exception>
async Task<bool> IKasaOutletBase.ISystemCommands.MultiOutlet.IsOutletOn(int outletId) {
/// <inheritdoc />
async Task<bool> 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;
}

/// <exception cref="ArgumentOutOfRangeException"><paramref name="outletId"/> is outside the range of 0-indexed outlets on the device</exception>
async Task IKasaOutletBase.ISystemCommands.MultiOutlet.SetOutletOn(int outletId, bool turnOn) {
/// <inheritdoc />
async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.SetOutletOn(int outletId, bool turnOn) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await SetOutletOn(turnOn, context).ConfigureAwait(false);
}

/// <exception cref="ArgumentOutOfRangeException"><paramref name="outletId"/> is outside the range of 0-indexed outlets on the device</exception>
async Task<string> IKasaOutletBase.ISystemCommands.MultiOutlet.GetName(int outletId) {
/// <inheritdoc />
async Task<string> 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;
}

/// <exception cref="ArgumentOutOfRangeException"><paramref name="outletId"/> is outside the range of 0-indexed outlets on the device</exception>
async Task IKasaOutletBase.ISystemCommands.MultiOutlet.SetName(int outletId, string name) {
/// <inheritdoc />
async Task IKasaOutletBase.ISystemCommands.IMultiOutlet.SetName(int outletId, string name) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await SetName(name, context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task<Timer?> IKasaOutletBase.ITimerCommandsMultiOutlet.Get(int outletId) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
return await GetTimer(context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task<Timer> IKasaOutletBase.ITimerCommandsMultiOutlet.Start(int outletId, TimeSpan duration, bool setOutletOnWhenComplete) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
return await StartTimer(duration, setOutletOnWhenComplete, context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task IKasaOutletBase.ITimerCommandsMultiOutlet.Clear(int outletId) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await ClearTimers(context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task<IEnumerable<Schedule>> IKasaOutletBase.IScheduleCommandsMultiOutlet.GetAll(int outletId) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
return await GetAllSchedules(context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task<Schedule> IKasaOutletBase.IScheduleCommandsMultiOutlet.Save(int outletId, Schedule schedule) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
return await SaveSchedule(schedule, context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.Delete(int outletId, Schedule schedule) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await DeleteSchedule(schedule, context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.Delete(int outletId, string scheduleId) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await DeleteSchedule(scheduleId, context).ConfigureAwait(false);
}

/// <inheritdoc />
async Task IKasaOutletBase.IScheduleCommandsMultiOutlet.DeleteAll(int outletId) {
ChildContext context = await GetChildContext(outletId).ConfigureAwait(false);
await DeleteAllSchedules(context).ConfigureAwait(false);
Expand Down
4 changes: 2 additions & 2 deletions Kasa/KasaOutlet.Cloud.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ public partial class KasaOutlet {

/// <inheritdoc />
async Task<bool> IKasaOutletBase.ICloudCommands.IsConnectedToCloudAccount() {
JObject response = await Client.Send<JObject>(CommandFamily.Cloud, "get_info").ConfigureAwait(false);
JObject response = await _client.Send<JObject>(CommandFamily.Cloud, "get_info").ConfigureAwait(false);
return response.Value<int?>("binded") == 1;
}

/// <inheritdoc />
Task IKasaOutletBase.ICloudCommands.DisconnectFromCloudAccount() {
return Client.Send<JObject>(CommandFamily.Cloud, "unbind");
return _client.Send<JObject>(CommandFamily.Cloud, "unbind");
}

}
8 changes: 4 additions & 4 deletions Kasa/KasaOutlet.EnergyMeter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ public partial class KasaOutlet {

/// <inheritdoc />
Task<PowerUsage> IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage() {
return Client.Send<PowerUsage>(CommandFamily.EnergyMeter, "get_realtime");
return _client.Send<PowerUsage>(CommandFamily.EnergyMeter, "get_realtime");
}

/// <inheritdoc />
async Task<IList<int>?> IKasaOutletBase.IEnergyMeterCommands.GetDailyEnergyUsage(int year, int month) {
JArray response = (JArray) (await Client.Send<JObject>(CommandFamily.EnergyMeter, "get_daystat", new { year, month }).ConfigureAwait(false))["day_list"]!;
JArray response = (JArray) (await _client.Send<JObject>(CommandFamily.EnergyMeter, "get_daystat", new { year, month }).ConfigureAwait(false))["day_list"]!;
List<int>? results = null;

if (response.Count > 0) {
Expand All @@ -32,7 +32,7 @@ Task<PowerUsage> IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage

/// <inheritdoc />
async Task<IList<int>?> IKasaOutletBase.IEnergyMeterCommands.GetMonthlyEnergyUsage(int year) {
JArray response = (JArray) (await Client.Send<JObject>(CommandFamily.EnergyMeter, "get_monthstat", new { year }).ConfigureAwait(false))["month_list"]!;
JArray response = (JArray) (await _client.Send<JObject>(CommandFamily.EnergyMeter, "get_monthstat", new { year }).ConfigureAwait(false))["month_list"]!;
List<int>? results = null;

if (response.Count > 0) {
Expand All @@ -49,7 +49,7 @@ Task<PowerUsage> IKasaOutletBase.IEnergyMeterCommands.GetInstantaneousPowerUsage

/// <inheritdoc />
Task IKasaOutletBase.IEnergyMeterCommands.DeleteHistoricalUsage() {
return Client.Send<JObject>(CommandFamily.EnergyMeter, "erase_emeter_stat");
return _client.Send<JObject>(CommandFamily.EnergyMeter, "erase_emeter_stat");
}

}
22 changes: 16 additions & 6 deletions Kasa/KasaOutlet.Schedule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ async Task<IEnumerable<Schedule>> IKasaOutletBase.IScheduleCommandsSingleOutlet.
return await GetAllSchedules(null).ConfigureAwait(false);
}

/// <exception cref="NetworkException"></exception>
/// <exception cref="ResponseParsingException"></exception>
internal async Task<IEnumerable<Schedule>> GetAllSchedules(ChildContext? context) {
JObject response = await Client.Send<JObject>(CommandFamily.Schedule, "get_rules", null, context).ConfigureAwait(false);
JObject response = await _client.Send<JObject>(CommandFamily.Schedule, "get_rules", null, context).ConfigureAwait(false);
return response["rule_list"]!.ToObject<IEnumerable<Schedule>>(KasaClient.JsonSerializer)!;
}

Expand All @@ -22,13 +24,15 @@ async Task<Schedule> IKasaOutletBase.IScheduleCommandsSingleOutlet.Save(Schedule
return await SaveSchedule(schedule, null).ConfigureAwait(false);
}

/// <exception cref="NetworkException"></exception>
/// <exception cref="ResponseParsingException"></exception>
internal async Task<Schedule> SaveSchedule(Schedule schedule, ChildContext? context) {
string id;
if (schedule.Id is null) {
JObject created = await Client.Send<JObject>(CommandFamily.Schedule, "add_rule", schedule, context).ConfigureAwait(false);
JObject created = await _client.Send<JObject>(CommandFamily.Schedule, "add_rule", schedule, context).ConfigureAwait(false);
id = created["id"]!.ToObject<string>(KasaClient.JsonSerializer)!;
} else {
await Client.Send<JObject>(CommandFamily.Schedule, "edit_rule", schedule, context).ConfigureAwait(false);
await _client.Send<JObject>(CommandFamily.Schedule, "edit_rule", schedule, context).ConfigureAwait(false);
id = schedule.Id;
}

Expand All @@ -40,7 +44,9 @@ Task IKasaOutletBase.IScheduleCommandsSingleOutlet.Delete(Schedule schedule) {
return DeleteSchedule(schedule, null);
}

/// <exception cref="ArgumentException">If the <paramref name="schedule"/> parameter has a <c>null</c> value for the <see cref="Schedule.Id"/> property, possibly because it was newly constructed instead of being fetched from <see cref="IKasaOutletBase.IScheduleCommandsSingleOutlet.GetAll"/> or <see cref="Save"/>.</exception>
/// <exception cref="ArgumentException">If the <paramref name="schedule"/> parameter has a <c>null</c> value for the <see cref="Schedule.Id"/> property, possibly because it was newly constructed instead of being fetched from <see cref="IKasaOutletBase.IScheduleCommandsSingleOutlet.GetAll"/> or <see cref="IKasaOutletBase.IScheduleCommandsSingleOutlet.Save"/>.</exception>
/// <exception cref="NetworkException"></exception>
/// <exception cref="ResponseParsingException"></exception>
internal Task DeleteSchedule(Schedule schedule, ChildContext? context) {
if (schedule.Id is not null) {
return DeleteSchedule(schedule.Id, context);
Expand All @@ -55,17 +61,21 @@ Task IKasaOutletBase.IScheduleCommandsSingleOutlet.Delete(string scheduleId) {
return DeleteSchedule(scheduleId, null);
}

/// <exception cref="NetworkException"></exception>
/// <exception cref="ResponseParsingException"></exception>
internal Task DeleteSchedule(string id, ChildContext? context) {
return Client.Send<JObject>(CommandFamily.Schedule, "delete_rule", new { id }, context);
return _client.Send<JObject>(CommandFamily.Schedule, "delete_rule", new { id }, context);
}

/// <inheritdoc />
Task IKasaOutletBase.IScheduleCommandsSingleOutlet.DeleteAll() {
return DeleteAllSchedules(null);
}

/// <exception cref="NetworkException"></exception>
/// <exception cref="ResponseParsingException"></exception>
internal Task DeleteAllSchedules(ChildContext? context) {
return Client.Send<JObject>(CommandFamily.Schedule, "delete_all_rules", null, context);
return _client.Send<JObject>(CommandFamily.Schedule, "delete_all_rules", null, context);
}

}
Loading

0 comments on commit 566390d

Please sign in to comment.