Add isEmptyString() helper and use it
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import {
4 AuthorizeRequest,
5 OCPP16AuthorizationStatus,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionResponse,
9 StartTransactionRequest,
10 StopTransactionRequest,
11 } from '../../../types/ocpp/1.6/Transaction';
12 import {
13 HeartbeatRequest,
14 OCPP16RequestCommand,
15 StatusNotificationRequest,
16 } from '../../../types/ocpp/1.6/Requests';
17 import {
18 HeartbeatResponse,
19 OCPP16BootNotificationResponse,
20 OCPP16RegistrationStatus,
21 StatusNotificationResponse,
22 } from '../../../types/ocpp/1.6/Responses';
23 import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
24
25 import type ChargingStation from '../../ChargingStation';
26 import { ErrorType } from '../../../types/ocpp/ErrorType';
27 import { JsonType } from '../../../types/JsonType';
28 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
29 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
30 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
31 import OCPPError from '../../../exception/OCPPError';
32 import OCPPResponseService from '../OCPPResponseService';
33 import { ResponseHandler } from '../../../types/ocpp/Responses';
34 import Utils from '../../../utils/Utils';
35 import logger from '../../../utils/Logger';
36
37 const moduleName = 'OCPP16ResponseService';
38
39 export default class OCPP16ResponseService extends OCPPResponseService {
40 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
41
42 public constructor(chargingStation: ChargingStation) {
43 if (new.target?.name === moduleName) {
44 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
45 }
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)],
54 [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
55 ]);
56 }
57
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 ) {
67 if (this.responseHandlers.has(commandName)) {
68 try {
69 await this.responseHandlers.get(commandName)(payload, requestPayload);
70 } catch (error) {
71 logger.error(
72 this.chargingStation.logPrefix() + ' Handle request response error: %j',
73 error
74 );
75 throw error;
76 }
77 } else {
78 // Throw exception
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 );
88 }
89 } else {
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 );
99 }
100 }
101
102 private handleResponseBootNotification(payload: OCPP16BootNotificationResponse): void {
103 if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
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();
116 }
117 if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
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);
124 } else {
125 logger.error(
126 this.chargingStation.logPrefix() +
127 ' Charging station boot notification response received: %j with undefined registration status',
128 payload
129 );
130 }
131 }
132
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 );
143 }
144
145 private handleResponseAuthorize(
146 payload: OCPP16AuthorizeResponse,
147 requestPayload: AuthorizeRequest
148 ): void {
149 let authorizeConnectorId: number;
150 for (const connectorId of this.chargingStation.connectors.keys()) {
151 if (
152 connectorId > 0 &&
153 this.chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag ===
154 requestPayload.idTag
155 ) {
156 authorizeConnectorId = connectorId;
157 break;
158 }
159 }
160 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
161 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
162 logger.debug(
163 `${this.chargingStation.logPrefix()} IdTag ${
164 requestPayload.idTag
165 } authorized on connector ${authorizeConnectorId}`
166 );
167 } else {
168 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
169 delete this.chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
170 logger.debug(
171 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${
172 payload.idTagInfo.status
173 } on connector ${authorizeConnectorId}`
174 );
175 }
176 }
177
178 private async handleResponseStartTransaction(
179 payload: OCPP16StartTransactionResponse,
180 requestPayload: StartTransactionRequest
181 ): Promise<void> {
182 const connectorId = requestPayload.connectorId;
183
184 let transactionConnectorId: number;
185 for (const id of this.chargingStation.connectors.keys()) {
186 if (id > 0 && id === connectorId) {
187 transactionConnectorId = id;
188 break;
189 }
190 }
191 if (!transactionConnectorId) {
192 logger.error(
193 this.chargingStation.logPrefix() +
194 ' Trying to start a transaction on a non existing connector Id ' +
195 connectorId.toString()
196 );
197 return;
198 }
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 );
213 await this.resetConnectorOnStartTransactionError(connectorId);
214 return;
215 }
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 );
230 await this.resetConnectorOnStartTransactionError(connectorId);
231 return;
232 }
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 );
246 await this.resetConnectorOnStartTransactionError(connectorId);
247 return;
248 }
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 );
263 await this.resetConnectorOnStartTransactionError(connectorId);
264 return;
265 }
266 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
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 );
274 return;
275 }
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 );
287 return;
288 }
289 if (!Number.isInteger(payload.transactionId)) {
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 );
295 payload.transactionId = Utils.convertToInt(payload.transactionId);
296 }
297
298 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
299 this.chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
300 this.chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
301 this.chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
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 );
334 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
335 this.chargingStation.stationInfo.powerDivider++;
336 }
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 );
346 } else {
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 );
356 await this.resetConnectorOnStartTransactionError(connectorId);
357 }
358 }
359
360 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
361 this.chargingStation.resetConnectorStatus(connectorId);
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;
372 }
373 }
374
375 private async handleResponseStopTransaction(
376 payload: OCPP16StopTransactionResponse,
377 requestPayload: StopTransactionRequest
378 ): Promise<void> {
379 let transactionConnectorId: number;
380 for (const connectorId of this.chargingStation.connectors.keys()) {
381 if (
382 connectorId > 0 &&
383 this.chargingStation.getConnectorStatus(connectorId)?.transactionId ===
384 requestPayload.transactionId
385 ) {
386 transactionConnectorId = connectorId;
387 break;
388 }
389 }
390 if (!transactionConnectorId) {
391 logger.error(
392 this.chargingStation.logPrefix() +
393 ' Trying to stop a non existing transaction ' +
394 requestPayload.transactionId.toString()
395 );
396 return;
397 }
398 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
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;
421 } else {
422 await this.chargingStation.ocppRequestService.sendStatusNotification(
423 transactionConnectorId,
424 OCPP16ChargePointStatus.AVAILABLE
425 );
426 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
427 OCPP16ChargePointStatus.AVAILABLE;
428 }
429 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
430 this.chargingStation.stationInfo.powerDivider--;
431 }
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 );
441 this.chargingStation.resetConnectorStatus(transactionConnectorId);
442 } else {
443 logger.warn(
444 this.chargingStation.logPrefix() +
445 ' Stopping transaction id ' +
446 requestPayload.transactionId.toString() +
447 ' REJECTED with status ' +
448 payload.idTagInfo?.status
449 );
450 }
451 }
452
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 );
463 }
464
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 );
475 }
476 }