Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion OCPP.Core.Management/Controllers/HomeController.ChargePoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Core.Pipeline;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OCPP.Core.Database;
using OCPP.Core.Management.Models;

Expand All @@ -32,7 +36,7 @@ namespace OCPP.Core.Management.Controllers
public partial class HomeController : BaseController
{
[Authorize]
public IActionResult ChargePoint(string Id, ChargePointViewModel cpvm)
public async Task<IActionResult> ChargePoint(string Id, ChargePointViewModel cpvm)
{
try
{
Expand Down Expand Up @@ -186,6 +190,59 @@ public IActionResult ChargePoint(string Id, ChargePointViewModel cpvm)
cpvm.Username = currentChargePoint.Username;
cpvm.Password = currentChargePoint.Password;
cpvm.ClientCertThumb = currentChargePoint.ClientCertThumb;

// Attempt to get configuration information from the charge point
try
{
string serverApiUrl = base.Config.GetValue<string>("ServerApiUrl");
string apiKeyConfig = base.Config.GetValue<string>("ApiKey");
if(!string.IsNullOrEmpty(serverApiUrl))
{
using var httpClient = new System.Net.Http.HttpClient();
if(!serverApiUrl.EndsWith('/'))
serverApiUrl += "/";

var uri = new Uri(serverApiUrl + "GetConfiguration/" + Id);

// API-Key authentication?
if (!string.IsNullOrWhiteSpace(apiKeyConfig))
{
httpClient.DefaultRequestHeaders.Add("X-API-Key", apiKeyConfig);
}
else
{
Logger.LogWarning("GetConfiguration: No API-Key configured!");
}

var response = await httpClient.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
string jsonResult = await response.Content.ReadAsStringAsync();
if(!string.IsNullOrEmpty(jsonResult))
{
dynamic jsonObject = JsonConvert.DeserializeObject(jsonResult);
Logger.LogInformation("GetConfiguration: Result of API request is '{0}'", jsonResult);
foreach(var kv in jsonObject.configurationKey)
{
Logger.LogTrace("GetConfiguration: {0}:{1} ({2})", (string)kv.key, (string)kv.value, (bool)kv.@readonly);
cpvm.DeviceConfiguration.Add((string)kv.key, ((string)kv.value, (bool) kv.@readonly));
}
}
else
{
Logger.LogError("GetConfiguration: Result is empty");
}
}
else
{
Logger.LogError("GetConfiguration: Error received {0}: {1}", response.StatusCode, await response.Content.ReadAsStringAsync());
}
}
}
catch(Exception exp)
{
Logger.LogError(exp, "GetConfiguration call failed.");
}
}

string viewName = (!string.IsNullOrEmpty(cpvm.ChargePointId) || Id == "@") ? "ChargePointDetail" : "ChargePointList";
Expand Down
2 changes: 2 additions & 0 deletions OCPP.Core.Management/Models/ChargePointViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@ public class ChargePointViewModel

[StringLength(100)]
public string ClientCertThumb { get; set; }

public Dictionary<string, (string, bool)> DeviceConfiguration { get; set; } = new Dictionary<string, (string, bool)>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@
<data name="Title" xml:space="preserve">
<value>Management</value>
</data>
<data name="TitleDeviceConfiguration" xml:space="preserve">
<value>Aktuelle Gerätekonfiguration</value>
</data>
<data name="TitleReset" xml:space="preserve">
<value>Neustart</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@
<data name="Title" xml:space="preserve">
<value>Management</value>
</data>
<data name="TitleDeviceConfiguration" xml:space="preserve">
<value>Current Device Configuration</value>
</data>
<data name="TitleReset" xml:space="preserve">
<value>Restart</value>
</data>
Expand Down
21 changes: 21 additions & 0 deletions OCPP.Core.Management/Views/Home/ChargePointDetail.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,28 @@
</div>
<input type="hidden" name="action" id="hidAction" value="" />
}
<br />

@if(Model.DeviceConfiguration?.Count > 0)
{
<h4>@Localizer["TitleDeviceConfiguration"]</h4>
<br />
<div class="container">
<div class="row">
<div class="col-sm-5"><b>Key</b></div>
<div class="col-sm-6"><b>Value</b></div>
<div class="col-sm-1"><b>Writable</b></div>
</div>
@foreach(var config in Model.DeviceConfiguration)
{
<div class="row border-top">
<div class="col-sm-5">@config.Key</div>
<div class="col-sm-6">@config.Value.Item1</div>
<div class="col-sm-1"><i class="fas fa-@(config.Value.Item2 ? "times" : "check")"></i></div>
</div>
}
</div>
}

