/// <summary> /// Sends a Unlock-Request to the chargepoint /// </summary> private async Task UnlockConnector16(ChargePointStatus chargePointStatus, HttpContext apiCallerContext) { ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16"); ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus); Messages_OCPP16.UnlockConnectorRequest unlockConnectorRequest = new Messages_OCPP16.UnlockConnectorRequest(); unlockConnectorRequest.ConnectorId = 0; string jsonResetRequest = JsonConvert.SerializeObject(unlockConnectorRequest); OCPPMessage msgOut = new OCPPMessage(); msgOut.MessageType = "2"; msgOut.Action = "UnlockConnector"; msgOut.UniqueId = Guid.NewGuid().ToString("N"); msgOut.JsonPayload = jsonResetRequest; msgOut.TaskCompletionSource = new TaskCompletionSource <string>(); // store HttpContext with MsgId for later answer processing (=> send anwer to API caller) _requestQueue.Add(msgOut.UniqueId, msgOut); // Send OCPP message with optional logging/dump await SendOcpp16Message(msgOut, logger, chargePointStatus.WebSocket); // Wait for asynchronous chargepoint response and processing string apiResult = await msgOut.TaskCompletionSource.Task; // apiCallerContext.Response.StatusCode = 200; apiCallerContext.Response.ContentType = "application/json"; await apiCallerContext.Response.WriteAsync(apiResult); }
/// <summary> /// Constructor /// </summary> public ControllerOCPP16(IConfiguration config, ILoggerFactory loggerFactory, ChargePointStatus chargePointStatus) { Configuration = config; Logger = loggerFactory.CreateLogger(typeof(ControllerOCPP16)); if (chargePointStatus != null) { ChargePointStatus = chargePointStatus; } else { Logger.LogError("New ControllerOCPP16 => empty chargepoint status"); } }
public async Task Invoke(HttpContext context) { _logger.LogTrace("OCPPMiddleware => Websocket request: Path='{0}'", context.Request.Path); ChargePointStatus chargePointStatus = null; if (context.Request.Path.StartsWithSegments("/OCPP")) { string chargepointIdentifier; string[] parts = context.Request.Path.Value.Split('/'); if (string.IsNullOrWhiteSpace(parts[parts.Length - 1])) { // (Last part - 1) is chargepoint identifier chargepointIdentifier = parts[parts.Length - 2]; } else { // Last part is chargepoint identifier chargepointIdentifier = parts[parts.Length - 1]; } _logger.LogInformation("OCPPMiddleware => Connection request with chargepoint identifier = '{0}'", chargepointIdentifier); // Known chargepoint? if (!string.IsNullOrWhiteSpace(chargepointIdentifier)) { using (OCPPCoreContext dbContext = new OCPPCoreContext(_configuration)) { ChargePoint chargePoint = dbContext.Find <ChargePoint>(chargepointIdentifier); if (chargePoint != null) { _logger.LogInformation("OCPPMiddleware => SUCCESS: Found chargepoint with identifier={0}", chargePoint.ChargePointId); // Check optional chargepoint authentication if (!string.IsNullOrWhiteSpace(chargePoint.Username)) { // Chargepoint MUST send basic authentication header bool basicAuthSuccess = false; string authHeader = context.Request.Headers["Authorization"]; if (!string.IsNullOrEmpty(authHeader)) { string[] cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(authHeader.Substring(6))).Split(':'); if (cred.Length == 2 && chargePoint.Username == cred[0] && chargePoint.Password == cred[1]) { // Authentication match => OK _logger.LogInformation("OCPPMiddleware => SUCCESS: Basic authentication for chargepoint '{0}' match", chargePoint.ChargePointId); basicAuthSuccess = true; } else { // Authentication does NOT match => Failure _logger.LogWarning("OCPPMiddleware => FAILURE: Basic authentication for chargepoint '{0}' does NOT match", chargePoint.ChargePointId); } } if (basicAuthSuccess == false) { context.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"OCPP.Core\""); context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } } else if (!string.IsNullOrWhiteSpace(chargePoint.ClientCertThumb)) { // Chargepoint MUST send basic authentication header bool certAuthSuccess = false; X509Certificate2 clientCert = context.Connection.ClientCertificate; if (clientCert != null) { if (clientCert.Thumbprint.Equals(chargePoint.ClientCertThumb, StringComparison.InvariantCultureIgnoreCase)) { // Authentication match => OK _logger.LogInformation("OCPPMiddleware => SUCCESS: Certificate authentication for chargepoint '{0}' match", chargePoint.ChargePointId); certAuthSuccess = true; } else { // Authentication does NOT match => Failure _logger.LogWarning("OCPPMiddleware => FAILURE: Certificate authentication for chargepoint '{0}' does NOT match", chargePoint.ChargePointId); } } if (certAuthSuccess == false) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } } else { _logger.LogInformation("OCPPMiddleware => No authentication for chargepoint '{0}' configured", chargePoint.ChargePointId); } // Store chargepoint data chargePointStatus = new ChargePointStatus(chargePoint); } else { _logger.LogWarning("OCPPMiddleware => FAILURE: Found no chargepoint with identifier={0}", chargepointIdentifier); } } } if (chargePointStatus != null) { if (context.WebSockets.IsWebSocketRequest) { // Match supported sub protocols string subProtocol = null; foreach (string supportedProtocol in SupportedProtocols) { if (context.WebSockets.WebSocketRequestedProtocols.Contains(supportedProtocol)) { subProtocol = supportedProtocol; break; } } if (string.IsNullOrEmpty(subProtocol)) { // Not matching protocol! => failure string protocols = string.Empty; foreach (string p in context.WebSockets.WebSocketRequestedProtocols) { if (string.IsNullOrEmpty(protocols)) { protocols += ","; } protocols += p; } _logger.LogWarning("OCPPMiddleware => No supported sub-protocol in '{0}' from charge station '{1}'", protocols, chargepointIdentifier); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } else { chargePointStatus.Protocol = subProtocol; bool statusSuccess = false; try { _logger.LogTrace("OCPPMiddleware => Store/Update status object"); lock (_chargePointStatusDict) { // Check if this chargepoint already/still hat a status object if (_chargePointStatusDict.ContainsKey(chargepointIdentifier)) { // exists => check status if (_chargePointStatusDict[chargepointIdentifier].WebSocket.State != WebSocketState.Open) { // Closed or aborted => remove _chargePointStatusDict.Remove(chargepointIdentifier); } } _chargePointStatusDict.Add(chargepointIdentifier, chargePointStatus); statusSuccess = true; } } catch (Exception exp) { _logger.LogError(exp, "OCPPMiddleware => Error storing status object in dictionary => refuse connection"); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } if (statusSuccess) { // Handle socket communication _logger.LogTrace("OCPPMiddleware => Waiting for message..."); using (WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol)) { _logger.LogTrace("OCPPMiddleware => WebSocket connection with charge point '{0}'", chargepointIdentifier); chargePointStatus.WebSocket = webSocket; if (subProtocol == Protocol_OCPP20) { // OCPP V2.0 await Receive20(chargePointStatus, context); } else { // OCPP V1.6 await Receive16(chargePointStatus, context); } } } } } else { // no websocket request => failure _logger.LogWarning("OCPPMiddleware => Non-Websocket request"); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } else { // unknown chargepoint _logger.LogTrace("OCPPMiddleware => no chargepoint: http 412"); context.Response.StatusCode = (int)HttpStatusCode.PreconditionFailed; } } else if (context.Request.Path.StartsWithSegments("/API")) { // Check authentication (X-API-Key) string apiKeyConfig = _configuration.GetValue <string>("ApiKey"); if (!string.IsNullOrWhiteSpace(apiKeyConfig)) { // ApiKey specified => check request string apiKeyCaller = context.Request.Headers["X-API-Key"].FirstOrDefault(); if (apiKeyConfig == apiKeyCaller) { // API-Key matches _logger.LogInformation("OCPPMiddleware => Success: X-API-Key matches"); } else { // API-Key does NOT matches => authentication failure!!! _logger.LogWarning("OCPPMiddleware => Failure: Wrong X-API-Key! Caller='{0}'", apiKeyCaller); context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } } else { // No API-Key configured => no authenticatiuon _logger.LogWarning("OCPPMiddleware => No X-API-Key configured!"); } // format: /API/<command>[/chargepointId] string[] urlParts = context.Request.Path.Value.Split('/'); if (urlParts.Length >= 3) { string cmd = urlParts[2]; string urlChargePointId = (urlParts.Length >= 4) ? urlParts[3] : null; _logger.LogTrace("OCPPMiddleware => cmd='{0}' / id='{1}' / FullPath='{2}')", cmd, urlChargePointId, context.Request.Path.Value); if (cmd == "Status") { try { List <ChargePointStatus> statusList = new List <ChargePointStatus>(); foreach (ChargePointStatus status in _chargePointStatusDict.Values) { statusList.Add(status); } string jsonStatus = JsonConvert.SerializeObject(statusList); context.Response.ContentType = "application/json"; await context.Response.WriteAsync(jsonStatus); } catch (Exception exp) { _logger.LogError(exp, "OCPPMiddleware => Error: {0}", exp.Message); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else if (cmd == "Reset") { if (!string.IsNullOrEmpty(urlChargePointId)) { try { ChargePointStatus status = null; if (_chargePointStatusDict.TryGetValue(urlChargePointId, out status)) { // Send message to chargepoint if (status.Protocol == Protocol_OCPP20) { // OCPP V2.0 await Reset20(status, context); } else { // OCPP V1.6 await Reset16(status, context); } } else { // Chargepoint offline _logger.LogError("OCPPMiddleware SoftReset => Chargepoint offline: {0}", urlChargePointId); context.Response.StatusCode = (int)HttpStatusCode.NotFound; } } catch (Exception exp) { _logger.LogError(exp, "OCPPMiddleware SoftReset => Error: {0}", exp.Message); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { _logger.LogError("OCPPMiddleware SoftReset => Missing chargepoint ID"); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } else if (cmd == "UnlockConnector") { if (!string.IsNullOrEmpty(urlChargePointId)) { try { ChargePointStatus status = null; if (_chargePointStatusDict.TryGetValue(urlChargePointId, out status)) { // Send message to chargepoint if (status.Protocol == Protocol_OCPP20) { // OCPP V2.0 await UnlockConnector20(status, context); } else { // OCPP V1.6 await UnlockConnector16(status, context); } } else { // Chargepoint offline _logger.LogError("OCPPMiddleware UnlockConnector => Chargepoint offline: {0}", urlChargePointId); context.Response.StatusCode = (int)HttpStatusCode.NotFound; } } catch (Exception exp) { _logger.LogError(exp, "OCPPMiddleware UnlockConnector => Error: {0}", exp.Message); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { _logger.LogError("OCPPMiddleware UnlockConnector => Missing chargepoint ID"); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } else { // Unknown action/function _logger.LogWarning("OCPPMiddleware => action/function: {0}", cmd); context.Response.StatusCode = (int)HttpStatusCode.NotFound; } } } else if (context.Request.Path.StartsWithSegments("/")) { try { bool showIndexInfo = _configuration.GetValue <bool>("ShowIndexInfo"); if (showIndexInfo) { _logger.LogTrace("OCPPMiddleware => Index status page"); context.Response.ContentType = "text/plain"; await context.Response.WriteAsync(string.Format("Running...\r\n\r\n{0} chargepoints connected", _chargePointStatusDict.Values.Count)); } else { _logger.LogInformation("OCPPMiddleware => Root path with deactivated index page"); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } } catch (Exception exp) { _logger.LogError(exp, "OCPPMiddleware => Error: {0}", exp.Message); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { _logger.LogWarning("OCPPMiddleware => Bad path request"); context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } }
/// <summary> /// Waits for new OCPP V1.6 messages on the open websocket connection and delegates processing to a controller /// </summary> private async Task Receive16(ChargePointStatus chargePointStatus, HttpContext context) { ILogger logger = _logFactory.CreateLogger("OCPPMiddleware.OCPP16"); ControllerOCPP16 controller16 = new ControllerOCPP16(_configuration, _logFactory, chargePointStatus); byte[] buffer = new byte[1024 * 4]; MemoryStream memStream = new MemoryStream(buffer.Length); while (chargePointStatus.WebSocket.State == WebSocketState.Open) { WebSocketReceiveResult result = await chargePointStatus.WebSocket.ReceiveAsync(new ArraySegment <byte>(buffer), CancellationToken.None); if (result != null && result.MessageType != WebSocketMessageType.Close) { logger.LogTrace("OCPPMiddleware.Receive16 => Receiving segment: {0} bytes (EndOfMessage={1} / MsgType={2})", result.Count, result.EndOfMessage, result.MessageType); memStream.Write(buffer, 0, result.Count); if (result.EndOfMessage) { // read complete message into byte array byte[] bMessage = memStream.ToArray(); // reset memory stream für next message memStream = new MemoryStream(buffer.Length); string dumpDir = _configuration.GetValue <string>("MessageDumpDir"); if (!string.IsNullOrWhiteSpace(dumpDir)) { string path = Path.Combine(dumpDir, string.Format("{0}_ocpp16-in.txt", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-ffff"))); try { // Write incoming message into dump directory File.WriteAllBytes(path, bMessage); } catch (Exception exp) { logger.LogError(exp, "OCPPMiddleware.Receive16 => Error dumping incoming message to path: '{0}'", path); } } string ocppMessage = UTF8Encoding.UTF8.GetString(bMessage); Match match = Regex.Match(ocppMessage, MessageRegExp); if (match != null && match.Groups != null && match.Groups.Count >= 3) { string messageTypeId = match.Groups[1].Value; string uniqueId = match.Groups[2].Value; string action = match.Groups[3].Value; string jsonPaylod = match.Groups[4].Value; logger.LogInformation("OCPPMiddleware.Receive16 => OCPP-Message: Type={0} / ID={1} / Action={2})", messageTypeId, uniqueId, action); OCPPMessage msgIn = new OCPPMessage(messageTypeId, uniqueId, action, jsonPaylod); if (msgIn.MessageType == "2") { // Request from chargepoint to OCPP server OCPPMessage msgOut = controller16.ProcessRequest(msgIn); // Send OCPP message with optional logging/dump await SendOcpp16Message(msgOut, logger, chargePointStatus.WebSocket); } else if (msgIn.MessageType == "3" || msgIn.MessageType == "4") { // Process answer from chargepoint if (_requestQueue.ContainsKey(msgIn.UniqueId)) { controller16.ProcessAnswer(msgIn, _requestQueue[msgIn.UniqueId]); _requestQueue.Remove(msgIn.UniqueId); } else { logger.LogError("OCPPMiddleware.Receive16 => HttpContext from caller not found / Msg: {0}", ocppMessage); } } else { // Unknown message type logger.LogError("OCPPMiddleware.Receive16 => Unknown message type: {0} / Msg: {1}", msgIn.MessageType, ocppMessage); } } else { logger.LogWarning("OCPPMiddleware.Receive16 => Error in RegEx-Matching: Msg={0})", ocppMessage); } } } else { logger.LogInformation("OCPPMiddleware.Receive16 => WebSocket Closed: CloseStatus={0} / MessageType={1}", result?.CloseStatus, result?.MessageType); await chargePointStatus.WebSocket.CloseOutputAsync((WebSocketCloseStatus)3001, string.Empty, CancellationToken.None); } } logger.LogInformation("OCPPMiddleware.Receive16 => Websocket closed: State={0} / CloseStatus={1}", chargePointStatus.WebSocket.State, chargePointStatus.WebSocket.CloseStatus); ChargePointStatus dummy; _chargePointStatusDict.Remove(chargePointStatus.Id, out dummy); }