Apply prettier formating
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
e7aeea18
JB
3import {
4 AuthorizeRequest,
5 OCPP16AuthorizationStatus,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionResponse,
9 StartTransactionRequest,
10 StopTransactionRequest,
11} from '../../../types/ocpp/1.6/Transaction';
12import {
13 HeartbeatRequest,
14 OCPP16RequestCommand,
15 StatusNotificationRequest,
16} from '../../../types/ocpp/1.6/Requests';
17import {
18 HeartbeatResponse,
19 OCPP16BootNotificationResponse,
20 OCPP16RegistrationStatus,
21 StatusNotificationResponse,
22} from '../../../types/ocpp/1.6/Responses';
c0560973
JB
23import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
24
73b9adec 25import type ChargingStation from '../../ChargingStation';
a4bc2942 26import { ErrorType } from '../../../types/ocpp/ErrorType';
d1888640 27import { JsonType } from '../../../types/JsonType';
c0560973 28import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
fd0c36fa 29import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
c0560973 30import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
e58068fd 31import OCPPError from '../../../exception/OCPPError';
c0560973 32import OCPPResponseService from '../OCPPResponseService';
74c6a954 33import { ResponseHandler } from '../../../types/ocpp/Responses';
c0560973 34import Utils from '../../../utils/Utils';
9f2e3130 35import logger from '../../../utils/Logger';
c0560973 36
909dcf2d
JB
37const moduleName = 'OCPP16ResponseService';
38
c0560973 39export default class OCPP16ResponseService extends OCPPResponseService {
58144adb
JB
40 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
41
9f2e3130 42 public constructor(chargingStation: ChargingStation) {
909dcf2d 43 if (new.target?.name === moduleName) {
06127450 44 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 45 }
58144adb
JB
46 super(chargingStation);
47 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
48 [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
49 [OCPP16RequestCommand.HEARTBEAT, this.handleResponseHeartbeat.bind(this)],
50 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)],
51 [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)],
52 [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)],
53 [OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)],
e7aeea18 54 [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
58144adb
JB
55 ]);
56 }
57
e7aeea18
JB
58 public async handleResponse(
59 commandName: OCPP16RequestCommand,
60 payload: JsonType | string,
61 requestPayload: JsonType
62 ): Promise<void> {
63 if (
64 this.chargingStation.isRegistered() ||
65 commandName === OCPP16RequestCommand.BOOT_NOTIFICATION
66 ) {
124f3553
JB
67 if (this.responseHandlers.has(commandName)) {
68 try {
69 await this.responseHandlers.get(commandName)(payload, requestPayload);
70 } catch (error) {
e7aeea18
JB
71 logger.error(
72 this.chargingStation.logPrefix() + ' Handle request response error: %j',
73 error
74 );
124f3553
JB
75 throw error;
76 }
77 } else {
78 // Throw exception
e7aeea18
JB
79 throw new OCPPError(
80 ErrorType.NOT_IMPLEMENTED,
81 `${commandName} is not implemented to handle request response payload ${JSON.stringify(
82 payload,
83 null,
84 2
85 )}`,
86 commandName
87 );
887fef76 88 }
c0560973 89 } else {
e7aeea18
JB
90 throw new OCPPError(
91 ErrorType.SECURITY_ERROR,
92 `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
93 payload,
94 null,
95 2
96 )} while the charging station is not registered on the central server. `,
97 commandName
98 );
c0560973
JB
99 }
100 }
101
624df199 102 private handleResponseBootNotification(payload: OCPP16BootNotificationResponse): void {
c0560973 103 if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
e7aeea18
JB
104 this.chargingStation.addConfigurationKey(
105 OCPP16StandardParametersKey.HeartBeatInterval,
106 payload.interval.toString()
107 );
108 this.chargingStation.addConfigurationKey(
109 OCPP16StandardParametersKey.HeartbeatInterval,
110 payload.interval.toString(),
111 { visible: false }
112 );
113 this.chargingStation.heartbeatSetInterval
114 ? this.chargingStation.restartHeartbeat()
115 : this.chargingStation.startHeartbeat();
672fed6e 116 }
74c6a954 117 if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
e7aeea18
JB
118 const logMsg = `${this.chargingStation.logPrefix()} Charging station in '${
119 payload.status
120 }' state on the central server`;
121 payload.status === OCPP16RegistrationStatus.REJECTED
122 ? logger.warn(logMsg)
123 : logger.info(logMsg);
c0560973 124 } else {
e7aeea18
JB
125 logger.error(
126 this.chargingStation.logPrefix() +
127 ' Charging station boot notification response received: %j with undefined registration status',
128 payload
129 );
c0560973
JB
130 }
131 }
132
e7aeea18
JB
133 private handleResponseHeartbeat(
134 payload: HeartbeatResponse,
135 requestPayload: HeartbeatRequest
136 ): void {
137 logger.debug(
138 this.chargingStation.logPrefix() +
139 ' Heartbeat response received: %j to Heartbeat request: %j',
140 payload,
141 requestPayload
142 );
58144adb
JB
143 }
144
e7aeea18
JB
145 private handleResponseAuthorize(
146 payload: OCPP16AuthorizeResponse,
147 requestPayload: AuthorizeRequest
148 ): void {
58144adb 149 let authorizeConnectorId: number;
734d790d 150 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
151 if (
152 connectorId > 0 &&
153 this.chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag ===
154 requestPayload.idTag
155 ) {
734d790d 156 authorizeConnectorId = connectorId;
58144adb
JB
157 break;
158 }
159 }
160 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
a2653482 161 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
e7aeea18
JB
162 logger.debug(
163 `${this.chargingStation.logPrefix()} IdTag ${
164 requestPayload.idTag
165 } authorized on connector ${authorizeConnectorId}`
166 );
58144adb 167 } else {
a2653482 168 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
734d790d 169 delete this.chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
e7aeea18
JB
170 logger.debug(
171 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${
172 payload.idTagInfo.status
173 } on connector ${authorizeConnectorId}`
174 );
58144adb
JB
175 }
176 }
177
e7aeea18
JB
178 private async handleResponseStartTransaction(
179 payload: OCPP16StartTransactionResponse,
180 requestPayload: StartTransactionRequest
181 ): Promise<void> {
c0560973
JB
182 const connectorId = requestPayload.connectorId;
183
184 let transactionConnectorId: number;
734d790d
JB
185 for (const id of this.chargingStation.connectors.keys()) {
186 if (id > 0 && id === connectorId) {
187 transactionConnectorId = id;
c0560973
JB
188 break;
189 }
190 }
191 if (!transactionConnectorId) {
e7aeea18
JB
192 logger.error(
193 this.chargingStation.logPrefix() +
194 ' Trying to start a transaction on a non existing connector Id ' +
195 connectorId.toString()
196 );
c0560973
JB
197 return;
198 }
e7aeea18
JB
199 if (
200 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
201 this.chargingStation.getAuthorizeRemoteTxRequests() &&
202 this.chargingStation.getLocalAuthListEnabled() &&
203 this.chargingStation.hasAuthorizedTags() &&
204 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized
205 ) {
206 logger.error(
207 this.chargingStation.logPrefix() +
208 ' Trying to start a transaction with a not local authorized idTag ' +
209 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
210 ' on connector Id ' +
211 connectorId.toString()
212 );
a2653482
JB
213 await this.resetConnectorOnStartTransactionError(connectorId);
214 return;
215 }
e7aeea18
JB
216 if (
217 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
218 this.chargingStation.getAuthorizeRemoteTxRequests() &&
219 this.chargingStation.getMayAuthorizeAtRemoteStart() &&
220 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
221 !this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized
222 ) {
223 logger.error(
224 this.chargingStation.logPrefix() +
225 ' Trying to start a transaction with a not authorized idTag ' +
226 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
227 ' on connector Id ' +
228 connectorId.toString()
229 );
a2653482
JB
230 await this.resetConnectorOnStartTransactionError(connectorId);
231 return;
232 }
e7aeea18
JB
233 if (
234 this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
235 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
236 ) {
237 logger.error(
238 this.chargingStation.logPrefix() +
239 ' Trying to start a transaction with an idTag ' +
240 requestPayload.idTag +
241 ' different from the authorize request one ' +
242 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
243 ' on connector Id ' +
244 connectorId.toString()
245 );
a2653482
JB
246 await this.resetConnectorOnStartTransactionError(connectorId);
247 return;
248 }
e7aeea18
JB
249 if (
250 this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
251 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !==
252 requestPayload.idTag
253 ) {
254 logger.error(
255 this.chargingStation.logPrefix() +
256 ' Trying to start a transaction with an idTag ' +
257 requestPayload.idTag +
258 ' different from the local authorized one ' +
259 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
260 ' on connector Id ' +
261 connectorId.toString()
262 );
a2653482 263 await this.resetConnectorOnStartTransactionError(connectorId);
163547b1
JB
264 return;
265 }
734d790d 266 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
e7aeea18
JB
267 logger.debug(
268 this.chargingStation.logPrefix() +
269 ' Trying to start a transaction on an already used connector ' +
270 connectorId.toString() +
271 ': %j',
272 this.chargingStation.getConnectorStatus(connectorId)
273 );
c0560973
JB
274 return;
275 }
e7aeea18
JB
276 if (
277 this.chargingStation.getConnectorStatus(connectorId)?.status !==
278 OCPP16ChargePointStatus.AVAILABLE &&
279 this.chargingStation.getConnectorStatus(connectorId)?.status !==
280 OCPP16ChargePointStatus.PREPARING
281 ) {
282 logger.error(
283 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
284 this.chargingStation.getConnectorStatus(connectorId)?.status
285 }`
286 );
290d006c
JB
287 return;
288 }
f3a6f63b 289 if (!Number.isInteger(payload.transactionId)) {
e7aeea18
JB
290 logger.warn(
291 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
292 payload.transactionId
293 }, converting to integer`
294 );
f3a6f63b
JB
295 payload.transactionId = Utils.convertToInt(payload.transactionId);
296 }
c0560973 297
f0c6ed89 298 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
734d790d
JB
299 this.chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
300 this.chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
301 this.chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
e7aeea18
JB
302 this.chargingStation.getConnectorStatus(
303 connectorId
304 ).transactionEnergyActiveImportRegisterValue = 0;
305 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue =
306 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
307 this.chargingStation,
308 connectorId,
309 requestPayload.meterStart
310 );
311 this.chargingStation.getBeginEndMeterValues() &&
312 (await this.chargingStation.ocppRequestService.sendTransactionBeginMeterValues(
313 connectorId,
314 payload.transactionId,
315 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue
316 ));
317 await this.chargingStation.ocppRequestService.sendStatusNotification(
318 connectorId,
319 OCPP16ChargePointStatus.CHARGING
320 );
321 this.chargingStation.getConnectorStatus(connectorId).status =
322 OCPP16ChargePointStatus.CHARGING;
323 logger.info(
324 this.chargingStation.logPrefix() +
325 ' Transaction ' +
326 payload.transactionId.toString() +
327 ' STARTED on ' +
328 this.chargingStation.stationInfo.chargingStationId +
329 '#' +
330 connectorId.toString() +
331 ' for idTag ' +
332 requestPayload.idTag
333 );
c0560973
JB
334 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
335 this.chargingStation.stationInfo.powerDivider++;
336 }
e7aeea18
JB
337 const configuredMeterValueSampleInterval = this.chargingStation.getConfigurationKey(
338 OCPP16StandardParametersKey.MeterValueSampleInterval
339 );
340 this.chargingStation.startMeterValues(
341 connectorId,
342 configuredMeterValueSampleInterval
343 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
344 : 60000
345 );
c0560973 346 } else {
e7aeea18
JB
347 logger.warn(
348 this.chargingStation.logPrefix() +
349 ' Starting transaction id ' +
350 payload.transactionId.toString() +
351 ' REJECTED with status ' +
352 payload?.idTagInfo?.status +
353 ', idTag ' +
354 requestPayload.idTag
355 );
a2653482
JB
356 await this.resetConnectorOnStartTransactionError(connectorId);
357 }
358 }
359
360 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
361 this.chargingStation.resetConnectorStatus(connectorId);
e7aeea18
JB
362 if (
363 this.chargingStation.getConnectorStatus(connectorId).status !==
364 OCPP16ChargePointStatus.AVAILABLE
365 ) {
366 await this.chargingStation.ocppRequestService.sendStatusNotification(
367 connectorId,
368 OCPP16ChargePointStatus.AVAILABLE
369 );
370 this.chargingStation.getConnectorStatus(connectorId).status =
371 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
372 }
373 }
374
e7aeea18
JB
375 private async handleResponseStopTransaction(
376 payload: OCPP16StopTransactionResponse,
377 requestPayload: StopTransactionRequest
378 ): Promise<void> {
c0560973 379 let transactionConnectorId: number;
734d790d 380 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
381 if (
382 connectorId > 0 &&
383 this.chargingStation.getConnectorStatus(connectorId)?.transactionId ===
384 requestPayload.transactionId
385 ) {
734d790d 386 transactionConnectorId = connectorId;
c0560973
JB
387 break;
388 }
389 }
390 if (!transactionConnectorId) {
e7aeea18
JB
391 logger.error(
392 this.chargingStation.logPrefix() +
393 ' Trying to stop a non existing transaction ' +
394 requestPayload.transactionId.toString()
395 );
c0560973
JB
396 return;
397 }
398 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
e7aeea18
JB
399 this.chargingStation.getBeginEndMeterValues() &&
400 !this.chargingStation.getOcppStrictCompliance() &&
401 this.chargingStation.getOutOfOrderEndMeterValues() &&
402 (await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(
403 transactionConnectorId,
404 requestPayload.transactionId,
405 OCPP16ServiceUtils.buildTransactionEndMeterValue(
406 this.chargingStation,
407 transactionConnectorId,
408 requestPayload.meterStop
409 )
410 ));
411 if (
412 !this.chargingStation.isChargingStationAvailable() ||
413 !this.chargingStation.isConnectorAvailable(transactionConnectorId)
414 ) {
415 await this.chargingStation.ocppRequestService.sendStatusNotification(
416 transactionConnectorId,
417 OCPP16ChargePointStatus.UNAVAILABLE
418 );
419 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
420 OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 421 } else {
e7aeea18
JB
422 await this.chargingStation.ocppRequestService.sendStatusNotification(
423 transactionConnectorId,
424 OCPP16ChargePointStatus.AVAILABLE
425 );
426 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
427 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
428 }
429 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
430 this.chargingStation.stationInfo.powerDivider--;
431 }
e7aeea18
JB
432 logger.info(
433 this.chargingStation.logPrefix() +
434 ' Transaction ' +
435 requestPayload.transactionId.toString() +
436 ' STOPPED on ' +
437 this.chargingStation.stationInfo.chargingStationId +
438 '#' +
439 transactionConnectorId.toString()
440 );
a2653482 441 this.chargingStation.resetConnectorStatus(transactionConnectorId);
c0560973 442 } else {
e7aeea18
JB
443 logger.warn(
444 this.chargingStation.logPrefix() +
445 ' Stopping transaction id ' +
446 requestPayload.transactionId.toString() +
447 ' REJECTED with status ' +
448 payload.idTagInfo?.status
449 );
c0560973
JB
450 }
451 }
452
e7aeea18
JB
453 private handleResponseStatusNotification(
454 payload: StatusNotificationRequest,
455 requestPayload: StatusNotificationResponse
456 ): void {
457 logger.debug(
458 this.chargingStation.logPrefix() +
459 ' Status notification response received: %j to StatusNotification request: %j',
460 payload,
461 requestPayload
462 );
c0560973
JB
463 }
464
e7aeea18
JB
465 private handleResponseMeterValues(
466 payload: MeterValuesRequest,
467 requestPayload: MeterValuesResponse
468 ): void {
469 logger.debug(
470 this.chargingStation.logPrefix() +
471 ' MeterValues response received: %j to MeterValues request: %j',
472 payload,
473 requestPayload
474 );
c0560973 475 }
c0560973 476}