fix: brown paper bag issue at referencing the same literal object instance
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
953d6b02
JB
2
3import type { JSONSchemaType } from 'ajv';
4
2896e06d 5import { type ChargingStation, ChargingStationConfigurationUtils } from '../../../charging-station';
268a74bb 6import { OCPPError } from '../../../exception';
b3fc3ff5 7import {
268a74bb
JB
8 ErrorType,
9 type JsonObject,
10 type JsonType,
268a74bb
JB
11 type OCPP20BootNotificationResponse,
12 type OCPP20ClearCacheResponse,
13 type OCPP20HeartbeatResponse,
b3fc3ff5 14 OCPP20IncomingRequestCommand,
857d8dd9 15 OCPP20OptionalVariableName,
b3fc3ff5 16 OCPP20RequestCommand,
268a74bb
JB
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
20 type ResponseHandler,
21} from '../../../types';
59b6ed8d 22import { Constants, logger } from '../../../utils';
2896e06d 23import { OCPP20ServiceUtils, OCPPResponseService } from '../internal';
953d6b02
JB
24
25const moduleName = 'OCPP20ResponseService';
26
268a74bb 27export class OCPP20ResponseService extends OCPPResponseService {
b3fc3ff5
JB
28 public jsonIncomingRequestResponseSchemas: Map<
29 OCPP20IncomingRequestCommand,
30 JSONSchemaType<JsonObject>
31 >;
32
953d6b02
JB
33 private responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>;
34 private jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>;
35
36 public constructor() {
b768993d
JB
37 // if (new.target?.name === moduleName) {
38 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
39 // }
d270cc87
JB
40 super(OCPPVersion.VERSION_20);
41 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
42 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
81533a20 43 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
6e939d9e 44 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
d270cc87
JB
45 ]);
46 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
47 [
48 OCPP20RequestCommand.BOOT_NOTIFICATION,
130783a7 49 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
1b271a54
JB
50 '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
51 moduleName,
52 'constructor'
e9a4164c 53 ),
d270cc87 54 ],
81533a20
JB
55 [
56 OCPP20RequestCommand.HEARTBEAT,
130783a7 57 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
1b271a54
JB
58 '../../../assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
59 moduleName,
60 'constructor'
e9a4164c 61 ),
81533a20 62 ],
6e939d9e
JB
63 [
64 OCPP20RequestCommand.STATUS_NOTIFICATION,
130783a7 65 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
1b271a54
JB
66 '../../../assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
67 moduleName,
68 'constructor'
e9a4164c 69 ),
6e939d9e 70 ],
d270cc87 71 ]);
02887891
JB
72 this.jsonIncomingRequestResponseSchemas = new Map([
73 [
74 OCPP20IncomingRequestCommand.CLEAR_CACHE,
130783a7 75 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
1b271a54
JB
76 '../../../assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
77 moduleName,
78 'constructor'
e9a4164c 79 ),
02887891
JB
80 ],
81 ]);
953d6b02
JB
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 (
d270cc87
JB
92 chargingStation.isRegistered() === true ||
93 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
953d6b02
JB
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
439fc71b 129 )} while the charging station is not registered on the central server.`,
953d6b02
JB
130 commandName,
131 payload
132 );
133 }
134 }
135
136 private validatePayload(
137 chargingStation: ChargingStation,
138 commandName: OCPP20RequestCommand,
139 payload: JsonType
140 ): boolean {
45988780 141 if (this.jsonSchemas.has(commandName) === true) {
953d6b02
JB
142 return this.validateResponsePayload(
143 chargingStation,
144 commandName,
145 this.jsonSchemas.get(commandName),
146 payload
147 );
148 }
149 logger.warn(
b3fc3ff5 150 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
953d6b02
JB
151 );
152 return false;
153 }
d270cc87
JB
154
155 private handleResponseBootNotification(
156 chargingStation: ChargingStation,
157 payload: OCPP20BootNotificationResponse
158 ): void {
159 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
268a74bb
JB
160 ChargingStationConfigurationUtils.addConfigurationKey(
161 chargingStation,
857d8dd9 162 OCPP20OptionalVariableName.HeartbeatInterval,
268a74bb 163 payload.interval.toString(),
abe9e9dd 164 {},
268a74bb
JB
165 { overwrite: true, save: true }
166 );
d270cc87
JB
167 chargingStation.heartbeatSetInterval
168 ? chargingStation.restartHeartbeat()
169 : chargingStation.startHeartbeat();
170 }
171 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
172 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
173 payload.status
174 }' state on the central server`;
175 payload.status === RegistrationStatusEnumType.REJECTED
176 ? logger.warn(logMsg)
177 : logger.info(logMsg);
178 } else {
179 logger.error(
44eb6026 180 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
d270cc87
JB
181 payload
182 );
183 }
184 }
953d6b02 185}