diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dfb437d3..331ad0cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 1.38.x +- Added Device Sensor Commands endpoints (currently in Early-Access) + ## 1.38.1 - Initial updates for 1.38 API changes - Added missing member Tag on AlertFilter diff --git a/Meraki.Api.NewTest/DeviceSensorCommandTests.cs b/Meraki.Api.NewTest/DeviceSensorCommandTests.cs new file mode 100644 index 000000000..86280fdf9 --- /dev/null +++ b/Meraki.Api.NewTest/DeviceSensorCommandTests.cs @@ -0,0 +1,55 @@ +using FluentAssertions; +using Meraki.Api.Data; +using Meraki.Api.Extensions; +using Xunit.Abstractions; + +namespace Meraki.Api.NewTest; + +[Collection("API Collection")] +public class DeviceSensorCommandTests : MerakiClientUnitTest +{ + public DeviceSensorCommandTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + + [Fact] + public async Task EnableMT40Device_Succeeds() + { + var command = await TestMerakiClient + .Devices + .SensorCommands + .CreateDeviceSensorCommandAsync( + TestMt40DeviceSerial, + new() + { + Operation = SensorCommandOperation.EnableDownstreamPower + }, + CancellationToken.None + ); + + _ = command.Status.Should().Be("pending"); + } + + [Fact] + public async Task GetAll_Succeeds() + { + var commands = await TestMerakiClient + .Devices + .SensorCommands + .GetDeviceSensorCommandsAllAsync(TestMt40DeviceSerial); + + _ = commands.Should().NotBeEmpty(); + } + + [Fact] + public async Task GetAllPaged_Succeeds() + { + var commands = await TestMerakiClient + .Devices + .SensorCommands + .GetDeviceSensorCommandsAsync(TestMt40DeviceSerial, perPage: 3); + + _ = commands.Should().NotBeEmpty(); + _ = commands.Should().HaveCount(3); + } +} diff --git a/Meraki.Api.NewTest/MerakiClientUnitTest.cs b/Meraki.Api.NewTest/MerakiClientUnitTest.cs index 07dd3dd46..c2d2a127e 100644 --- a/Meraki.Api.NewTest/MerakiClientUnitTest.cs +++ b/Meraki.Api.NewTest/MerakiClientUnitTest.cs @@ -8,6 +8,9 @@ public abstract class MerakiClientUnitTest { protected string TestOrganizationId { get; } protected string TestSwitchSerial { get; } + + protected string TestMt40DeviceSerial { get; } + #pragma warning disable CS3003 // Type is not CLS-compliant protected ICacheLogger Logger { get; } #pragma warning restore CS3003 // Type is not CLS-compliant @@ -26,6 +29,7 @@ protected MerakiClientUnitTest(ITestOutputHelper testOutputHelper) }; TestOrganizationId = testConfig.OrganizationId; TestSwitchSerial = testConfig.SwitchSerial; + TestMt40DeviceSerial = testConfig.Mt40DeviceSerial; Logger = testOutputHelper.BuildLogger(); TestMerakiClient = new MerakiClient(merakiClientOptions, Logger); } diff --git a/Meraki.Api.NewTest/TestConfig.cs b/Meraki.Api.NewTest/TestConfig.cs index cbde67b09..15b1dbb20 100644 --- a/Meraki.Api.NewTest/TestConfig.cs +++ b/Meraki.Api.NewTest/TestConfig.cs @@ -5,4 +5,6 @@ public class TestConfig public string ApiNode { get; set; } = string.Empty; public string OrganizationId { get; set; } = string.Empty; public string SwitchSerial { get; set; } = string.Empty; + + public string Mt40DeviceSerial { get; set; } = string.Empty; } diff --git a/Meraki.Api.NewTest/appsettings.example.json b/Meraki.Api.NewTest/appsettings.example.json index e117c461c..2137402ea 100644 --- a/Meraki.Api.NewTest/appsettings.example.json +++ b/Meraki.Api.NewTest/appsettings.example.json @@ -2,5 +2,6 @@ "ApiKey": "xxx", "ApiNode": "xxx", "OrganizationId": "xxx", - "SwitchSerial": "xxx" + "SwitchSerial": "xxx", + "Mt40DeviceSerial": "xxx" } \ No newline at end of file diff --git a/Meraki.Api/Data/SensorCommand.cs b/Meraki.Api/Data/SensorCommand.cs new file mode 100644 index 000000000..882f251bc --- /dev/null +++ b/Meraki.Api/Data/SensorCommand.cs @@ -0,0 +1,53 @@ +namespace Meraki.Api.Data; + +[DataContract] +public class SensorCommand +{ + /// + /// ID to check the status of the command request + /// + [DataMember(Name = "commandId")] + public string CommandId { get; set; } = string.Empty; + + /// + /// Time when the command was triggered + /// + [DataMember(Name = "CreatedAt")] + public DateTime CreatedAt { get; set; } + + /// + /// Time when the command was completed + /// + [DataMember(Name = "completedAt")] + public DateTime CompletedAt { get; set; } + + /// + /// Information about the admin who triggered the command + /// + [DataMember(Name = "createdBy")] + public SensorCommandCreatedby CreatedBy { get; set; } = new(); + + /// + /// Operation run on the sensor + /// + [DataMember(Name = "operation")] + public SensorCommandOperation Operation { get; set; } + + /// + /// Status of the command request + /// + [DataMember(Name = "status")] + public string Status { get; set; } = string.Empty; + + /// + /// Info about the gateway that was used to connect to the sensor + /// + [DataMember(Name = "gateway")] + public SensorCommandGateway Gateway { get; set; } = new(); + + /// + /// Array of errors if failed + /// + [DataMember(Name = "errors")] + public List Errors { get; set; } = new(); +} diff --git a/Meraki.Api/Data/SensorCommandCreateRequest.cs b/Meraki.Api/Data/SensorCommandCreateRequest.cs new file mode 100644 index 000000000..411cef897 --- /dev/null +++ b/Meraki.Api/Data/SensorCommandCreateRequest.cs @@ -0,0 +1,12 @@ +namespace Meraki.Api.Data; + +[DataContract] +public class SensorCommandCreateRequest +{ + /// + /// Operation to run on the sensor. Valid values are 'connect', 'cycleDownstreamPower', 'disableDownstreamPower', 'enableDownstreamPower' + /// + [ApiAccess(ApiAccess.ReadCreate)] + [DataMember(Name = "operation")] + public SensorCommandOperation Operation { get; set; } +} diff --git a/Meraki.Api/Data/SensorCommandCreatedby.cs b/Meraki.Api/Data/SensorCommandCreatedby.cs new file mode 100644 index 000000000..7957583fd --- /dev/null +++ b/Meraki.Api/Data/SensorCommandCreatedby.cs @@ -0,0 +1,23 @@ +namespace Meraki.Api.Data; + +[DataContract] +public class SensorCommandCreatedby +{ + /// + /// ID of the admin + /// + [DataMember(Name = "adminId")] + public string AdminId { get; set; } = string.Empty; + + /// + /// Email of the admin + /// + [DataMember(Name = "email")] + public string Email { get; set; } = string.Empty; + + /// + /// Name of the admin + /// + [DataMember(Name = "name")] + public string Name { get; set; } = string.Empty; +} diff --git a/Meraki.Api/Data/SensorCommandGateway.cs b/Meraki.Api/Data/SensorCommandGateway.cs new file mode 100644 index 000000000..252aa7432 --- /dev/null +++ b/Meraki.Api/Data/SensorCommandGateway.cs @@ -0,0 +1,17 @@ +namespace Meraki.Api.Data; + +[DataContract] +public class SensorCommandGateway +{ + /// + /// Gateway serial + /// + [DataMember(Name = "serial")] + public string Serial { get; set; } = string.Empty; + + /// + /// Gateway name + /// + [DataMember(Name = "name")] + public string Name { get; set; } = string.Empty; +} diff --git a/Meraki.Api/Data/SensorCommandOperation.cs b/Meraki.Api/Data/SensorCommandOperation.cs new file mode 100644 index 000000000..d5eb837a9 --- /dev/null +++ b/Meraki.Api/Data/SensorCommandOperation.cs @@ -0,0 +1,16 @@ +namespace Meraki.Api.Data; + +public enum SensorCommandOperation +{ + [EnumMember(Value = "connect")] + Connect = 1, + + [EnumMember(Value = "cycleDownstreamPower")] + CycleDownstreamPower, + + [EnumMember(Value = "disableDownstreamPower")] + DisableDownstreamPower, + + [EnumMember(Value = "enableDownstreamPower")] + EnableDownstreamPower, +} \ No newline at end of file diff --git a/Meraki.Api/Extensions/IDeviceSensorCommandsExtensions.cs b/Meraki.Api/Extensions/IDeviceSensorCommandsExtensions.cs new file mode 100644 index 000000000..5f270f6e5 --- /dev/null +++ b/Meraki.Api/Extensions/IDeviceSensorCommandsExtensions.cs @@ -0,0 +1,38 @@ +namespace Meraki.Api.Extensions; +public static class IDeviceSensorCommandsExtensions +{ + /// + /// Returns a historical log of all commands + /// + /// Serial + /// Optional parameter to filter commands by operation + /// A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + /// The beginning of the timespan for the data. The maximum lookback period is 30 days from today. + /// The end of the timespan for the data. t1 can be a maximum of 30 days after t0. + /// The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be less than or equal to 30 days. The default is 1 day. + /// + /// A list of + public static Task> GetDeviceSensorCommandsAllAsync( + this IDeviceSensorCommands deviceSensorCommands, + string serial, + List? operations = null, + string? endingBefore = null, + string? t0 = null, + string? t1 = null, + string? timespan = null, + CancellationToken cancellationToken = default) + => MerakiClient.GetAllAsync( + (startingAfter, cancellationToken) + => deviceSensorCommands.GetDeviceSensorCommandsApiResponseAsync( + serial, + operations, + startingAfter, + endingBefore, + t0, + t1, + timespan, + cancellationToken + ), + cancellationToken + ); +} diff --git a/Meraki.Api/Interfaces/General/Devices/IDeviceSensorCommands.cs b/Meraki.Api/Interfaces/General/Devices/IDeviceSensorCommands.cs new file mode 100644 index 000000000..4b1030a03 --- /dev/null +++ b/Meraki.Api/Interfaces/General/Devices/IDeviceSensorCommands.cs @@ -0,0 +1,66 @@ +namespace Meraki.Api.Interfaces.General.Devices; +public interface IDeviceSensorCommands +{ + /// + /// Returns a historical log of all commands + /// + /// Serial + /// Optional parameter to filter commands by operation + /// The number of entries per page returned. Acceptable range is 3 - 1000. Default is 10. + /// A token used by the server to indicate the start of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + /// A token used by the server to indicate the end of the page. Often this is a timestamp or an ID but it is not limited to those. This parameter should not be defined by client applications. The link for the first, last, prev, or next page in the HTTP Link header should define it. + /// The beginning of the timespan for the data. The maximum lookback period is 30 days from today. + /// The end of the timespan for the data. t1 can be a maximum of 30 days after t0. + /// The timespan for which the information will be fetched. If specifying timespan, do not specify parameters t0 and t1. The value must be in seconds and be less than or equal to 30 days. The default is 1 day. + /// + /// A list of + [ApiOperationId("getDeviceSensorCommands")] + [Get("/devices/{serial}/sensor/commands")] + Task> GetDeviceSensorCommandsAsync( + string serial, + [AliasAs("operations[]")] List? operations = null, + int? perPage = 1000, + string? startingAfter = null, + string? endingBefore = null, + string? t0 = null, + string? t1 = null, + string? timespan = null, + CancellationToken cancellationToken = default); + + [Get("/devices/{serial}/sensor/commands")] + internal Task>> GetDeviceSensorCommandsApiResponseAsync( + string serial, + [AliasAs("operations[]")] List? operations = null, + string? startingAfter = null, + string? endingBefore = null, + string? t0 = null, + string? t1 = null, + string? timespan = null, + CancellationToken cancellationToken = default); + + /// + /// Returns information about the command's execution, including the status + /// + /// Serial + /// + /// A + [ApiOperationId("getDeviceSensorCommand")] + [Get("/devices/{serial}/sensor/commands/{id}")] + Task GetDeviceSensorCommandAsync( + string serial, + string id, + CancellationToken cancellationToken = default); + + /// + /// Sends a command to a sensor + /// + /// Thrown when fails to make API call + /// The serial number + /// Body + [ApiOperationId("createDeviceSensorCommand")] + [Post("/devices/{serial}/sensor/commands")] + Task CreateDeviceSensorCommandAsync( + string serial, + [Body] SensorCommandCreateRequest createDeviceSensorCommand, + CancellationToken cancellationToken = default); +} diff --git a/Meraki.Api/MerakiClient.cs b/Meraki.Api/MerakiClient.cs index a28986709..0afa62693 100644 --- a/Meraki.Api/MerakiClient.cs +++ b/Meraki.Api/MerakiClient.cs @@ -48,7 +48,8 @@ public MerakiClient(MerakiClientOptions options, ILogger? logger = default) Clients = RefitFor(Devices.Clients), LldpCdp = RefitFor(Devices.LldpCdp), LossAndLatencyHistory = RefitFor(Devices.LossAndLatencyHistory), - ManagementInterface = RefitFor(Devices.ManagementInterface) + ManagementInterface = RefitFor(Devices.ManagementInterface), + SensorCommands = RefitFor(Devices.SensorCommands), }; Organizations = new OrganizationsSection diff --git a/Meraki.Api/Sections/General/Devices/DevicesSection.cs b/Meraki.Api/Sections/General/Devices/DevicesSection.cs index 9c25d4438..34bc91f92 100644 --- a/Meraki.Api/Sections/General/Devices/DevicesSection.cs +++ b/Meraki.Api/Sections/General/Devices/DevicesSection.cs @@ -10,4 +10,5 @@ public partial class DevicesSection public IDeviceLldpCdp LldpCdp { get; internal set; } = null!; public IDeviceLossAndLatencyHistory LossAndLatencyHistory { get; internal set; } = null!; public IDeviceManagementInterface ManagementInterface { get; internal set; } = null!; + public IDeviceSensorCommands SensorCommands { get; internal set; } = null!; }