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