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