UNPKG

@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.

302 lines 14.7 kB
// 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()); }); }; import 'reflect-metadata'; import { Logger } from 'tslog'; import { v4 as uuidv4 } from 'uuid'; import { AS_HANDLER_METADATA } from '.'; import { ErrorCode, OcppError } from '../../ocpp/rpc/message'; import { RequestBuilder } from '../../util/request'; import { CacheNamespace } from '../cache/types'; import { MessageOrigin, MessageState, } from '../messages'; export 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 === 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(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 OcppError(message.context.correlationId, 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 === MessageState.Request) { // CallErrors are only emitted for Calls this._logger.error('Sending CallError to ChargingStation...'); message.origin = MessageOrigin.ChargingStationManagementSystem; if (error instanceof OcppError) { yield this._sender.sendResponse(message, error); } else if (error instanceof Error) { yield this._sender.sendResponse(message, new OcppError(message.context.correlationId, 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 = MessageOrigin.ChargingStationManagementSystem) { const _correlationId = correlationId === undefined ? uuidv4() : 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, 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(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 = MessageOrigin.ChargingStationManagementSystem) { return this._sender.sendResponse(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 = 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 = MessageOrigin.ChargingStationManagementSystem) { return this._sender.sendResponse(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 = 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 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: MessageState.Request.toString(), }); success = success && (yield this._handler.subscribe(this._eventGroup.toString() + '_responses', responses, { state: MessageState.Response.toString(), })); return success; }); } } AbstractModule.CALLBACK_URL_CACHE_PREFIX = 'CALLBACK_URL_'; //# sourceMappingURL=AbstractModule.js.map