diff --git a/src/PVOutput.Net/Builders/BatchNetStatusPostBuilder.cs b/src/PVOutput.Net/Builders/BatchNetStatusPostBuilder.cs
new file mode 100644
index 0000000..8cf7c60
--- /dev/null
+++ b/src/PVOutput.Net/Builders/BatchNetStatusPostBuilder.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Dawn;
+using PVOutput.Net.Objects;
+using PVOutput.Net.Objects.Core;
+using PVOutput.Net.Objects.Modules;
+
+namespace PVOutput.Net.Builders
+{
+ ///
+ /// Builder that creates batch outputs to post to PVOutput.
+ ///
+ public sealed class BatchNetStatusPostBuilder
+ {
+ internal BatchNetStatusPost _statusPost { get; set; }
+
+ ///
+ /// Creates a new builder.
+ ///
+ public BatchNetStatusPostBuilder()
+ {
+ _statusPost = new BatchNetStatusPost();
+ }
+
+ ///
+ /// Sets the timestamp for the status.
+ ///
+ /// Timestamp.
+ /// The builder.
+ public BatchNetStatusPostBuilder SetTimeStamp(DateTime timestamp)
+ {
+ Guard.Argument(timestamp, nameof(timestamp)).IsNoFutureDate();
+
+ _statusPost.Timestamp = timestamp;
+ return this;
+ }
+
+ ///
+ /// Sets the net power exported for the status.
+ ///
+ /// Net power exported consumed.
+ /// The builder.
+ public BatchNetStatusPostBuilder SetPowerExported(int powerExported)
+ {
+ Guard.Argument(powerExported, nameof(powerExported)).Min(0);
+
+ _statusPost.PowerExported = powerExported;
+ return this;
+ }
+
+ ///
+ /// Sets the net power imported for the status.
+ ///
+ /// Net power imported consumed.
+ /// The builder.
+ public BatchNetStatusPostBuilder SetPowerImported(int powerImported)
+ {
+ Guard.Argument(powerImported, nameof(powerImported)).Min(0);
+
+ _statusPost.PowerImported = powerImported;
+ return this;
+ }
+
+ ///
+ /// Resets the builder to it's default state. Ready to build a new status.
+ ///
+ public void Reset() => _statusPost = new BatchNetStatusPost();
+
+ ///
+ /// Uses information within the builder to return the built status.
+ ///
+ /// The status.
+ public IBatchNetStatusPost Build()
+ {
+ ValidateStatus();
+ return _statusPost;
+ }
+
+ ///
+ /// Uses information within the builder to return the built status.
+ /// Resets the builder to it's default state after building.
+ ///
+ /// The status.
+ public IBatchNetStatusPost BuildAndReset()
+ {
+ IBatchNetStatusPost result = Build();
+ Reset();
+ return result;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Exception messages are non translatable for now")]
+ private void ValidateStatus()
+ {
+ if (_statusPost.PowerExported == null
+ && _statusPost.PowerImported == null)
+ {
+ throw new InvalidOperationException("Status has no power values");
+ }
+ }
+ }
+}
diff --git a/src/PVOutput.Net/Builders/StatusPostBuilder.cs b/src/PVOutput.Net/Builders/StatusPostBuilder.cs
index 780ffa6..e6b0e9b 100644
--- a/src/PVOutput.Net/Builders/StatusPostBuilder.cs
+++ b/src/PVOutput.Net/Builders/StatusPostBuilder.cs
@@ -36,8 +36,7 @@ public StatusPostBuilder SetTimeStamp(DateTime timestamp)
Guard.Argument(timestamp, nameof(timestamp)).IsNoFutureDate();
_statusPost.Timestamp = timestamp;
- return this;
-
+ return this;
}
///
diff --git a/src/PVOutput.Net/Modules/StatusService.cs b/src/PVOutput.Net/Modules/StatusService.cs
index f947994..8953e22 100644
--- a/src/PVOutput.Net/Modules/StatusService.cs
+++ b/src/PVOutput.Net/Modules/StatusService.cs
@@ -4,6 +4,7 @@
using System.Threading.Tasks;
using Dawn;
using PVOutput.Net.Builders;
+using PVOutput.Net.Enums;
using PVOutput.Net.Objects;
using PVOutput.Net.Objects.Core;
using PVOutput.Net.Requests.Handler;
@@ -141,6 +142,50 @@ public Task> AddBatchStatusAsync(I
return handler.ExecuteArrayRequestAsync(new AddBatchStatusRequest() { StatusPosts = statuses }, loggingScope, cancellationToken);
}
+ ///
+ /// Adds multiple statuses to the owned system.
+ /// See the official API information.
+ /// Use the to create objects.
+ ///
+ /// The statuses to add.
+ /// Sets whether or not the provided data is cumulative.
+ /// A cancellation token for the request.
+ /// If the operation succeeded.
+ public Task> AddBatchStatusAsync(IEnumerable statuses, bool isCumulative, CancellationToken cancellationToken = default)
+ {
+ var loggingScope = new Dictionary()
+ {
+ [LoggingEvents.RequestId] = LoggingEvents.StatusService_AddBatchStatus,
+ [LoggingEvents.Parameter_CumulativeType] = isCumulative
+ };
+
+ Guard.Argument(statuses, nameof(statuses)).NotNull().NotEmpty();
+
+ var handler = new RequestHandler(Client);
+ return handler.ExecuteArrayRequestAsync(new AddBatchStatusRequest() { StatusPosts = statuses, Cumulative = isCumulative }, loggingScope, cancellationToken);
+ }
+
+ ///
+ /// Adds multiple statuses to the owned system.
+ /// See the official API information.
+ /// Use the to create objects.
+ ///
+ /// The statuses to add.
+ /// A cancellation token for the request.
+ /// If the operation succeeded.
+ public Task> AddBatchNetStatusAsync(IEnumerable statuses, CancellationToken cancellationToken = default)
+ {
+ var loggingScope = new Dictionary()
+ {
+ [LoggingEvents.RequestId] = LoggingEvents.StatusService_AddNetBatchStatus
+ };
+
+ Guard.Argument(statuses, nameof(statuses)).NotNull().NotEmpty();
+
+ var handler = new RequestHandler(Client);
+ return handler.ExecuteArrayRequestAsync(new AddBatchNetStatusRequest() { StatusPosts = statuses }, loggingScope, cancellationToken);
+ }
+
///
/// Deletes a status on the specified moment.
/// See the official API information.
diff --git a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs
index e22f271..2f2e2ce 100644
--- a/src/PVOutput.Net/Objects/Core/LoggingEvents.cs
+++ b/src/PVOutput.Net/Objects/Core/LoggingEvents.cs
@@ -42,6 +42,7 @@ internal class LoggingEvents
public const string Parameter_ApplicationId = "ApplicationId";
public const string Parameter_CallBackUrl = "CallBackUrl";
public const string Parameter_AlertType = "AlertType";
+ public const string Parameter_CumulativeType = "CumulativeType";
/*
* RequestHandler base events
@@ -88,6 +89,7 @@ internal class LoggingEvents
public static readonly EventId StatusService_AddStatus = new EventId(20804, "AddStatus");
public static readonly EventId StatusService_AddBatchStatus = new EventId(20805, "AddBatchStatus");
public static readonly EventId StatusService_DeleteStatus = new EventId(20806, "DeleteStatus");
+ public static readonly EventId StatusService_AddNetBatchStatus = new EventId(20807, "AddNetBatchStatus");
public static readonly EventId SupplyService_GetSupply = new EventId(20901, "GetSupply");
public static readonly EventId SystemService_GetOwnSystem = new EventId(21001, "GetOwnSystem");
public static readonly EventId SystemService_GetOtherSystem = new EventId(21002, "GetOtherSystem");
diff --git a/src/PVOutput.Net/Objects/IBatchNetStatusPost.cs b/src/PVOutput.Net/Objects/IBatchNetStatusPost.cs
new file mode 100644
index 0000000..8fb5de6
--- /dev/null
+++ b/src/PVOutput.Net/Objects/IBatchNetStatusPost.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace PVOutput.Net.Objects
+{
+ ///
+ /// A single net batch status used for posting multiple statuses.
+ ///
+ public interface IBatchNetStatusPost
+ {
+ ///
+ /// Timestamp for the recorded status.
+ ///
+ DateTime Timestamp { get; set; }
+
+ ///
+ /// Total energy generated up to and including the timestamp.
+ ///
+ int? PowerExported { get; set; }
+
+ ///
+ /// Actual power being generated at the moment of the timestamp.
+ ///
+ int? PowerImported { get; set; }
+ }
+}
diff --git a/src/PVOutput.Net/Objects/Modules/BatchNetStatusPost.cs b/src/PVOutput.Net/Objects/Modules/BatchNetStatusPost.cs
new file mode 100644
index 0000000..161c88e
--- /dev/null
+++ b/src/PVOutput.Net/Objects/Modules/BatchNetStatusPost.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PVOutput.Net.Objects.Modules
+{
+ internal class BatchNetStatusPost : IBatchNetStatusPost
+ {
+ public DateTime Timestamp { get; set; }
+ public int? PowerExported { get; set; }
+ public int? PowerImported { get; set; }
+ }
+}
diff --git a/src/PVOutput.Net/Requests/Modules/AddBatchNetStatusRequest.cs b/src/PVOutput.Net/Requests/Modules/AddBatchNetStatusRequest.cs
new file mode 100644
index 0000000..5ad7465
--- /dev/null
+++ b/src/PVOutput.Net/Requests/Modules/AddBatchNetStatusRequest.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using PVOutput.Net.Objects.Core;
+using PVOutput.Net.Objects;
+using PVOutput.Net.Requests.Base;
+
+namespace PVOutput.Net.Requests.Modules
+{
+ internal class AddBatchNetStatusRequest : PostRequest
+ {
+ public IEnumerable StatusPosts { get; set; }
+
+ public override HttpMethod Method => HttpMethod.Post;
+
+ public override string UriTemplate => "addbatchstatus.jsp{?n,data}";
+
+ public override IDictionary GetUriPathParameters() => new Dictionary
+ {
+ ["n"] = 1,
+ ["data"] = FormatStatusPosts()
+ };
+
+ private string FormatStatusPosts()
+ {
+ var sb = new StringBuilder();
+
+ foreach (IBatchNetStatusPost status in StatusPosts)
+ {
+ sb.Append(FormatStatusPost(status)).Append(';');
+ }
+
+ return sb.ToString();
+ }
+
+ internal static string FormatStatusPost(IBatchNetStatusPost status)
+ {
+ var sb = new StringBuilder();
+ sb.Append(FormatHelper.GetDateAsString(status.Timestamp));
+ sb.Append(',');
+ sb.Append(FormatHelper.GetTimeAsString(status.Timestamp));
+ sb.Append(",-1,"); // Skip single field as per documentation
+ sb.Append(status.PowerExported);
+ sb.Append(",-1,"); // Skip single field as per documentation
+ sb.Append(status.PowerImported);
+ return sb.ToString();
+ }
+ }
+}
diff --git a/src/PVOutput.Net/Requests/Modules/AddBatchStatusRequest.cs b/src/PVOutput.Net/Requests/Modules/AddBatchStatusRequest.cs
index 1e219e3..8f85531 100644
--- a/src/PVOutput.Net/Requests/Modules/AddBatchStatusRequest.cs
+++ b/src/PVOutput.Net/Requests/Modules/AddBatchStatusRequest.cs
@@ -13,9 +13,7 @@ internal class AddBatchStatusRequest : PostRequest
{
public IEnumerable StatusPosts { get; set; }
- public bool Net { get; set; }
-
- public CumulativeStatusType Cumulative { get; set; }
+ public bool Cumulative { get; set; }
public override HttpMethod Method => HttpMethod.Post;
@@ -23,8 +21,8 @@ internal class AddBatchStatusRequest : PostRequest
public override IDictionary GetUriPathParameters() => new Dictionary
{
- ["c1"] = Cumulative != CumulativeStatusType.None ? (int?)Cumulative : null,
- ["n"] = Net ? 1 : 0,
+ ["c1"] = Cumulative ? 1 : 0,
+ ["n"] = 0,
["data"] = FormatStatusPosts()
};
diff --git a/tests/PVOutput.Net.Tests/Modules/Status/AddBatchNetStatusRequestTests.cs b/tests/PVOutput.Net.Tests/Modules/Status/AddBatchNetStatusRequestTests.cs
new file mode 100644
index 0000000..cb775ed
--- /dev/null
+++ b/tests/PVOutput.Net.Tests/Modules/Status/AddBatchNetStatusRequestTests.cs
@@ -0,0 +1,46 @@
+using System;
+using NUnit.Framework;
+using PVOutput.Net.Requests.Modules;
+using PVOutput.Net.Objects.Modules.Implementations;
+using System.Threading.Tasks;
+using PVOutput.Net.Objects;
+using PVOutput.Net.Tests.Utils;
+using System.Collections.Generic;
+using System.Linq;
+using RichardSzalay.MockHttp;
+using PVOutput.Net.Builders;
+using PVOutput.Net.Objects.Modules;
+
+namespace PVOutput.Net.Tests.Modules.Status
+{
+ public class AddBatchNetStatusRequestTests : BaseRequestsTest
+ {
+ private string[] GetSplitStatusPostLine(BatchNetStatusPost post) => AddBatchNetStatusRequest.FormatStatusPost(post).Split(',');
+
+ [Test]
+ public void Parameter_Timestamp_CreatesCorrectUriParameters()
+ {
+ var post = new BatchNetStatusPost() { Timestamp = new DateTime(2020, 2, 1, 13, 12, 20) };
+
+ string[] postArray = GetSplitStatusPostLine(post);
+ Assert.That(postArray[0], Is.EqualTo("20200201"));
+ Assert.That(postArray[1], Is.EqualTo("13:12"));
+ }
+
+ [Test]
+ public void Parameter_PowerExported_CreatesCorrectUriParameters()
+ {
+ var post = new BatchNetStatusPost() { PowerExported = 1111 };
+ string[] postArray = GetSplitStatusPostLine(post);
+ Assert.That(postArray[3], Is.EqualTo("1111"));
+ }
+
+ [Test]
+ public void Parameter_PowerImported_CreatesCorrectUriParameters()
+ {
+ var post = new BatchNetStatusPost() { PowerImported = 2222 };
+ string[] postArray = GetSplitStatusPostLine(post);
+ Assert.That(postArray[5], Is.EqualTo("2222"));
+ }
+ }
+}
diff --git a/tests/PVOutput.Net.Tests/Modules/Status/BatchNetStatusBuilderTests.cs b/tests/PVOutput.Net.Tests/Modules/Status/BatchNetStatusBuilderTests.cs
new file mode 100644
index 0000000..638ad53
--- /dev/null
+++ b/tests/PVOutput.Net.Tests/Modules/Status/BatchNetStatusBuilderTests.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using PVOutput.Net.Builders;
+using PVOutput.Net.Enums;
+using PVOutput.Net.Objects;
+using PVOutput.Net.Objects.Factories;
+using PVOutput.Net.Objects.Modules;
+using PVOutput.Net.Requests.Modules;
+using PVOutput.Net.Tests.Utils;
+
+namespace PVOutput.Net.Tests.Modules.Status
+{
+ [TestFixture]
+ public class BatchNetStatusBuilderTests
+ {
+ [Test]
+ public void BatchNetStatusBuilder_WithTimeStamp_SetsTimeStamp()
+ {
+ var timeStamp = DateTime.Now;
+ var builder = new StatusPostBuilder().SetTimeStamp(timeStamp);
+
+ Assert.That(builder._statusPost.Timestamp, Is.EqualTo(timeStamp));
+ }
+
+ [Test]
+ public void BatchNetStatusBuilder_WithFutureTimeStamp_Throws()
+ {
+ var timeStamp = DateTime.Now.AddDays(1);
+ var builder = new StatusPostBuilder();
+
+ Assert.Throws(() =>
+ {
+ builder.SetTimeStamp(timeStamp);
+ });
+ }
+
+ [Test]
+ [TestCase(100)]
+ [TestCase(0)]
+ public void BatchNetStatusBuilder_WithPowerExported_SetsPowerExported(int powerExported)
+ {
+ var builder = new BatchNetStatusPostBuilder().SetPowerExported(powerExported);
+
+ Assert.That(builder._statusPost.PowerExported, Is.EqualTo(powerExported));
+ }
+
+ [Test]
+ [TestCase(100)]
+ [TestCase(0)]
+ public void BatchNetStatusBuilder_WithPowerImported_SetsPowerImported(int powerImported)
+ {
+ var builder = new BatchNetStatusPostBuilder().SetPowerImported(powerImported);
+
+ Assert.That(builder._statusPost.PowerImported, Is.EqualTo(powerImported));
+ }
+
+ [Test]
+ public void BatchNetStatusBuilder_AfterReset_HasNoStateLeft()
+ {
+ var builder = new BatchNetStatusPostBuilder().SetPowerExported(1000).SetTimeStamp(DateTime.Now);
+ IBatchNetStatusPost status = builder.Build();
+
+ builder.Reset();
+
+ Assert.That(builder._statusPost, Is.Not.SameAs(status));
+ }
+
+
+ [Test]
+ public void BatchNetStatusBuilder_AfterBuildAndReset_HasNoStateLeft()
+ {
+ var builder = new BatchNetStatusPostBuilder().SetPowerImported(1000).SetTimeStamp(DateTime.Now);
+ IBatchNetStatusPost status = builder.BuildAndReset();
+
+ Assert.That(builder._statusPost, Is.Not.SameAs(status));
+ }
+
+ [Test]
+ public void BatchNetStatusBuilder_WithoutPowerOrConsumption_CannotBuild()
+ {
+ var builder = new BatchNetStatusPostBuilder().SetTimeStamp(DateTime.Now);
+
+ Assert.Throws(() =>
+ {
+ builder.Build();
+ });
+ }
+ }
+}
diff --git a/tests/PVOutput.Net.Tests/Modules/Status/StatusServiceTests.cs b/tests/PVOutput.Net.Tests/Modules/Status/StatusServiceTests.cs
index 3655462..cd32f08 100644
--- a/tests/PVOutput.Net.Tests/Modules/Status/StatusServiceTests.cs
+++ b/tests/PVOutput.Net.Tests/Modules/Status/StatusServiceTests.cs
@@ -356,6 +356,58 @@ public async Task StatusService_AddBatchStatus_CallsCorrectUri()
testProvider.VerifyNoOutstandingExpectation();
}
+ [Test]
+ public async Task StatusService_AddBatchCumulativeStatus_CallsCorrectUri()
+ {
+ var batchStatus = new StatusPostBuilder().SetTimeStamp(new DateTime(2020, 1, 1, 12, 22, 0))
+ .SetGeneration(11000).SetConsumption(9000).Build();
+
+ PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider);
+ testProvider.ExpectUriFromBase(ADDBATCHSTATUS_URL)
+ .WithQueryString("n=0&c1=1&data=20200101,12:22,11000,,9000,,,,,,,,,;")
+ .RespondPlainText("");
+
+ await client.Status.AddBatchStatusAsync(new[] { batchStatus }, true);
+ testProvider.VerifyNoOutstandingExpectation();
+ }
+
+ [Test]
+ public void StatusService_AddBatchNetStatus_WithNullStatuses_Throws()
+ {
+ PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider);
+
+ Assert.ThrowsAsync(async () =>
+ {
+ _ = await client.Status.AddBatchNetStatusAsync(null);
+ });
+ }
+
+ [Test]
+ public void StatusService_AddBatchNetStatus_WithEmptyStatuses_Throws()
+ {
+ PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider);
+
+ Assert.ThrowsAsync(async () =>
+ {
+ _ = await client.Status.AddBatchNetStatusAsync(new List());
+ });
+ }
+
+ [Test]
+ public async Task StatusService_AddBatchNetStatus_CallsCorrectUri()
+ {
+ var batchStatus = new BatchNetStatusPostBuilder().SetTimeStamp(new DateTime(2020, 1, 1, 12, 22, 0))
+ .SetPowerExported(11000).SetPowerImported(9000).Build();
+
+ PVOutputClient client = TestUtility.GetMockClient(out MockHttpMessageHandler testProvider);
+ testProvider.ExpectUriFromBase(ADDBATCHSTATUS_URL)
+ .WithQueryString("n=1&data=20200101,12:22,-1,11000,-1,9000;")
+ .RespondPlainText("");
+
+ await client.Status.AddBatchNetStatusAsync(new[] { batchStatus });
+ testProvider.VerifyNoOutstandingExpectation();
+ }
+
/*
* Deserialisation tests below
*/