4866b5c5c241856a87db28f54851c6a6cfc9e507
[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 type { JSONSchemaType } from 'ajv';
4
5 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
6 import { OCPPError } from '../../../exception';
7 import {
8 ErrorType,
9 type JsonObject,
10 type JsonType,
11 type OCPP20BootNotificationResponse,
12 type OCPP20ClearCacheResponse,
13 type OCPP20HeartbeatResponse,
14 OCPP20IncomingRequestCommand,
15 OCPP20OptionalVariableName,
16 OCPP20RequestCommand,
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
20 type ResponseHandler,
21 } from '../../../types';
22 import { logger } from '../../../utils/Logger';
23 import type { ChargingStation } from '../../ChargingStation';
24 import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
25 import { OCPPResponseService } from '../OCPPResponseService';
26
27 const moduleName = 'OCPP20ResponseService';
28
29 export class OCPP20ResponseService extends OCPPResponseService {
30 public jsonIncomingRequestResponseSchemas: Map<
31 OCPP20IncomingRequestCommand,
32 JSONSchemaType<JsonObject>
33 >;
34
35 private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>;
36 private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
37
38 public constructor() {
39 if (new.target?.name === moduleName) {
40 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
41 }
42 super(OCPPVersion.VERSION_20);
43 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
44 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
45 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
46 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
47 ]);
48 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
49 [
50 OCPP20RequestCommand.BOOT_NOTIFICATION,
51 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
52 '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
53 moduleName,
54 'constructor'
55 ),
56 ],
57 [
58 OCPP20RequestCommand.HEARTBEAT,
59 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
60 '../../../assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
61 moduleName,
62 'constructor'
63 ),
64 ],
65 [
66 OCPP20RequestCommand.STATUS_NOTIFICATION,
67 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
68 '../../../assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
69 moduleName,
70 'constructor'
71 ),
72 ],
73 ]);
74 this.jsonIncomingRequestResponseSchemas = new Map([
75 [
76 OCPP20IncomingRequestCommand.CLEAR_CACHE,
77 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
78 '../../../assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
79 moduleName,
80 'constructor'
81 ),
82 ],
83 ]);
84 this.validatePayload.bind(this);
85 }
86
87 public async responseHandler(
88 chargingStation: ChargingStation,
89 commandName: OCPP20RequestCommand,
90 payload: JsonType,
91 requestPayload: JsonType
92 ): Promise<void> {
93 if (
94 chargingStation.isRegistered() === true ||
95 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
96 ) {
97 if (
98 this.responseHandlers.has(commandName) === true &&
99 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
100 ) {
101 try {
102 this.validatePayload(chargingStation, commandName, payload);
103 await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
104 } catch (error) {
105 logger.error(
106 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
107 error
108 );
109 throw error;
110 }
111 } else {
112 // Throw exception
113 throw new OCPPError(
114 ErrorType.NOT_IMPLEMENTED,
115 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
116 payload,
117 null,
118 2
119 )}`,
120 commandName,
121 payload
122 );
123 }
124 } else {
125 throw new OCPPError(
126 ErrorType.SECURITY_ERROR,
127 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
128 payload,
129 null,
130 2
131 )} while the charging station is not registered on the central server.`,
132 commandName,
133 payload
134 );
135 }
136 }
137
138 private validatePayload(
139 chargingStation: ChargingStation,
140 commandName: OCPP20RequestCommand,
141 payload: JsonType
142 ): boolean {
143 if (this.jsonSchemas.has(commandName) === true) {
144 return this.validateResponsePayload(
145 chargingStation,
146 commandName,
147 this.jsonSchemas.get(commandName),
148 payload
149 );
150 }
151 logger.warn(
152 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
153 );
154 return false;
155 }
156
157 private handleResponseBootNotification(
158 chargingStation: ChargingStation,
159 payload: OCPP20BootNotificationResponse
160 ): void {
161 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
162 ChargingStationConfigurationUtils.addConfigurationKey(
163 chargingStation,
164 OCPP20OptionalVariableName.HeartbeatInterval,
165 payload.interval.toString(),
166 {},
167 { overwrite: true, save: true }
168 );
169 chargingStation.heartbeatSetInterval
170 ? chargingStation.restartHeartbeat()
171 : chargingStation.startHeartbeat();
172 }
173 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
174 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
175 payload.status
176 }' state on the central server`;
177 payload.status === RegistrationStatusEnumType.REJECTED
178 ? logger.warn(logMsg)
179 : logger.info(logMsg);
180 } else {
181 logger.error(
182 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
183 payload
184 );
185 }
186 }
187 }