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