Skip to content
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
55 changes: 55 additions & 0 deletions Meraki.Api.NewTest/DeviceSensorCommandTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 4 additions & 0 deletions Meraki.Api.NewTest/MerakiClientUnitTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions Meraki.Api.NewTest/TestConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
3 changes: 2 additions & 1 deletion Meraki.Api.NewTest/appsettings.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"ApiKey": "xxx",
"ApiNode": "xxx",
"OrganizationId": "xxx",
"SwitchSerial": "xxx"
"SwitchSerial": "xxx",
"Mt40DeviceSerial": "xxx"
}
53 changes: 53 additions & 0 deletions Meraki.Api/Data/SensorCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Meraki.Api.Data;

[DataContract]
public class SensorCommand
{
/// <summary>
/// ID to check the status of the command request
/// </summary>
[DataMember(Name = "commandId")]
public string CommandId { get; set; } = string.Empty;

/// <summary>
/// Time when the command was triggered
/// </summary>
[DataMember(Name = "CreatedAt")]
public DateTime CreatedAt { get; set; }

/// <summary>
/// Time when the command was completed
/// </summary>
[DataMember(Name = "completedAt")]
public DateTime CompletedAt { get; set; }

/// <summary>
/// Information about the admin who triggered the command
/// </summary>
[DataMember(Name = "createdBy")]
public SensorCommandCreatedby CreatedBy { get; set; } = new();

/// <summary>
/// Operation run on the sensor
/// </summary>
[DataMember(Name = "operation")]
public SensorCommandOperation Operation { get; set; }

/// <summary>
/// Status of the command request
/// </summary>
[DataMember(Name = "status")]
public string Status { get; set; } = string.Empty;

/// <summary>
/// Info about the gateway that was used to connect to the sensor
/// </summary>
[DataMember(Name = "gateway")]
public SensorCommandGateway Gateway { get; set; } = new();

/// <summary>
/// Array of errors if failed
/// </summary>
[DataMember(Name = "errors")]
public List<string> Errors { get; set; } = new();
}
12 changes: 12 additions & 0 deletions Meraki.Api/Data/SensorCommandCreateRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Meraki.Api.Data;

[DataContract]
public class SensorCommandCreateRequest
{
/// <summary>
/// Operation to run on the sensor. Valid values are 'connect', 'cycleDownstreamPower', 'disableDownstreamPower', 'enableDownstreamPower'
/// </summary>
[ApiAccess(ApiAccess.ReadCreate)]
[DataMember(Name = "operation")]
public SensorCommandOperation Operation { get; set; }
}
23 changes: 23 additions & 0 deletions Meraki.Api/Data/SensorCommandCreatedby.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Meraki.Api.Data;

[DataContract]
public class SensorCommandCreatedby
{
/// <summary>
/// ID of the admin
/// </summary>
[DataMember(Name = "adminId")]
public string AdminId { get; set; } = string.Empty;

/// <summary>
/// Email of the admin
/// </summary>
[DataMember(Name = "email")]
public string Email { get; set; } = string.Empty;

/// <summary>
/// Name of the admin
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; } = string.Empty;
}
17 changes: 17 additions & 0 deletions Meraki.Api/Data/SensorCommandGateway.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Meraki.Api.Data;

[DataContract]
public class SensorCommandGateway
{
/// <summary>
/// Gateway serial
/// </summary>
[DataMember(Name = "serial")]
public string Serial { get; set; } = string.Empty;

/// <summary>
/// Gateway name
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; } = string.Empty;
}
16 changes: 16 additions & 0 deletions Meraki.Api/Data/SensorCommandOperation.cs
Original file line number Diff line number Diff line change
@@ -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,
}
38 changes: 38 additions & 0 deletions Meraki.Api/Extensions/IDeviceSensorCommandsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace Meraki.Api.Extensions;
public static class IDeviceSensorCommandsExtensions
{
/// <summary>
/// Returns a historical log of all commands
/// </summary>
/// <param name="serial">Serial</param>
/// <param name="operations">Optional parameter to filter commands by operation</param>
/// <param name="endingBefore">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.</param>
/// <param name="t0">The beginning of the timespan for the data. The maximum lookback period is 30 days from today.</param>
/// <param name="t1">The end of the timespan for the data. t1 can be a maximum of 30 days after t0.</param>
/// <param name="timespan">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.</param>
/// <param name="cancellationToken"></param>
/// <returns>A list of <see cref="SensorCommand"></returns>
public static Task<List<SensorCommand>> GetDeviceSensorCommandsAllAsync(
this IDeviceSensorCommands deviceSensorCommands,
string serial,
List<string>? 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
);
}
66 changes: 66 additions & 0 deletions Meraki.Api/Interfaces/General/Devices/IDeviceSensorCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
namespace Meraki.Api.Interfaces.General.Devices;
public interface IDeviceSensorCommands
{
/// <summary>
/// Returns a historical log of all commands
/// </summary>
/// <param name="serial">Serial</param>
/// <param name="operations">Optional parameter to filter commands by operation</param>
/// <param name="perPage">The number of entries per page returned. Acceptable range is 3 - 1000. Default is 10.</param>
/// <param name="startingAfter">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.</param>
/// <param name="endingBefore">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.</param>
/// <param name="t0">The beginning of the timespan for the data. The maximum lookback period is 30 days from today.</param>
/// <param name="t1">The end of the timespan for the data. t1 can be a maximum of 30 days after t0.</param>
/// <param name="timespan">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.</param>
/// <param name="cancellationToken"></param>
/// <returns>A list of <see cref="SensorCommand"></returns>
[ApiOperationId("getDeviceSensorCommands")]
[Get("/devices/{serial}/sensor/commands")]
Task<List<SensorCommand>> GetDeviceSensorCommandsAsync(
string serial,
[AliasAs("operations[]")] List<string>? 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<ApiResponse<List<SensorCommand>>> GetDeviceSensorCommandsApiResponseAsync(
string serial,
[AliasAs("operations[]")] List<string>? operations = null,
string? startingAfter = null,
string? endingBefore = null,
string? t0 = null,
string? t1 = null,
string? timespan = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Returns information about the command's execution, including the status
/// </summary>
/// <param name="serial">Serial</param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="SensorCommand"></returns>
[ApiOperationId("getDeviceSensorCommand")]
[Get("/devices/{serial}/sensor/commands/{id}")]
Task<SensorCommand> GetDeviceSensorCommandAsync(
string serial,
string id,
CancellationToken cancellationToken = default);

/// <summary>
/// Sends a command to a sensor
/// </summary>
/// <exception cref="ApiException">Thrown when fails to make API call</exception>
/// <param name="serial">The serial number</param>
/// <param name="createDeviceSensorCommand">Body</param>
[ApiOperationId("createDeviceSensorCommand")]
[Post("/devices/{serial}/sensor/commands")]
Task<SensorCommand> CreateDeviceSensorCommandAsync(
string serial,
[Body] SensorCommandCreateRequest createDeviceSensorCommand,
CancellationToken cancellationToken = default);
}
3 changes: 2 additions & 1 deletion Meraki.Api/MerakiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Meraki.Api/Sections/General/Devices/DevicesSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}