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