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