@citrineos/base
Version:
The base module for OCPP v2.0.1 including all interfaces. This module is not intended to be used directly, but rather as a dependency for other modules.
306 lines • 15 kB
JavaScript
"use strict";
// Copyright (c) 2023 S44, LLC
// Copyright Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache 2.0
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AbstractModule = void 0;
require("reflect-metadata");
const tslog_1 = require("tslog");
const uuid_1 = require("uuid");
const _1 = require(".");
const message_1 = require("../../ocpp/rpc/message");
const request_1 = require("../../util/request");
const types_1 = require("../cache/types");
const messages_1 = require("../messages");
class AbstractModule {
constructor(config, cache, handler, sender, eventGroup, logger) {
this._requests = [];
this._responses = [];
this.startTime = Date.now();
this._logger = this._initLogger(logger);
this._logger.info('Initializing...');
this._config = config;
this._handler = handler;
this._sender = sender;
this._eventGroup = eventGroup;
this._cache = cache;
// Set module for proper message flow.
this.handler.module = this;
}
/**
* Getters & Setters
*/
get cache() {
return this._cache;
}
get sender() {
return this._sender;
}
get handler() {
return this._handler;
}
get config() {
return this._config;
}
/**
* Sets the system configuration for the module.
*
* @param {SystemConfig} config - The new configuration to set.
*/
set config(config) {
this._config = config;
// Update all necessary settings for hot reload
this._logger.info(`Updating system configuration for ${this._eventGroup} module...`);
this._logger.settings.minLevel = this._config.logLevel;
}
/**
* Methods
*/
/**
* Handles a message with an OcppRequest or OcppResponse payload.
*
* @param {IMessage<OcppRequest | OcppResponse>} message - The message to handle.
* @param {HandlerProperties} props - Optional properties for the handler.
* @return {void} This function does not return anything.
*/
handle(message, props) {
return __awaiter(this, void 0, void 0, function* () {
if (message.state === messages_1.MessageState.Response) {
yield this.handleMessageApiCallback(message);
yield this._cache.set(message.context.correlationId, JSON.stringify(message.payload), message.context.stationId, this._config.maxCachingSeconds);
}
try {
const handlerDefinition = Reflect.getMetadata(_1.AS_HANDLER_METADATA, this.constructor)
.filter((h) => h.protocol === message.protocol && h.action === message.action)
.pop();
if (handlerDefinition) {
yield handlerDefinition.method.call(this, message, props);
}
else {
throw new message_1.OcppError(message.context.correlationId, message_1.ErrorCode.NotSupported, 'No handler found for action: ' + message.action + ' at module ' + this._eventGroup);
}
}
catch (error) {
this._logger.error('Failed handling message: ', error, message);
if (message.state === messages_1.MessageState.Request) {
// CallErrors are only emitted for Calls
this._logger.error('Sending CallError to ChargingStation...');
message.origin = messages_1.MessageOrigin.ChargingStationManagementSystem;
if (error instanceof message_1.OcppError) {
yield this._sender.sendResponse(message, error);
}
else if (error instanceof Error) {
yield this._sender.sendResponse(message, new message_1.OcppError(message.context.correlationId, message_1.ErrorCode.InternalError, 'Failed handling message: ' + error.message));
}
else {
this._logger.warn("Unknown error type, couldn't send CallError");
}
}
}
});
}
/**
* Interface methods.
*/
/**
* Unimplemented method to handle incoming {@link IMessage}.
*
* **Note**: This method is **programmatically** overridden by the {@link ModuleHandlers} annotation.
*
* @param message The {@link IMessage} to handle. Can contain either a {@link OcppRequest} or a {@link OcppResponse} as payload.
* @param props The {@link HandlerProperties} for this {@link IMessage} containing implementation specific metadata. Metadata is not used in the base implementation.
*/
handleMessageApiCallback(message) {
return __awaiter(this, void 0, void 0, function* () {
const url = yield this._cache.get(message.context.correlationId, AbstractModule.CALLBACK_URL_CACHE_PREFIX + message.context.stationId);
if (url) {
try {
yield fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(message.payload),
});
}
catch (error) {
// TODO: Ideally the error log is also stored in the database in a failed invocations table to ensure these are visible outside of a log file.
this._logger.error('Failed sending call result: ', error);
}
}
});
}
/**
* Calls shutdown on the handler and sender.
*
* Note: To be overwritten by subclass if other logic is necessary.
*
*/
shutdown() {
return __awaiter(this, void 0, void 0, function* () {
yield this._handler.shutdown();
yield this._sender.shutdown();
});
}
/**
* Default implementation
*/
/**
* Sends a call with the specified identifier, tenantId, protocol, action, payload, and origin.
*
* @param {string} identifier - The identifier of the call.
* @param {string} tenantId - The tenant ID.
* @param {string} protocol - The subprotocol of the Websocket, i.e. "ocpp1.6" or "ocpp2.0.1".
* @param {CallAction} action - The action to be performed.
* @param {OcppRequest} payload - The payload of the call.
* @param {string} [callbackUrl] - The callback URL for the call.
* @param {string} [correlationId] - The correlation ID of the call.
* @param {MessageOrigin} [origin] - The origin of the call.
* @return {Promise<IMessageConfirmation>} A promise that resolves to the message confirmation.
*/
sendCall(identifier_1, tenantId_1, protocol_1, action_1, payload_1, callbackUrl_1, correlationId_1) {
return __awaiter(this, arguments, void 0, function* (identifier, tenantId, protocol, action, payload, callbackUrl, correlationId, origin = messages_1.MessageOrigin.ChargingStationManagementSystem) {
const _correlationId = correlationId === undefined ? (0, uuid_1.v4)() : correlationId;
if (callbackUrl) {
// TODO: Handle callErrors, failure to send to charger, timeout from charger, with different responses to callback
this._cache
.set(_correlationId, callbackUrl, AbstractModule.CALLBACK_URL_CACHE_PREFIX + identifier, this._config.maxCachingSeconds)
.then()
.catch((error) => this._logger.error('Failed setting cache: ', error));
}
// TODO: Future - Compound key with tenantId
return this._cache.get(identifier, types_1.CacheNamespace.Connections).then((connection) => {
if (connection) {
const websocketConnection = JSON.parse(connection);
if (websocketConnection.protocol !== protocol) {
this._logger.error(`Failed sending call. Requested protocol: '${protocol}', connection protocol: '${websocketConnection.protocol}' for identifier: `, identifier);
return Promise.resolve({
success: false,
payload: `Requested protocol: '${protocol}', connection protocol: '${websocketConnection.protocol}' for identifier: '${identifier}'`,
});
}
return this._sender.sendRequest(request_1.RequestBuilder.buildCall(identifier, _correlationId, tenantId, action, payload, this._eventGroup, origin, protocol));
}
else {
this._logger.error('Failed sending call. No connection found for identifier: ', identifier);
return Promise.resolve({
success: false,
payload: 'No connection found for identifier: ' + identifier,
});
}
});
});
}
/**
* Sends the call result message and returns a Promise that resolves with the confirmation message.
*
* @param {string} correlationId - The correlation ID of the message.
* @param {string} identifier - The identifier of the message.
* @param {string} tenantId - The ID of the tenant.
* @param {CallAction} action - The call action.
* @param {OcppResponse} payload - The payload of the call result message.
* @param {MessageOrigin} origin - (optional) The origin of the message.
* @return {Promise<IMessageConfirmation>} A Promise that resolves with the confirmation message.
*/
sendCallResult(correlationId, identifier, tenantId, protocol, action, payload, origin = messages_1.MessageOrigin.ChargingStationManagementSystem) {
return this._sender.sendResponse(request_1.RequestBuilder.buildCallResult(identifier, correlationId, tenantId, action, payload, this._eventGroup, origin, protocol));
}
/**
* Sends the call result using the request message's fields.
* Payload will overwrite message.payload.
*
* @param {IMessage<OcppRequest>} message - The request message object.
* @param {OcppResponse} payload - The payload to send.
* @return {Promise<IMessageConfirmation>} A promise that resolves to the message confirmation.
*/
sendCallResultWithMessage(message, payload) {
message.origin = messages_1.MessageOrigin.ChargingStationManagementSystem;
return this._sender.sendResponse(message, payload);
}
/**
* Sends the call error message and returns a Promise that resolves with the confirmation message.
*
* @param {string} correlationId - The correlation ID of the message.
* @param {string} identifier - The identifier of the message.
* @param {string} tenantId - The ID of the tenant.
* @param {CallAction} action - The call action.
* @param {OcppError} payload - The payload of the call error message.
* @param {MessageOrigin} origin - (optional) The origin of the message.
* @return {Promise<IMessageConfirmation>} A Promise that resolves with the confirmation message.
*/
sendCallError(correlationId, identifier, tenantId, protocol, action, payload, origin = messages_1.MessageOrigin.ChargingStationManagementSystem) {
return this._sender.sendResponse(request_1.RequestBuilder.buildCallError(identifier, correlationId, tenantId, action, payload, this._eventGroup, origin, protocol));
}
/**
* Sends the call error using the request message's fields.
* Payload will overwrite message.payload.
*
* @param {IMessage<OcppRequest>} message - The request message object.
* @param {OcppResponse} payload - The payload to send.
* @return {Promise<IMessageConfirmation>} A promise that resolves to the message confirmation.
*/
sendCallErrorWithMessage(message, payload) {
message.origin = messages_1.MessageOrigin.ChargingStationManagementSystem;
return this._sender.sendResponse(message, payload);
}
/**
* Initializes the logger for the class.
*
* @return {Logger<ILogObj>} The initialized logger.
*/
_initLogger(baseLogger) {
return baseLogger
? baseLogger.getSubLogger({ name: this.constructor.name })
: new tslog_1.Logger({
name: this.constructor.name,
minLevel: this._config.logLevel,
hideLogPositionForProduction: this._config.env === 'production',
});
}
/**
* Initializes the handler for handling requests and responses.
*/
initHandlers() {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this._initHandler(this._requests, this._responses);
if (!result) {
throw new Error('Could not initialize module due to failure in handler initialization.');
}
this._logger.info(`Initialized in ${Date.now() - this.startTime}ms...`);
});
}
/**
* Initializes the handler for handling requests and responses.
*
* @param {CallAction[]} requests - The array of call actions for requests.
* @param {CallAction[]} responses - The array of call actions for responses.
* @return {Promise<boolean>} Returns a promise that resolves to a boolean indicating if the initialization was successful.
*/
_initHandler(requests, responses) {
return __awaiter(this, void 0, void 0, function* () {
this._handler.module = this;
let success = yield this._handler.subscribe(this._eventGroup.toString() + '_requests', requests, {
state: messages_1.MessageState.Request.toString(),
});
success =
success &&
(yield this._handler.subscribe(this._eventGroup.toString() + '_responses', responses, {
state: messages_1.MessageState.Response.toString(),
}));
return success;
});
}
}
exports.AbstractModule = AbstractModule;
AbstractModule.CALLBACK_URL_CACHE_PREFIX = 'CALLBACK_URL_';
//# sourceMappingURL=AbstractModule.js.map