// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import { JSONSchemaType } from 'ajv';
+
import OCPPError from '../../../exception/OCPPError';
import { JsonType } from '../../../types/JsonType';
import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
} from '../../../types/ocpp/1.6/Requests';
import {
OCPP16BootNotificationResponse,
+ OCPP16HeartbeatResponse,
OCPP16RegistrationStatus,
OCPP16StatusNotificationResponse,
} from '../../../types/ocpp/1.6/Responses';
export default class OCPP16ResponseService extends OCPPResponseService {
private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
+ private bootNotificationResponseJsonSchema: JSONSchemaType<OCPP16BootNotificationResponse>;
+ private heartbeatResponseJsonSchema: JSONSchemaType<OCPP16HeartbeatResponse>;
+ private authorizeResponseJsonSchema: JSONSchemaType<OCPP16AuthorizeResponse>;
+ private startTransactionResponseJsonSchema: JSONSchemaType<OCPP16StartTransactionResponse>;
+ private stopTransactionResponseJsonSchema: JSONSchemaType<OCPP16StopTransactionResponse>;
+ private statusNotificationResponseJsonSchema: JSONSchemaType<OCPP16StatusNotificationResponse>;
+ private meterValuesResponseJsonSchema: JSONSchemaType<OCPP16MeterValuesResponse>;
public constructor() {
if (new.target?.name === moduleName) {
[OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)],
[OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
]);
+ this.bootNotificationResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16BootNotificationResponse>;
+ this.heartbeatResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16HeartbeatResponse>;
+ this.authorizeResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16AuthorizeResponse>;
+ this.startTransactionResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16StartTransactionResponse>;
+ this.stopTransactionResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16StopTransactionResponse>;
+ this.statusNotificationResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16StatusNotificationResponse>;
+ this.meterValuesResponseJsonSchema = JSON.parse(
+ fs.readFileSync(
+ path.resolve(
+ path.dirname(fileURLToPath(import.meta.url)),
+ '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json'
+ ),
+ 'utf8'
+ )
+ ) as JSONSchemaType<OCPP16MeterValuesResponse>;
}
public async responseHandler(
chargingStation: ChargingStation,
payload: OCPP16BootNotificationResponse
): void {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.BOOT_NOTIFICATION,
+ this.bootNotificationResponseJsonSchema,
+ payload
+ );
if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
ChargingStationConfigurationUtils.addConfigurationKey(
chargingStation,
}
}
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- private handleResponseHeartbeat(): void {}
+ private handleResponseHeartbeat(
+ chargingStation: ChargingStation,
+ payload: OCPP16HeartbeatResponse
+ ): void {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.HEARTBEAT,
+ this.heartbeatResponseJsonSchema,
+ payload
+ );
+ }
private handleResponseAuthorize(
chargingStation: ChargingStation,
payload: OCPP16AuthorizeResponse,
requestPayload: OCPP16AuthorizeRequest
): void {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.AUTHORIZE,
+ this.authorizeResponseJsonSchema,
+ payload
+ );
let authorizeConnectorId: number;
for (const connectorId of chargingStation.connectors.keys()) {
if (
payload: OCPP16StartTransactionResponse,
requestPayload: OCPP16StartTransactionRequest
): Promise<void> {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.START_TRANSACTION,
+ this.startTransactionResponseJsonSchema,
+ payload
+ );
const connectorId = requestPayload.connectorId;
let transactionConnectorId: number;
payload: OCPP16StopTransactionResponse,
requestPayload: OCPP16StopTransactionRequest
): Promise<void> {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ this.stopTransactionResponseJsonSchema,
+ payload
+ );
const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
requestPayload.transactionId
);
}
}
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- private handleResponseStatusNotification(): void {}
+ private handleResponseStatusNotification(
+ chargingStation: ChargingStation,
+ payload: OCPP16StatusNotificationResponse
+ ): void {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ this.statusNotificationResponseJsonSchema,
+ payload
+ );
+ }
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- private handleResponseMeterValues(): void {}
+ private handleResponseMeterValues(
+ chargingStation: ChargingStation,
+ payload: OCPP16MeterValuesResponse
+ ): void {
+ this.validateResponsePayload(
+ chargingStation,
+ OCPP16RequestCommand.METER_VALUES,
+ this.meterValuesResponseJsonSchema,
+ payload
+ );
+ }
}
+import { JSONSchemaType } from 'ajv';
+import Ajv from 'ajv-draft-04';
+import ajvFormats from 'ajv-formats';
+
+import OCPPError from '../../exception/OCPPError';
import { JsonType } from '../../types/JsonType';
+import { ErrorType } from '../../types/ocpp/ErrorType';
import { RequestCommand } from '../../types/ocpp/Requests';
+import logger from '../../utils/Logger';
import type ChargingStation from '../ChargingStation';
const moduleName = 'OCPPResponseService';
export default abstract class OCPPResponseService {
private static instance: OCPPResponseService | null = null;
+ private ajv: Ajv;
protected constructor() {
- // This is intentional
+ this.ajv = new Ajv();
+ ajvFormats(this.ajv);
}
public static getInstance<T extends OCPPResponseService>(this: new () => T): T {
return OCPPResponseService.instance as T;
}
+ protected validateResponsePayload<T extends JsonType>(
+ chargingStation: ChargingStation,
+ commandName: RequestCommand,
+ schema: JSONSchemaType<T>,
+ payload: T
+ ): boolean {
+ if (!chargingStation.getPayloadSchemaValidation()) {
+ return true;
+ }
+ const validate = this.ajv.compile(schema);
+ if (validate(payload)) {
+ return true;
+ }
+ logger.error(
+ `${chargingStation.logPrefix()} ${moduleName}.validateResponsePayload: Response PDU is invalid: %j`,
+ validate.errors
+ );
+ throw new OCPPError(
+ ErrorType.FORMATION_VIOLATION,
+ 'Response PDU is invalid',
+ commandName,
+ JSON.stringify(validate.errors, null, 2)
+ );
+ }
+
public abstract responseHandler(
chargingStation: ChargingStation,
commandName: RequestCommand,