@section scripts {
@if (!string.IsNullOrWhiteSpace(Model.ChargePointId))
Expand Down
30 changes: 30 additions & 0 deletions OCPP.Core.Server/ControllerOCPP16.GetConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.Extensions.Logging;
using OCPP.Core.Server.Messages_OCPP16;
using System;

namespace OCPP.Core.Server
{
public partial class ControllerOCPP16
{
public void HandleGetConfiguration(OCPPMessage msgIn, OCPPMessage msgOut)
{
Logger.LogInformation("GetConfiguration answer: ChargePointId={0} / MsgType={1} / ErrCode={2}", ChargePointStatus.Id, msgIn.MessageType, msgIn.ErrorCode);

try
{
var getConfigurationResponse = DeserializeMessage<GetConfigurationResponse>(msgIn);
Logger.LogInformation("GetConfiguration => KeyCount: {0}", getConfigurationResponse.ConfigurationKey?.Count);
WriteMessageLog(ChargePointStatus?.Id, null, msgOut.Action, getConfigurationResponse.ConfigurationKey?.ToString(), msgIn.ErrorCode);

if(msgOut.TaskCompletionSource != null)
{
msgOut.TaskCompletionSource.SetResult(msgIn.JsonPayload);
}
}
catch(Exception exp)
{
Logger.LogError(exp, "GetConfiguration => Exception: {0}", exp.Message);
}
}
}
}
4 changes: 4 additions & 0 deletions OCPP.Core.Server/ControllerOCPP16.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ public void ProcessAnswer(OCPPMessage msgIn, OCPPMessage msgOut)
HandleClearChargingProfile(msgIn, msgOut);
break;

case "GetConfiguration":
HandleGetConfiguration(msgIn, msgOut);
break;

default:
WriteMessageLog(ChargePointStatus.Id, null, msgIn.Action, msgIn.JsonPayload, "Unknown answer");
break;
Expand Down
11 changes: 11 additions & 0 deletions OCPP.Core.Server/Messages_OCPP16/GetConfigurationRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace OCPP.Core.Server.Messages_OCPP16
{
public class GetConfigurationRequest
{
[JsonProperty("key")]
public ICollection<string> Key { get; set; }
}
}
20 changes: 20 additions & 0 deletions OCPP.Core.Server/Messages_OCPP16/GetConfigurationResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Newtonsoft.Json;
using System.Collections.Generic;

namespace OCPP.Core.Server.Messages_OCPP16
{
public class GetConfigurationResponse
{
[JsonProperty("configurationKey")]
public ICollection<OcppKeyValue> ConfigurationKey { get; set; }
[JsonProperty("unknownKey")]
public ICollection<string> UnknownKey { get; set; }
}

public class OcppKeyValue
{
public string Key { get; set; }
public bool ReadOnly { get; set; }
public string Value { get; set; }
}
}
37 changes: 37 additions & 0 deletions OCPP.Core.Server/OCPPMiddleware.OCPP16.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,43 @@ private async Task ClearChargingProfile16(ChargePointStatus chargePointStatus, H
await apiCallerContext.Response.WriteAsync(apiResult);
}

/// <summary>
/// Sends a GetConfiguration message to the chargepoint
/// </summary>
private async Task GetConfiguration16(ChargePointStatus chargePointStatus, HttpContext apiCallerContext, OCPPCoreContext dbContext, string urlConnectorId)
{
ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16");
ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus, dbContext);

// By default, don't specify any keys, if a reduced set is required then specify keys in object.
var getConfigRequest = new GetConfigurationRequest();

logger.LogTrace("OCPPMiddleware.OCPP16 => GetConfiguration16: ChargePoint='{0}'", chargePointStatus.Id);

string jsonGetConfigRequest = JsonConvert.SerializeObject(getConfigRequest);

OCPPMessage msgOut = new OCPPMessage()
{
MessageType = "2",
Action = "GetConfiguration",
UniqueId = Guid.NewGuid().ToString("N"),
JsonPayload = JsonConvert.SerializeObject(getConfigRequest),
TaskCompletionSource = new TaskCompletionSource<string>()
};

_requestQueue.Add(msgOut.UniqueId, msgOut);

await SendOcpp16Message(msgOut, logger, chargePointStatus);

string apiResult = await msgOut.TaskCompletionSource.Task;

logger.LogTrace("OCPPMiddleware.OCPP16 => GetConfiguration16: Response='{0}'", apiResult);

apiCallerContext.Response.StatusCode = 200;
apiCallerContext.Response.ContentType = "application/json";
await apiCallerContext.Response.WriteAsync(apiResult);
}

private async Task SendOcpp16Message(OCPPMessage msg, ILogger logger, ChargePointStatus chargePointStatus)
{
// Send raw outgoing messages to extensions
Expand Down
41 changes: 41 additions & 0 deletions OCPP.Core.Server/OCPPMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -565,6 +566,46 @@ public async Task Invoke(HttpContext context, OCPPCoreContext dbContext)
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
}
else if (cmd == "GetConfiguration")
{
if(!string.IsNullOrEmpty(urlChargePointId))
{
try
{
ChargePointStatus status = null;
if(_chargePointStatusDict.TryGetValue(urlChargePointId, out status))
{
if(status.Protocol == Protocol_OCPP16)
{
// OCPP V1.6
await GetConfiguration16(status, context, dbContext, urlConnectorId);
}
else
{
throw new NotImplementedException("GetConfiguration not yet implemented for OCPP 2.0");
}
}
else
{
// Chargepoint offline
_logger.LogError("OCPPMiddleware GetConfiguration => Chargepoint offline: {0}", urlChargePointId);
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
catch(Exception exp)
{
_logger.LogError(exp, "OCPPMiddleware GetConfiguration => Error: {0}", exp.Message);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var msg = Encoding.UTF8.GetBytes(exp.Message);
await context.Response.Body.WriteAsync(msg, 0, msg.Length);
}
}
else
{
_logger.LogError("OCPPMiddleware GetConfiguration => Missing chargepoint ID");
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
}
}
else
{
// Unknown action/function
Expand Down