Skip to content

Feature/admin api #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 25, 2024
Merged
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
31 changes: 6 additions & 25 deletions 00_Base/src/interfaces/router/AbstractRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// SPDX-License-Identifier: Apache 2.0

import Ajv, { ErrorObject } from "ajv";

import { Call, CallAction, CallResult, ICache, SystemConfig, CALL_SCHEMA_MAP, CALL_RESULT_SCHEMA_MAP, IMessageConfirmation, MessageOrigin, OcppError, OcppRequest, OcppResponse, IMessageHandler, IMessageSender, IMessage, MessageState } from "../..";
import { ILogObj, Logger } from "tslog";
import { IMessageRouter } from "./Router";
Expand All @@ -23,7 +24,7 @@ export abstract class AbstractMessageRouter implements IMessageRouter {
protected _networkHook: (identifier: string, message: string) => Promise<boolean>;

/**
* Constructor of abstract central system.
* Constructor of abstract ocpp router.
*
* @param {Ajv} ajv - The Ajv instance to use for schema validation.
*/
Expand Down Expand Up @@ -69,7 +70,7 @@ export abstract class AbstractMessageRouter implements IMessageRouter {
set config(config: SystemConfig) {
this._config = config;
// Update all necessary settings for hot reload
this._logger.info(`Updating system configuration for central system...`);
this._logger.info(`Updating system configuration for ocpp router...`);
this._logger.settings.minLevel = this._config.logLevel;
}

Expand All @@ -79,39 +80,19 @@ export abstract class AbstractMessageRouter implements IMessageRouter {

abstract onMessage(identifier: string, message: string): Promise<boolean>;

abstract registerConnection(connectionIdentifier: string): Promise<boolean>;
abstract deregisterConnection(connectionIdentifier: string): Promise<boolean>;

abstract sendCall(identifier: string, tenantId: string, action: CallAction, payload: OcppRequest, correlationId?: string, origin?: MessageOrigin): Promise<IMessageConfirmation>;
abstract sendCallResult(correlationId: string, identifier: string, tenantId: string, action: CallAction, payload: OcppResponse, origin?: MessageOrigin): Promise<IMessageConfirmation>;
abstract sendCallError(correlationId: string, identifier: string, tenantId: string, action: CallAction, error: OcppError, origin?: MessageOrigin): Promise<IMessageConfirmation>;


abstract shutdown(): void;

/**
* Public Methods
*/

async registerConnection(connectionIdentifier: string): Promise<boolean> {
const requestSubscription = await this._handler.subscribe(connectionIdentifier, undefined, {
stationId: connectionIdentifier,
state: MessageState.Request.toString(),
origin: MessageOrigin.CentralSystem.toString()
});

const responseSubscription = await this._handler.subscribe(connectionIdentifier, undefined, {
stationId: connectionIdentifier,
state: MessageState.Response.toString(),
origin: MessageOrigin.ChargingStation.toString()
});

return requestSubscription && responseSubscription;
}

async deregisterConnection(connectionIdentifier: string): Promise<boolean> {
// TODO: ensure that all queue implementations in 02_Util only unsubscribe 1 queue per call
// ...which will require refactoring this method to unsubscribe request and response queues separately
return await this._handler.unsubscribe(connectionIdentifier)
}

async handle(message: IMessage<OcppRequest | OcppResponse>): Promise<void> {
this._logger.debug("Received message:", message);

Expand Down
2 changes: 1 addition & 1 deletion 00_Base/src/interfaces/router/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { IModule } from "../..";

/**
* Interface for the central system
* Interface for the ocpp router
*/
export interface IMessageRouter extends IModule {
/**
Expand Down
5 changes: 3 additions & 2 deletions 00_Base/src/ocpp/persistence/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ export enum Namespace {
MeterValueType = 'MeterValue',
ModemType = 'Modem',
SecurityEventNotificationRequest = 'SecurityEvent',
Subscription = 'Subscription',
SystemConfig = 'SystemConfig',
TransactionEventRequest = 'TransactionEvent',
TransactionType = 'Transaction',
VariableAttributeType = 'VariableAttribute',
VariableCharacteristicsType = 'VariableCharacteristics',
VariableStatus = 'VariableStatus',
VariableType = 'Variable',
SystemConfig = 'SystemConfig'
VariableType = 'Variable'
}
73 changes: 26 additions & 47 deletions 02_Util/src/networkconnection/WebsocketNetworkConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ export class WebsocketNetworkConnection {
private _httpServers: (http.Server | https.Server)[];
private _authenticator: IAuthenticator;
private _router: IMessageRouter;
// private _onConnectionCallbacks: ((identifier: string, info?: Map<string, string>) => Promise<boolean>)[] = [];
// private _onCloseCallbacks: ((identifier: string, info?: Map<string, string>) => Promise<boolean>)[] = [];
// private _onMessageCallbacks: ((identifier: string, message: string, info?: Map<string, string>) => Promise<boolean>)[] = [];

constructor(
config: SystemConfig,
Expand Down Expand Up @@ -87,18 +84,6 @@ export class WebsocketNetworkConnection {
});
}

// addOnConnectionCallback(onConnectionCallback: (identifier: string, info?: Map<string, string>) => Promise<boolean>): void {
// this._onConnectionCallbacks.push(onConnectionCallback);
// }

// addOnCloseCallback(onCloseCallback: (identifier: string, info?: Map<string, string>) => Promise<boolean>): void {
// this._onCloseCallbacks.push(onCloseCallback);
// }

// addOnMessageCallback(onMessageCallback: (identifier: string, message: string, info?: Map<string, string>) => Promise<boolean>): void {
// this._onMessageCallbacks.push(onMessageCallback);
// }

/**
* Send a message to the charging station specified by the identifier.
*
Expand All @@ -107,29 +92,34 @@ export class WebsocketNetworkConnection {
* @return {boolean} True if the method sends the message successfully, false otherwise.
*/
sendMessage(identifier: string, message: string): Promise<boolean> {
return this._cache.get(identifier, CacheNamespace.Connections).then(clientConnection => {
if (clientConnection) {
const websocketConnection = this._identifierConnections.get(identifier);
if (websocketConnection && websocketConnection.readyState === WebSocket.OPEN) {
websocketConnection.send(message, (error) => {
if (error) {
this._logger.error("On message send error", error);
}
}); // TODO: Handle errors
// TODO: Embed error handling into websocket message flow
return true;
return new Promise<boolean>((resolve, reject) => {
this._cache.get(identifier, CacheNamespace.Connections).then(clientConnection => {
if (clientConnection) {
const websocketConnection = this._identifierConnections.get(identifier);
if (websocketConnection && websocketConnection.readyState === WebSocket.OPEN) {
websocketConnection.send(message, (error) => {
if (error) {
this._logger.error("On message send error", error);
reject(error); // Reject the promise with the error
} else {
resolve(true); // Resolve the promise with true indicating success
}
});
} else {
const errorMsg = "Websocket connection is not ready - " + identifier;
this._logger.fatal(errorMsg);
websocketConnection?.close(1011, errorMsg);
reject(new Error(errorMsg)); // Reject with a new error
}
} else {
this._logger.fatal("Websocket connection is not ready -", identifier);
websocketConnection?.close(1011, "Websocket connection is not ready - " + identifier);
return false;
const errorMsg = "Cannot identify client connection for " + identifier;
// This can happen when a charging station disconnects in the moment a message is trying to send.
// Retry logic on the message sender might not suffice as charging station might connect to different instance.
this._logger.error(errorMsg);
this._identifierConnections.get(identifier)?.close(1011, "Failed to get connection information for " + identifier);
reject(new Error(errorMsg)); // Reject with a new error
}
} else {
// This can happen when a charging station disconnects in the moment a message is trying to send.
// Retry logic on the message sender might not suffice as charging station might connect to different instance.
this._logger.error("Cannot identify client connection for", identifier);
this._identifierConnections.get(identifier)?.close(1011, "Failed to get connection information for " + identifier);
return false;
}
}).catch(reject); // In case `_cache.get` fails
});
}

Expand Down Expand Up @@ -248,11 +238,6 @@ export class WebsocketNetworkConnection {

this._router.registerConnection(identifier);

// await this._onConnectionCallbacks.forEach(async callback => {
// const info = new Map<string, string>([["ip", ip], ["port", port.toString()]]);
// await callback(identifier, info);
// });

this._logger.info("Successfully connected new charging station.", identifier);

// Register all websocket events
Expand Down Expand Up @@ -290,9 +275,6 @@ export class WebsocketNetworkConnection {
this._cache.remove(identifier, CacheNamespace.Connections);
this._identifierConnections.delete(identifier);
this._router.deregisterConnection(identifier);
// this._onCloseCallbacks.forEach(callback => {
// callback(identifier);
// });
});

ws.on("pong", async () => {
Expand Down Expand Up @@ -321,9 +303,6 @@ export class WebsocketNetworkConnection {
*/
private _onMessage(identifier: string, message: string): void {
this._router.onMessage(identifier, message);
// this._onMessageCallbacks.forEach(callback => {
// callback(identifier, message);
// });
}

/**
Expand Down
1 change: 0 additions & 1 deletion 02_Util/src/networkconnection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@

export { Authenticator } from "./authenticator/Authenticator"
export { WebsocketNetworkConnection } from "./WebsocketNetworkConnection"
export { MessageRouterImpl } from "./router/MessageRouter"
2 changes: 1 addition & 1 deletion 03_Modules/Configuration/src/module/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export class ConfigurationModule extends AbstractModule {
}
// Handle post-response actions
if (bootNotificationResponseMessageConfirmation.success) {
this._logger.debug("BootNotification response successfully sent to central system: ", bootNotificationResponseMessageConfirmation);
this._logger.debug("BootNotification response successfully sent to ocpp router: ", bootNotificationResponseMessageConfirmation);

// Update charger-specific boot config with details of most recently sent BootNotificationResponse
let bootConfigDbEntity: Boot | undefined = await this._bootRepository.readByKey(stationId);
Expand Down
11 changes: 11 additions & 0 deletions 03_Modules/OcppRouter/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
"env": {
"browser": false,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
}
40 changes: 40 additions & 0 deletions 03_Modules/OcppRouter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@citrineos/ocpprouter",
"version": "1.0.0",
"description": "The ocpprouter module for OCPP v2.0.1. This module is not intended to be used directly, but rather as a dependency for other modules.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib"
],
"scripts": {
"prepublish": "npx eslint",
"prepare": "npm run build",
"build": "tsc",
"refresh-base": "cd ../../00_Base && npm run build && npm pack && cd ../03_Modules/OcppRouter && npm install ../../00_Base/citrineos-base-1.0.0.tgz",
"refresh-data": "cd ../../01_Data && npm run build && npm pack && cd ../03_Modules/OcppRouter && npm install ../../01_Data/citrineos-data-1.0.0.tgz",
"refresh-util": "cd ../../02_Util && npm run build && npm pack && cd ../03_Modules/OcppRouter && npm install ../../02_Util/citrineos-util-1.0.0.tgz",
"install-all": "npm install ../../00_Base/citrineos-base-1.0.0.tgz && npm install ../../02_Util/citrineos-util-1.0.0.tgz && npm install ../../01_Data/citrineos-data-1.0.0.tgz",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"ocpp",
"ocpp_v201"
],
"license": "Apache-2.0",
"devDependencies": {
"@types/deasync-promise": "^1.0.0",
"@types/node-forge": "^1.3.1",
"@types/uuid": "^9.0.1",
"eslint": "^8.48.0",
"typescript": "^5.0.4"
},
"dependencies": {
"@citrineos/base": "file:../../00_Base/citrineos-base-1.0.0.tgz",
"@citrineos/data": "file:../../01_Data/citrineos-data-1.0.0.tgz",
"@citrineos/util": "file:../../02_Util/citrineos-util-1.0.0.tgz",
"fastify": "^4.22.2",
"node-forge": "^1.3.1",
"uuid": "^9.0.0"
}
}
8 changes: 8 additions & 0 deletions 03_Modules/OcppRouter/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2023 S44, LLC
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0

export { AdminApi } from './module/api';
export { IAdminApi } from './module/interface';
export { MessageRouterImpl } from './module/router';
Loading