Add BootNotification and ClearCache OCPP 2.0.1 commands support
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import fs from 'fs';
4 import path from 'path';
5 import { fileURLToPath } from 'url';
6
7 import type { JSONSchemaType } from 'ajv';
8
9 import OCPPError from '../../../exception/OCPPError';
10 import type { JsonObject, JsonType } from '../../../types/JsonType';
11 import { OCPP20RequestCommand } from '../../../types/ocpp/2.0/Requests';
12 import type { OCPP20BootNotificationResponse } from '../../../types/ocpp/2.0/Responses';
13 import { ErrorType } from '../../../types/ocpp/ErrorType';
14 import { OCPPVersion } from '../../../types/ocpp/OCPPVersion';
15 import { RegistrationStatusEnumType, ResponseHandler } from '../../../types/ocpp/Responses';
16 import logger from '../../../utils/Logger';
17 import type ChargingStation from '../../ChargingStation';
18 import OCPPResponseService from '../OCPPResponseService';
19 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
20
21 const moduleName = 'OCPP20ResponseService';
22
23 export default class OCPP20ResponseService extends OCPPResponseService {
24 private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>;
25 private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
26
27 public constructor() {
28 if (new.target?.name === moduleName) {
29 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
30 }
31 super(OCPPVersion.VERSION_20);
32 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
33 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
34 ]);
35 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
36 [
37 OCPP20RequestCommand.BOOT_NOTIFICATION,
38 JSON.parse(
39 fs.readFileSync(
40 path.resolve(
41 path.dirname(fileURLToPath(import.meta.url)),
42 '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json'
43 ),
44 'utf8'
45 )
46 ) as JSONSchemaType<OCPP20BootNotificationResponse>,
47 ],
48 ]);
49 this.validatePayload.bind(this);
50 }
51
52 public async responseHandler(
53 chargingStation: ChargingStation,
54 commandName: OCPP20RequestCommand,
55 payload: JsonType,
56 requestPayload: JsonType
57 ): Promise<void> {
58 if (
59 chargingStation.isRegistered() === true ||
60 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
61 ) {
62 if (
63 this.responseHandlers.has(commandName) === true &&
64 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
65 ) {
66 try {
67 this.validatePayload(chargingStation, commandName, payload);
68 await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
69 } catch (error) {
70 logger.error(
71 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
72 error
73 );
74 throw error;
75 }
76 } else {
77 // Throw exception
78 throw new OCPPError(
79 ErrorType.NOT_IMPLEMENTED,
80 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
81 payload,
82 null,
83 2
84 )}`,
85 commandName,
86 payload
87 );
88 }
89 } else {
90 throw new OCPPError(
91 ErrorType.SECURITY_ERROR,
92 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
93 payload,
94 null,
95 2
96 )} while the charging station is not registered on the central server.`,
97 commandName,
98 payload
99 );
100 }
101 }
102
103 private validatePayload(
104 chargingStation: ChargingStation,
105 commandName: OCPP20RequestCommand,
106 payload: JsonType
107 ): boolean {
108 if (this.jsonSchemas.has(commandName)) {
109 return this.validateResponsePayload(
110 chargingStation,
111 commandName,
112 this.jsonSchemas.get(commandName),
113 payload
114 );
115 }
116 logger.warn(
117 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
118 );
119 return false;
120 }
121
122 private handleResponseBootNotification(
123 chargingStation: ChargingStation,
124 payload: OCPP20BootNotificationResponse
125 ): void {
126 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
127 // ChargingStationConfigurationUtils.addConfigurationKey(
128 // chargingStation,
129 // OCPP16StandardParametersKey.HeartbeatInterval,
130 // payload.interval.toString(),
131 // {},
132 // { overwrite: true, save: true }
133 // );
134 // ChargingStationConfigurationUtils.addConfigurationKey(
135 // chargingStation,
136 // OCPP16StandardParametersKey.HeartBeatInterval,
137 // payload.interval.toString(),
138 // { visible: false },
139 // { overwrite: true, save: true }
140 // );
141 chargingStation.heartbeatSetInterval
142 ? chargingStation.restartHeartbeat()
143 : chargingStation.startHeartbeat();
144 }
145 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
146 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
147 payload.status
148 }' state on the central server`;
149 payload.status === RegistrationStatusEnumType.REJECTED
150 ? logger.warn(logMsg)
151 : logger.info(logMsg);
152 } else {
153 logger.error(
154 chargingStation.logPrefix() +
155 ' Charging station boot notification response received: %j with undefined registration status',
156 payload
157 );
158 }
159 }
160 }