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