type OCPP16SampledValue,
} from '../../../types/ocpp/1.6/MeterValues';
import {
- OCPP16IncomingRequestCommand,
+ type OCPP16IncomingRequestCommand,
OCPP16RequestCommand,
} from '../../../types/ocpp/1.6/Requests';
import { ErrorType } from '../../../types/ocpp/ErrorType';
--- /dev/null
+// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+
+import type { JSONSchemaType } from 'ajv';
+
+import OCPPError from '../../../exception/OCPPError';
+import type { JsonObject, JsonType } from '../../../types/JsonType';
+import type { OCPP20IncomingRequestCommand } from '../../../types/ocpp/2.0/Requests';
+import { ErrorType } from '../../../types/ocpp/ErrorType';
+import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
+import logger from '../../../utils/Logger';
+import type ChargingStation from '../../ChargingStation';
+import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
+import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
+
+const moduleName = 'OCPP20IncomingRequestService';
+
+export default class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
+ private incomingRequestHandlers: Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>;
+ private jsonSchemas: Map<OCPP20IncomingRequestCommand, JSONSchemaType<JsonObject>>;
+
+ public constructor() {
+ if (new.target?.name === moduleName) {
+ throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
+ }
+ super();
+ this.incomingRequestHandlers = new Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>();
+ this.jsonSchemas = new Map<OCPP20IncomingRequestCommand, JSONSchemaType<JsonObject>>();
+ this.validatePayload.bind(this);
+ }
+
+ public async incomingRequestHandler(
+ chargingStation: ChargingStation,
+ messageId: string,
+ commandName: OCPP20IncomingRequestCommand,
+ commandPayload: JsonType
+ ): Promise<void> {
+ let response: JsonType;
+ if (
+ chargingStation.getOcppStrictCompliance() === true &&
+ chargingStation.isInPendingState() === true /* &&
+ (commandName === OCPP20IncomingRequestCommand.REMOTE_START_TRANSACTION ||
+ commandName === OCPP20IncomingRequestCommand.REMOTE_STOP_TRANSACTION ) */
+ ) {
+ throw new OCPPError(
+ ErrorType.SECURITY_ERROR,
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
+ commandPayload,
+ null,
+ 2
+ )} while the charging station is in pending state on the central server`,
+ commandName,
+ commandPayload
+ );
+ }
+ if (
+ chargingStation.isRegistered() === true ||
+ (chargingStation.getOcppStrictCompliance() === false &&
+ chargingStation.isInUnknownState() === true)
+ ) {
+ if (
+ this.incomingRequestHandlers.has(commandName) === true &&
+ OCPP20ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
+ ) {
+ try {
+ this.validatePayload(chargingStation, commandName, commandPayload);
+ // Call the method to build the response
+ response = await this.incomingRequestHandlers.get(commandName)(
+ chargingStation,
+ commandPayload
+ );
+ } catch (error) {
+ // Log
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
+ error
+ );
+ throw error;
+ }
+ } else {
+ // Throw exception
+ throw new OCPPError(
+ ErrorType.NOT_IMPLEMENTED,
+ `${commandName} is not implemented to handle request PDU ${JSON.stringify(
+ commandPayload,
+ null,
+ 2
+ )}`,
+ commandName,
+ commandPayload
+ );
+ }
+ } else {
+ throw new OCPPError(
+ ErrorType.SECURITY_ERROR,
+ `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
+ commandPayload,
+ null,
+ 2
+ )} while the charging station is not registered on the central server.`,
+ commandName,
+ commandPayload
+ );
+ }
+ // Send the built response
+ await chargingStation.ocppRequestService.sendResponse(
+ chargingStation,
+ messageId,
+ response,
+ commandName
+ );
+ }
+
+ private validatePayload(
+ chargingStation: ChargingStation,
+ commandName: OCPP20IncomingRequestCommand,
+ commandPayload: JsonType
+ ): boolean {
+ if (this.jsonSchemas.has(commandName)) {
+ return this.validateIncomingRequestPayload(
+ chargingStation,
+ commandName,
+ this.jsonSchemas.get(commandName),
+ commandPayload
+ );
+ }
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
+ );
+ return false;
+ }
+}
--- /dev/null
+// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+
+import type { JSONSchemaType } from 'ajv';
+
+import OCPPError from '../../../exception/OCPPError';
+import type { JsonObject, JsonType } from '../../../types/JsonType';
+import type { OCPP20RequestCommand } from '../../../types/ocpp/2.0/Requests';
+import { ErrorType } from '../../../types/ocpp/ErrorType';
+import type { RequestParams } from '../../../types/ocpp/Requests';
+import logger from '../../../utils/Logger';
+import Utils from '../../../utils/Utils';
+import type ChargingStation from '../../ChargingStation';
+import OCPPRequestService from '../OCPPRequestService';
+import type OCPPResponseService from '../OCPPResponseService';
+import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
+
+const moduleName = 'OCPP20RequestService';
+
+export default class OCPP20RequestService extends OCPPRequestService {
+ private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
+
+ public constructor(ocppResponseService: OCPPResponseService) {
+ if (new.target?.name === moduleName) {
+ throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
+ }
+ super(ocppResponseService);
+ this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>();
+ this.buildRequestPayload.bind(this);
+ this.validatePayload.bind(this);
+ }
+
+ public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: OCPP20RequestCommand,
+ commandParams?: JsonType,
+ params?: RequestParams
+ ): Promise<ResponseType> {
+ if (OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true) {
+ const requestPayload = this.buildRequestPayload<RequestType>(
+ chargingStation,
+ commandName,
+ commandParams
+ );
+ this.validatePayload(chargingStation, commandName, requestPayload);
+ return (await this.sendMessage(
+ chargingStation,
+ Utils.generateUUID(),
+ requestPayload,
+ commandName,
+ params
+ )) as unknown as ResponseType;
+ }
+ // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+ throw new OCPPError(
+ ErrorType.NOT_SUPPORTED,
+ `Unsupported OCPP command '${commandName}'`,
+ commandName,
+ commandParams
+ );
+ }
+
+ private buildRequestPayload<Request extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: OCPP20RequestCommand,
+ commandParams?: JsonType
+ ): Request {
+ commandParams = commandParams as JsonObject;
+ switch (commandName) {
+ default:
+ // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+ throw new OCPPError(
+ ErrorType.NOT_SUPPORTED,
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ `Unsupported OCPP command '${commandName}'`,
+ commandName,
+ commandParams
+ );
+ }
+ }
+
+ private validatePayload<Request extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: OCPP20RequestCommand,
+ requestPayload: Request
+ ): boolean {
+ if (this.jsonSchemas.has(commandName)) {
+ return this.validateRequestPayload(
+ chargingStation,
+ commandName,
+ this.jsonSchemas.get(commandName),
+ requestPayload
+ );
+ }
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
+ );
+ return false;
+ }
+}
--- /dev/null
+// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+
+import type { JSONSchemaType } from 'ajv';
+
+import OCPPError from '../../../exception/OCPPError';
+import type { JsonObject, JsonType } from '../../../types/JsonType';
+import type { OCPP20RequestCommand } from '../../../types/ocpp/2.0/Requests';
+import { ErrorType } from '../../../types/ocpp/ErrorType';
+import type { ResponseHandler } from '../../../types/ocpp/Responses';
+import logger from '../../../utils/Logger';
+import type ChargingStation from '../../ChargingStation';
+import OCPPResponseService from '../OCPPResponseService';
+import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
+
+const moduleName = 'OCPP20ResponseService';
+
+export default class OCPP20ResponseService extends OCPPResponseService {
+ private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>;
+ private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
+
+ public constructor() {
+ if (new.target?.name === moduleName) {
+ throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
+ }
+ super();
+ this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>();
+ this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>();
+ this.validatePayload.bind(this);
+ }
+
+ public async responseHandler(
+ chargingStation: ChargingStation,
+ commandName: OCPP20RequestCommand,
+ payload: JsonType,
+ requestPayload: JsonType
+ ): Promise<void> {
+ if (
+ chargingStation.isRegistered() === true /* ||
+ commandName === OCPP20RequestCommand.BOOT_NOTIFICATION */
+ ) {
+ if (
+ this.responseHandlers.has(commandName) === true &&
+ OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
+ ) {
+ try {
+ this.validatePayload(chargingStation, commandName, payload);
+ await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
+ } catch (error) {
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
+ error
+ );
+ throw error;
+ }
+ } else {
+ // Throw exception
+ throw new OCPPError(
+ ErrorType.NOT_IMPLEMENTED,
+ `${commandName} is not implemented to handle response PDU ${JSON.stringify(
+ payload,
+ null,
+ 2
+ )}`,
+ commandName,
+ payload
+ );
+ }
+ } else {
+ throw new OCPPError(
+ ErrorType.SECURITY_ERROR,
+ `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
+ payload,
+ null,
+ 2
+ )} while the charging station is not registered on the central server. `,
+ commandName,
+ payload
+ );
+ }
+ }
+
+ private validatePayload(
+ chargingStation: ChargingStation,
+ commandName: OCPP20RequestCommand,
+ payload: JsonType
+ ): boolean {
+ if (this.jsonSchemas.has(commandName)) {
+ return this.validateResponsePayload(
+ chargingStation,
+ commandName,
+ this.jsonSchemas.get(commandName),
+ payload
+ );
+ }
+ logger.warn(
+ `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
+ );
+ return false;
+ }
+}
--- /dev/null
+// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+
+import { OCPPServiceUtils } from '../OCPPServiceUtils';
+
+export class OCPP20ServiceUtils extends OCPPServiceUtils {}
import type { CircularArray } from '../utils/CircularArray';
+import type { IncomingRequestCommand, RequestCommand } from './ocpp/Requests';
import type { WorkerData } from './Worker';
export type TimeSeries = {
DATA_TRANSFER = 'DataTransfer',
}
+export enum OCPP16IncomingRequestCommand {
+ RESET = 'Reset',
+ CLEAR_CACHE = 'ClearCache',
+ CHANGE_AVAILABILITY = 'ChangeAvailability',
+ UNLOCK_CONNECTOR = 'UnlockConnector',
+ GET_CONFIGURATION = 'GetConfiguration',
+ CHANGE_CONFIGURATION = 'ChangeConfiguration',
+ SET_CHARGING_PROFILE = 'SetChargingProfile',
+ CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
+ REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
+ REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
+ GET_DIAGNOSTICS = 'GetDiagnostics',
+ TRIGGER_MESSAGE = 'TriggerMessage',
+ DATA_TRANSFER = 'DataTransfer',
+ UPDATE_FIRMWARE = 'UpdateFirmware',
+}
+
export type OCPP16HeartbeatRequest = EmptyObject;
export interface OCPP16BootNotificationRequest extends JsonObject {
vendorErrorCode?: string;
}
-export enum OCPP16IncomingRequestCommand {
- RESET = 'Reset',
- CLEAR_CACHE = 'ClearCache',
- CHANGE_AVAILABILITY = 'ChangeAvailability',
- UNLOCK_CONNECTOR = 'UnlockConnector',
- GET_CONFIGURATION = 'GetConfiguration',
- CHANGE_CONFIGURATION = 'ChangeConfiguration',
- SET_CHARGING_PROFILE = 'SetChargingProfile',
- CLEAR_CHARGING_PROFILE = 'ClearChargingProfile',
- REMOTE_START_TRANSACTION = 'RemoteStartTransaction',
- REMOTE_STOP_TRANSACTION = 'RemoteStopTransaction',
- GET_DIAGNOSTICS = 'GetDiagnostics',
- TRIGGER_MESSAGE = 'TriggerMessage',
- DATA_TRANSFER = 'DataTransfer',
- UPDATE_FIRMWARE = 'UpdateFirmware',
-}
-
export type OCPP16ClearCacheRequest = EmptyObject;
export interface ChangeConfigurationRequest extends JsonObject {
--- /dev/null
+export enum OCPP20RequestCommand {}
+
+export enum OCPP20IncomingRequestCommand {}
import type { OCPP16MeterValuesRequest } from './1.6/MeterValues';
import {
OCPP16AvailabilityType,
- OCPP16BootNotificationRequest,
- OCPP16DataTransferRequest,
- OCPP16HeartbeatRequest,
+ type OCPP16BootNotificationRequest,
+ type OCPP16DataTransferRequest,
+ type OCPP16HeartbeatRequest,
OCPP16IncomingRequestCommand,
OCPP16MessageTrigger,
OCPP16RequestCommand,
- OCPP16StatusNotificationRequest,
+ type OCPP16StatusNotificationRequest,
} from './1.6/Requests';
+import { OCPP20IncomingRequestCommand, OCPP20RequestCommand } from './2.0/Requests';
import type { MessageType } from './MessageType';
export type RequestCommand = OCPP16RequestCommand;
export const RequestCommand = {
...OCPP16RequestCommand,
+ ...OCPP20RequestCommand,
};
export type OutgoingRequest = [MessageType.CALL_MESSAGE, string, RequestCommand, JsonType];
export const IncomingRequestCommand = {
...OCPP16IncomingRequestCommand,
+ ...OCPP20IncomingRequestCommand,
};
export type IncomingRequest = [MessageType.CALL_MESSAGE, string, IncomingRequestCommand, JsonType];