fix: fix reservationId payload field filling at start transaction
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import { createWriteStream, readdirSync } from 'node:fs';
4 import { dirname, join, resolve } from 'node:path';
5 import { URL, fileURLToPath } from 'node:url';
6
7 import type { JSONSchemaType } from 'ajv';
8 import { Client, type FTPResponse } from 'basic-ftp';
9 import { secondsToMilliseconds } from 'date-fns';
10 import { create } from 'tar';
11
12 import { OCPP16Constants } from './OCPP16Constants';
13 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
14 import {
15 type ChargingStation,
16 checkChargingStation,
17 getConfigurationKey,
18 removeExpiredReservations,
19 setConfigurationKeyValue,
20 } from '../../../charging-station';
21 import { OCPPError } from '../../../exception';
22 import {
23 type ChangeConfigurationRequest,
24 type ChangeConfigurationResponse,
25 type ClearChargingProfileRequest,
26 type ClearChargingProfileResponse,
27 ErrorType,
28 type GenericResponse,
29 GenericStatus,
30 type GetConfigurationRequest,
31 type GetConfigurationResponse,
32 type GetDiagnosticsRequest,
33 type GetDiagnosticsResponse,
34 type IncomingRequestHandler,
35 type JsonObject,
36 type JsonType,
37 OCPP16AuthorizationStatus,
38 OCPP16AvailabilityType,
39 type OCPP16BootNotificationRequest,
40 type OCPP16BootNotificationResponse,
41 type OCPP16CancelReservationRequest,
42 type OCPP16ChangeAvailabilityRequest,
43 type OCPP16ChangeAvailabilityResponse,
44 OCPP16ChargePointErrorCode,
45 OCPP16ChargePointStatus,
46 type OCPP16ChargingProfile,
47 OCPP16ChargingProfilePurposeType,
48 type OCPP16ChargingSchedule,
49 type OCPP16ClearCacheRequest,
50 type OCPP16DataTransferRequest,
51 type OCPP16DataTransferResponse,
52 OCPP16DataTransferVendorId,
53 OCPP16DiagnosticsStatus,
54 type OCPP16DiagnosticsStatusNotificationRequest,
55 type OCPP16DiagnosticsStatusNotificationResponse,
56 OCPP16FirmwareStatus,
57 type OCPP16FirmwareStatusNotificationRequest,
58 type OCPP16FirmwareStatusNotificationResponse,
59 type OCPP16GetCompositeScheduleRequest,
60 type OCPP16GetCompositeScheduleResponse,
61 type OCPP16HeartbeatRequest,
62 type OCPP16HeartbeatResponse,
63 OCPP16IncomingRequestCommand,
64 OCPP16MessageTrigger,
65 OCPP16RequestCommand,
66 type OCPP16ReserveNowRequest,
67 type OCPP16ReserveNowResponse,
68 OCPP16StandardParametersKey,
69 type OCPP16StartTransactionRequest,
70 type OCPP16StartTransactionResponse,
71 type OCPP16StatusNotificationRequest,
72 type OCPP16StatusNotificationResponse,
73 OCPP16StopTransactionReason,
74 OCPP16SupportedFeatureProfiles,
75 type OCPP16TriggerMessageRequest,
76 type OCPP16TriggerMessageResponse,
77 type OCPP16UpdateFirmwareRequest,
78 type OCPP16UpdateFirmwareResponse,
79 type OCPPConfigurationKey,
80 OCPPVersion,
81 type RemoteStartTransactionRequest,
82 type RemoteStopTransactionRequest,
83 ReservationTerminationReason,
84 type ResetRequest,
85 type SetChargingProfileRequest,
86 type SetChargingProfileResponse,
87 type UnlockConnectorRequest,
88 type UnlockConnectorResponse,
89 } from '../../../types';
90 import {
91 Constants,
92 convertToDate,
93 convertToInt,
94 formatDurationMilliSeconds,
95 getRandomInteger,
96 isEmptyArray,
97 isNotEmptyArray,
98 isNotEmptyString,
99 isNullOrUndefined,
100 isUndefined,
101 logger,
102 sleep,
103 } from '../../../utils';
104 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService';
105
106 const moduleName = 'OCPP16IncomingRequestService';
107
108 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
109 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
110 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
111
112 public constructor() {
113 // if (new.target?.name === moduleName) {
114 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
115 // }
116 super(OCPPVersion.VERSION_16);
117 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
118 [
119 OCPP16IncomingRequestCommand.RESET,
120 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler,
121 ],
122 [
123 OCPP16IncomingRequestCommand.CLEAR_CACHE,
124 this.handleRequestClearCache.bind(this) as IncomingRequestHandler,
125 ],
126 [
127 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
128 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler,
129 ],
130 [
131 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
132 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler,
133 ],
134 [
135 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
136 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler,
137 ],
138 [
139 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
140 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler,
141 ],
142 [
143 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
144 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler,
145 ],
146 [
147 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
148 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler,
149 ],
150 [
151 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
152 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler,
153 ],
154 [
155 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
156 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler,
157 ],
158 [
159 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
160 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler,
161 ],
162 [
163 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
164 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler,
165 ],
166 [
167 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
168 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler,
169 ],
170 [
171 OCPP16IncomingRequestCommand.DATA_TRANSFER,
172 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler,
173 ],
174 [
175 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
176 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler,
177 ],
178 [
179 OCPP16IncomingRequestCommand.RESERVE_NOW,
180 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler,
181 ],
182 [
183 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
184 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler,
185 ],
186 ]);
187 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
188 [
189 OCPP16IncomingRequestCommand.RESET,
190 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
191 'assets/json-schemas/ocpp/1.6/Reset.json',
192 moduleName,
193 'constructor',
194 ),
195 ],
196 [
197 OCPP16IncomingRequestCommand.CLEAR_CACHE,
198 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
199 'assets/json-schemas/ocpp/1.6/ClearCache.json',
200 moduleName,
201 'constructor',
202 ),
203 ],
204 [
205 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
206 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
207 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
208 moduleName,
209 'constructor',
210 ),
211 ],
212 [
213 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
214 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
215 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
216 moduleName,
217 'constructor',
218 ),
219 ],
220 [
221 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
222 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
223 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
224 moduleName,
225 'constructor',
226 ),
227 ],
228 [
229 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
230 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
231 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
232 moduleName,
233 'constructor',
234 ),
235 ],
236 [
237 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
238 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
239 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
240 moduleName,
241 'constructor',
242 ),
243 ],
244 [
245 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
246 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
247 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
248 moduleName,
249 'constructor',
250 ),
251 ],
252 [
253 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
254 OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileRequest>(
255 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
256 moduleName,
257 'constructor',
258 ),
259 ],
260 [
261 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
262 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
263 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
264 moduleName,
265 'constructor',
266 ),
267 ],
268 [
269 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
270 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
271 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
272 moduleName,
273 'constructor',
274 ),
275 ],
276 [
277 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
278 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
279 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
280 moduleName,
281 'constructor',
282 ),
283 ],
284 [
285 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
286 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
287 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
288 moduleName,
289 'constructor',
290 ),
291 ],
292 [
293 OCPP16IncomingRequestCommand.DATA_TRANSFER,
294 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
295 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
296 moduleName,
297 'constructor',
298 ),
299 ],
300 [
301 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
302 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
303 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
304 moduleName,
305 'constructor',
306 ),
307 ],
308 [
309 OCPP16IncomingRequestCommand.RESERVE_NOW,
310 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
311 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
312 moduleName,
313 'constructor',
314 ),
315 ],
316 [
317 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
318 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
319 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
320 moduleName,
321 'constructor',
322 ),
323 ],
324 ]);
325 this.validatePayload = this.validatePayload.bind(this) as (
326 chargingStation: ChargingStation,
327 commandName: OCPP16IncomingRequestCommand,
328 commandPayload: JsonType,
329 ) => boolean;
330 }
331
332 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
333 chargingStation: ChargingStation,
334 messageId: string,
335 commandName: OCPP16IncomingRequestCommand,
336 commandPayload: ReqType,
337 ): Promise<void> {
338 let response: ResType;
339 if (
340 chargingStation.getOcppStrictCompliance() === true &&
341 chargingStation.inPendingState() === true &&
342 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
343 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
344 ) {
345 throw new OCPPError(
346 ErrorType.SECURITY_ERROR,
347 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
348 commandPayload,
349 null,
350 2,
351 )} while the charging station is in pending state on the central server`,
352 commandName,
353 commandPayload,
354 );
355 }
356 if (
357 chargingStation.isRegistered() === true ||
358 (chargingStation.getOcppStrictCompliance() === false &&
359 chargingStation.inUnknownState() === true)
360 ) {
361 if (
362 this.incomingRequestHandlers.has(commandName) === true &&
363 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
364 ) {
365 try {
366 this.validatePayload(chargingStation, commandName, commandPayload);
367 // Call the method to build the response
368 response = (await this.incomingRequestHandlers.get(commandName)!(
369 chargingStation,
370 commandPayload,
371 )) as ResType;
372 } catch (error) {
373 // Log
374 logger.error(
375 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
376 Handle incoming request error:`,
377 error,
378 );
379 throw error;
380 }
381 } else {
382 // Throw exception
383 throw new OCPPError(
384 ErrorType.NOT_IMPLEMENTED,
385 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
386 commandPayload,
387 null,
388 2,
389 )}`,
390 commandName,
391 commandPayload,
392 );
393 }
394 } else {
395 throw new OCPPError(
396 ErrorType.SECURITY_ERROR,
397 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
398 commandPayload,
399 null,
400 2,
401 )} while the charging station is not registered on the central server.`,
402 commandName,
403 commandPayload,
404 );
405 }
406 // Send the built response
407 await chargingStation.ocppRequestService.sendResponse(
408 chargingStation,
409 messageId,
410 response,
411 commandName,
412 );
413 }
414
415 private validatePayload(
416 chargingStation: ChargingStation,
417 commandName: OCPP16IncomingRequestCommand,
418 commandPayload: JsonType,
419 ): boolean {
420 if (this.jsonSchemas.has(commandName) === true) {
421 return this.validateIncomingRequestPayload(
422 chargingStation,
423 commandName,
424 this.jsonSchemas.get(commandName)!,
425 commandPayload,
426 );
427 }
428 logger.warn(
429 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
430 for command '${commandName}' PDU validation`,
431 );
432 return false;
433 }
434
435 // Simulate charging station restart
436 private handleRequestReset(
437 chargingStation: ChargingStation,
438 commandPayload: ResetRequest,
439 ): GenericResponse {
440 this.runInAsyncScope(
441 chargingStation.reset.bind(chargingStation) as (
442 this: ChargingStation,
443 ...args: unknown[]
444 ) => Promise<void>,
445 chargingStation,
446 `${commandPayload.type}Reset` as OCPP16StopTransactionReason,
447 ).catch(Constants.EMPTY_FUNCTION);
448 logger.info(
449 `${chargingStation.logPrefix()} ${
450 commandPayload.type
451 } reset command received, simulating it. The station will be
452 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`,
453 );
454 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
455 }
456
457 private async handleRequestUnlockConnector(
458 chargingStation: ChargingStation,
459 commandPayload: UnlockConnectorRequest,
460 ): Promise<UnlockConnectorResponse> {
461 const connectorId = commandPayload.connectorId;
462 if (chargingStation.hasConnector(connectorId) === false) {
463 logger.error(
464 `${chargingStation.logPrefix()} Trying to unlock a non existing
465 connector id ${connectorId.toString()}`,
466 );
467 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
468 }
469 if (connectorId === 0) {
470 logger.error(
471 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
472 );
473 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
474 }
475 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
476 const stopResponse = await chargingStation.stopTransactionOnConnector(
477 connectorId,
478 OCPP16StopTransactionReason.UNLOCK_COMMAND,
479 );
480 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
481 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
482 }
483 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
484 }
485 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
486 chargingStation,
487 connectorId,
488 OCPP16ChargePointStatus.Available,
489 );
490 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
491 }
492
493 private handleRequestGetConfiguration(
494 chargingStation: ChargingStation,
495 commandPayload: GetConfigurationRequest,
496 ): GetConfigurationResponse {
497 const configurationKey: OCPPConfigurationKey[] = [];
498 const unknownKey: string[] = [];
499 if (isUndefined(commandPayload.key) === true) {
500 for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) {
501 if (isUndefined(configuration.visible) === true) {
502 configuration.visible = true;
503 }
504 if (configuration.visible === false) {
505 continue;
506 }
507 configurationKey.push({
508 key: configuration.key,
509 readonly: configuration.readonly,
510 value: configuration.value,
511 });
512 }
513 } else if (isNotEmptyArray(commandPayload.key) === true) {
514 for (const key of commandPayload.key!) {
515 const keyFound = getConfigurationKey(chargingStation, key, true);
516 if (keyFound) {
517 if (isUndefined(keyFound.visible) === true) {
518 keyFound.visible = true;
519 }
520 if (keyFound.visible === false) {
521 continue;
522 }
523 configurationKey.push({
524 key: keyFound.key,
525 readonly: keyFound.readonly,
526 value: keyFound.value,
527 });
528 } else {
529 unknownKey.push(key);
530 }
531 }
532 }
533 return {
534 configurationKey,
535 unknownKey,
536 };
537 }
538
539 private handleRequestChangeConfiguration(
540 chargingStation: ChargingStation,
541 commandPayload: ChangeConfigurationRequest,
542 ): ChangeConfigurationResponse {
543 const keyToChange = getConfigurationKey(chargingStation, commandPayload.key, true);
544 if (keyToChange?.readonly === true) {
545 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
546 } else if (keyToChange?.readonly === false) {
547 let valueChanged = false;
548 if (keyToChange.value !== commandPayload.value) {
549 setConfigurationKeyValue(chargingStation, commandPayload.key, commandPayload.value, true);
550 valueChanged = true;
551 }
552 let triggerHeartbeatRestart = false;
553 if (
554 (keyToChange.key as OCPP16StandardParametersKey) ===
555 OCPP16StandardParametersKey.HeartBeatInterval &&
556 valueChanged
557 ) {
558 setConfigurationKeyValue(
559 chargingStation,
560 OCPP16StandardParametersKey.HeartbeatInterval,
561 commandPayload.value,
562 );
563 triggerHeartbeatRestart = true;
564 }
565 if (
566 (keyToChange.key as OCPP16StandardParametersKey) ===
567 OCPP16StandardParametersKey.HeartbeatInterval &&
568 valueChanged
569 ) {
570 setConfigurationKeyValue(
571 chargingStation,
572 OCPP16StandardParametersKey.HeartBeatInterval,
573 commandPayload.value,
574 );
575 triggerHeartbeatRestart = true;
576 }
577 if (triggerHeartbeatRestart) {
578 chargingStation.restartHeartbeat();
579 }
580 if (
581 (keyToChange.key as OCPP16StandardParametersKey) ===
582 OCPP16StandardParametersKey.WebSocketPingInterval &&
583 valueChanged
584 ) {
585 chargingStation.restartWebSocketPing();
586 }
587 if (keyToChange.reboot) {
588 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
589 }
590 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
591 }
592 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
593 }
594
595 private handleRequestSetChargingProfile(
596 chargingStation: ChargingStation,
597 commandPayload: SetChargingProfileRequest,
598 ): SetChargingProfileResponse {
599 if (
600 OCPP16ServiceUtils.checkFeatureProfile(
601 chargingStation,
602 OCPP16SupportedFeatureProfiles.SmartCharging,
603 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
604 ) === false
605 ) {
606 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
607 }
608 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
609 logger.error(
610 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
611 non existing connector id ${commandPayload.connectorId}`,
612 );
613 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
614 }
615 if (
616 commandPayload.csChargingProfiles.chargingProfilePurpose ===
617 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
618 commandPayload.connectorId !== 0
619 ) {
620 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
621 }
622 if (
623 commandPayload.csChargingProfiles.chargingProfilePurpose ===
624 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
625 (commandPayload.connectorId === 0 ||
626 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
627 false)
628 ) {
629 logger.error(
630 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
631 on connector ${commandPayload.connectorId} without a started transaction`,
632 );
633 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
634 }
635 OCPP16ServiceUtils.setChargingProfile(
636 chargingStation,
637 commandPayload.connectorId,
638 commandPayload.csChargingProfiles,
639 );
640 logger.debug(
641 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
642 commandPayload.connectorId
643 }: %j`,
644 commandPayload.csChargingProfiles,
645 );
646 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
647 }
648
649 private handleRequestGetCompositeSchedule(
650 chargingStation: ChargingStation,
651 commandPayload: OCPP16GetCompositeScheduleRequest,
652 ): OCPP16GetCompositeScheduleResponse {
653 if (
654 OCPP16ServiceUtils.checkFeatureProfile(
655 chargingStation,
656 OCPP16SupportedFeatureProfiles.SmartCharging,
657 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
658 ) === false
659 ) {
660 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
661 }
662 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
663 logger.error(
664 `${chargingStation.logPrefix()} Trying to get composite schedule to a
665 non existing connector id ${commandPayload.connectorId}`,
666 );
667 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
668 }
669 if (
670 isEmptyArray(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles)
671 ) {
672 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
673 }
674 const startDate = new Date();
675 const endDate = new Date(startDate.getTime() + secondsToMilliseconds(commandPayload.duration));
676 let compositeSchedule: OCPP16ChargingSchedule | undefined;
677 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)!
678 .chargingProfiles!) {
679 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
680 if (
681 chargingProfile.chargingSchedule.startSchedule! >= startDate &&
682 chargingProfile.chargingSchedule.startSchedule! <= endDate
683 ) {
684 compositeSchedule = chargingProfile.chargingSchedule;
685 break;
686 }
687 }
688 return {
689 status: GenericStatus.Accepted,
690 scheduleStart: compositeSchedule?.startSchedule,
691 connectorId: commandPayload.connectorId,
692 chargingSchedule: compositeSchedule,
693 };
694 }
695
696 private handleRequestClearChargingProfile(
697 chargingStation: ChargingStation,
698 commandPayload: ClearChargingProfileRequest,
699 ): ClearChargingProfileResponse {
700 if (
701 OCPP16ServiceUtils.checkFeatureProfile(
702 chargingStation,
703 OCPP16SupportedFeatureProfiles.SmartCharging,
704 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
705 ) === false
706 ) {
707 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
708 }
709 if (chargingStation.hasConnector(commandPayload.connectorId!) === false) {
710 logger.error(
711 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
712 a non existing connector id ${commandPayload.connectorId}`,
713 );
714 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
715 }
716 if (
717 !isNullOrUndefined(commandPayload.connectorId) &&
718 isNotEmptyArray(
719 chargingStation.getConnectorStatus(commandPayload.connectorId!)?.chargingProfiles,
720 )
721 ) {
722 chargingStation.getConnectorStatus(commandPayload.connectorId!)!.chargingProfiles = [];
723 logger.debug(
724 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
725 commandPayload.connectorId
726 }`,
727 );
728 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
729 }
730 if (isNullOrUndefined(commandPayload.connectorId)) {
731 let clearedCP = false;
732 if (chargingStation.hasEvses) {
733 for (const evseStatus of chargingStation.evses.values()) {
734 for (const connectorStatus of evseStatus.connectors.values()) {
735 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
736 chargingStation,
737 commandPayload,
738 connectorStatus.chargingProfiles,
739 );
740 }
741 }
742 } else {
743 for (const connectorId of chargingStation.connectors.keys()) {
744 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
745 chargingStation,
746 commandPayload,
747 chargingStation.getConnectorStatus(connectorId)?.chargingProfiles,
748 );
749 }
750 }
751 if (clearedCP) {
752 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
753 }
754 }
755 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
756 }
757
758 private async handleRequestChangeAvailability(
759 chargingStation: ChargingStation,
760 commandPayload: OCPP16ChangeAvailabilityRequest,
761 ): Promise<OCPP16ChangeAvailabilityResponse> {
762 const connectorId: number = commandPayload.connectorId;
763 if (chargingStation.hasConnector(connectorId) === false) {
764 logger.error(
765 `${chargingStation.logPrefix()} Trying to change the availability of a
766 non existing connector id ${connectorId.toString()}`,
767 );
768 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
769 }
770 const chargePointStatus: OCPP16ChargePointStatus =
771 commandPayload.type === OCPP16AvailabilityType.Operative
772 ? OCPP16ChargePointStatus.Available
773 : OCPP16ChargePointStatus.Unavailable;
774 if (connectorId === 0) {
775 let response: OCPP16ChangeAvailabilityResponse;
776 if (chargingStation.hasEvses) {
777 for (const evseStatus of chargingStation.evses.values()) {
778 response = await OCPP16ServiceUtils.changeAvailability(
779 chargingStation,
780 [...evseStatus.connectors.keys()],
781 chargePointStatus,
782 commandPayload.type,
783 );
784 }
785 } else {
786 response = await OCPP16ServiceUtils.changeAvailability(
787 chargingStation,
788 [...chargingStation.connectors.keys()],
789 chargePointStatus,
790 commandPayload.type,
791 );
792 }
793 return response!;
794 } else if (
795 connectorId > 0 &&
796 (chargingStation.isChargingStationAvailable() === true ||
797 (chargingStation.isChargingStationAvailable() === false &&
798 commandPayload.type === OCPP16AvailabilityType.Inoperative))
799 ) {
800 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
801 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
802 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
803 }
804 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
805 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
806 chargingStation,
807 connectorId,
808 chargePointStatus,
809 );
810 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
811 }
812 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
813 }
814
815 private async handleRequestRemoteStartTransaction(
816 chargingStation: ChargingStation,
817 commandPayload: RemoteStartTransactionRequest,
818 ): Promise<GenericResponse> {
819 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
820 if (chargingStation.hasConnector(transactionConnectorId) === false) {
821 return this.notifyRemoteStartTransactionRejected(
822 chargingStation,
823 transactionConnectorId,
824 idTag,
825 );
826 }
827 if (
828 !chargingStation.isChargingStationAvailable() ||
829 !chargingStation.isConnectorAvailable(transactionConnectorId)
830 ) {
831 return this.notifyRemoteStartTransactionRejected(
832 chargingStation,
833 transactionConnectorId,
834 idTag,
835 );
836 }
837 const remoteStartTransactionLogMsg = `
838 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
839 chargingStation.stationInfo.chargingStationId
840 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
841 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
842 chargingStation,
843 transactionConnectorId,
844 OCPP16ChargePointStatus.Preparing,
845 );
846 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
847 // Authorization check required
848 if (
849 chargingStation.getAuthorizeRemoteTxRequests() === true &&
850 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
851 ) {
852 // Authorization successful, start transaction
853 if (
854 this.setRemoteStartTransactionChargingProfile(
855 chargingStation,
856 transactionConnectorId,
857 chargingProfile!,
858 ) === true
859 ) {
860 connectorStatus.transactionRemoteStarted = true;
861 if (
862 (
863 await chargingStation.ocppRequestService.requestHandler<
864 OCPP16StartTransactionRequest,
865 OCPP16StartTransactionResponse
866 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
867 connectorId: transactionConnectorId,
868 idTag,
869 })
870 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
871 ) {
872 logger.debug(remoteStartTransactionLogMsg);
873 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
874 }
875 return this.notifyRemoteStartTransactionRejected(
876 chargingStation,
877 transactionConnectorId,
878 idTag,
879 );
880 }
881 return this.notifyRemoteStartTransactionRejected(
882 chargingStation,
883 transactionConnectorId,
884 idTag,
885 );
886 }
887 // No authorization check required, start transaction
888 if (
889 this.setRemoteStartTransactionChargingProfile(
890 chargingStation,
891 transactionConnectorId,
892 chargingProfile!,
893 ) === true
894 ) {
895 connectorStatus.transactionRemoteStarted = true;
896 if (
897 (
898 await chargingStation.ocppRequestService.requestHandler<
899 OCPP16StartTransactionRequest,
900 OCPP16StartTransactionResponse
901 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
902 connectorId: transactionConnectorId,
903 idTag,
904 })
905 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
906 ) {
907 logger.debug(remoteStartTransactionLogMsg);
908 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
909 }
910 return this.notifyRemoteStartTransactionRejected(
911 chargingStation,
912 transactionConnectorId,
913 idTag,
914 );
915 }
916 return this.notifyRemoteStartTransactionRejected(
917 chargingStation,
918 transactionConnectorId,
919 idTag,
920 );
921 }
922
923 private async notifyRemoteStartTransactionRejected(
924 chargingStation: ChargingStation,
925 connectorId: number,
926 idTag: string,
927 ): Promise<GenericResponse> {
928 if (
929 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
930 ) {
931 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
932 chargingStation,
933 connectorId,
934 OCPP16ChargePointStatus.Available,
935 );
936 }
937 logger.warn(
938 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
939 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
940 connectorId,
941 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
942 );
943 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
944 }
945
946 private setRemoteStartTransactionChargingProfile(
947 chargingStation: ChargingStation,
948 connectorId: number,
949 chargingProfile: OCPP16ChargingProfile,
950 ): boolean {
951 if (
952 chargingProfile &&
953 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
954 ) {
955 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
956 logger.debug(
957 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
958 on connector id ${connectorId}: %j`,
959 chargingProfile,
960 );
961 return true;
962 } else if (
963 chargingProfile &&
964 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
965 ) {
966 logger.warn(
967 `${chargingStation.logPrefix()} Not allowed to set ${
968 chargingProfile.chargingProfilePurpose
969 } charging profile(s) at remote start transaction`,
970 );
971 return false;
972 }
973 return true;
974 }
975
976 private async handleRequestRemoteStopTransaction(
977 chargingStation: ChargingStation,
978 commandPayload: RemoteStopTransactionRequest,
979 ): Promise<GenericResponse> {
980 const transactionId = commandPayload.transactionId;
981 if (chargingStation.hasEvses) {
982 for (const [evseId, evseStatus] of chargingStation.evses) {
983 if (evseId > 0) {
984 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
985 if (connectorStatus.transactionId === transactionId) {
986 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
987 }
988 }
989 }
990 }
991 } else {
992 for (const connectorId of chargingStation.connectors.keys()) {
993 if (
994 connectorId > 0 &&
995 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
996 ) {
997 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
998 }
999 }
1000 }
1001 logger.warn(
1002 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1003 ${transactionId.toString()}`,
1004 );
1005 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1006 }
1007
1008 private handleRequestUpdateFirmware(
1009 chargingStation: ChargingStation,
1010 commandPayload: OCPP16UpdateFirmwareRequest,
1011 ): OCPP16UpdateFirmwareResponse {
1012 if (
1013 OCPP16ServiceUtils.checkFeatureProfile(
1014 chargingStation,
1015 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1016 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1017 ) === false
1018 ) {
1019 logger.warn(
1020 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1021 Cannot simulate firmware update: feature profile not supported`,
1022 );
1023 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1024 }
1025 if (
1026 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1027 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1028 ) {
1029 logger.warn(
1030 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1031 Cannot simulate firmware update: firmware update is already in progress`,
1032 );
1033 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1034 }
1035 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1036 const now = Date.now();
1037 if (retrieveDate?.getTime() <= now) {
1038 this.runInAsyncScope(
1039 this.updateFirmwareSimulation.bind(this) as (
1040 this: OCPP16IncomingRequestService,
1041 ...args: unknown[]
1042 ) => Promise<void>,
1043 this,
1044 chargingStation,
1045 ).catch(Constants.EMPTY_FUNCTION);
1046 } else {
1047 setTimeout(
1048 () => {
1049 this.runInAsyncScope(
1050 this.updateFirmwareSimulation.bind(this) as (
1051 this: OCPP16IncomingRequestService,
1052 ...args: unknown[]
1053 ) => Promise<void>,
1054 this,
1055 chargingStation,
1056 ).catch(Constants.EMPTY_FUNCTION);
1057 },
1058 retrieveDate?.getTime() - now,
1059 );
1060 }
1061 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1062 }
1063
1064 private async updateFirmwareSimulation(
1065 chargingStation: ChargingStation,
1066 maxDelay = 30,
1067 minDelay = 15,
1068 ): Promise<void> {
1069 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1070 return;
1071 }
1072 if (chargingStation.hasEvses) {
1073 for (const [evseId, evseStatus] of chargingStation.evses) {
1074 if (evseId > 0) {
1075 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1076 if (connectorStatus?.transactionStarted === false) {
1077 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1078 chargingStation,
1079 connectorId,
1080 OCPP16ChargePointStatus.Unavailable,
1081 );
1082 }
1083 }
1084 }
1085 }
1086 } else {
1087 for (const connectorId of chargingStation.connectors.keys()) {
1088 if (
1089 connectorId > 0 &&
1090 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1091 ) {
1092 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1093 chargingStation,
1094 connectorId,
1095 OCPP16ChargePointStatus.Unavailable,
1096 );
1097 }
1098 }
1099 }
1100 await chargingStation.ocppRequestService.requestHandler<
1101 OCPP16FirmwareStatusNotificationRequest,
1102 OCPP16FirmwareStatusNotificationResponse
1103 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1104 status: OCPP16FirmwareStatus.Downloading,
1105 });
1106 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1107 if (
1108 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1109 OCPP16FirmwareStatus.DownloadFailed
1110 ) {
1111 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1112 await chargingStation.ocppRequestService.requestHandler<
1113 OCPP16FirmwareStatusNotificationRequest,
1114 OCPP16FirmwareStatusNotificationResponse
1115 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1116 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1117 });
1118 chargingStation.stationInfo.firmwareStatus =
1119 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1120 return;
1121 }
1122 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1123 await chargingStation.ocppRequestService.requestHandler<
1124 OCPP16FirmwareStatusNotificationRequest,
1125 OCPP16FirmwareStatusNotificationResponse
1126 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1127 status: OCPP16FirmwareStatus.Downloaded,
1128 });
1129 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1130 let wasTransactionsStarted = false;
1131 let transactionsStarted: boolean;
1132 do {
1133 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1134 if (runningTransactions > 0) {
1135 const waitTime = secondsToMilliseconds(15);
1136 logger.debug(
1137 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1138 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1139 waitTime,
1140 )} before continuing firmware update simulation`,
1141 );
1142 await sleep(waitTime);
1143 transactionsStarted = true;
1144 wasTransactionsStarted = true;
1145 } else {
1146 if (chargingStation.hasEvses) {
1147 for (const [evseId, evseStatus] of chargingStation.evses) {
1148 if (evseId > 0) {
1149 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1150 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1151 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1152 chargingStation,
1153 connectorId,
1154 OCPP16ChargePointStatus.Unavailable,
1155 );
1156 }
1157 }
1158 }
1159 }
1160 } else {
1161 for (const connectorId of chargingStation.connectors.keys()) {
1162 if (
1163 connectorId > 0 &&
1164 chargingStation.getConnectorStatus(connectorId)?.status !==
1165 OCPP16ChargePointStatus.Unavailable
1166 ) {
1167 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1168 chargingStation,
1169 connectorId,
1170 OCPP16ChargePointStatus.Unavailable,
1171 );
1172 }
1173 }
1174 }
1175 transactionsStarted = false;
1176 }
1177 } while (transactionsStarted);
1178 !wasTransactionsStarted &&
1179 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1180 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1181 return;
1182 }
1183 await chargingStation.ocppRequestService.requestHandler<
1184 OCPP16FirmwareStatusNotificationRequest,
1185 OCPP16FirmwareStatusNotificationResponse
1186 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1187 status: OCPP16FirmwareStatus.Installing,
1188 });
1189 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1190 if (
1191 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1192 OCPP16FirmwareStatus.InstallationFailed
1193 ) {
1194 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1195 await chargingStation.ocppRequestService.requestHandler<
1196 OCPP16FirmwareStatusNotificationRequest,
1197 OCPP16FirmwareStatusNotificationResponse
1198 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1199 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1200 });
1201 chargingStation.stationInfo.firmwareStatus =
1202 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1203 return;
1204 }
1205 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1206 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1207 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1208 }
1209 }
1210
1211 private async handleRequestGetDiagnostics(
1212 chargingStation: ChargingStation,
1213 commandPayload: GetDiagnosticsRequest,
1214 ): Promise<GetDiagnosticsResponse> {
1215 if (
1216 OCPP16ServiceUtils.checkFeatureProfile(
1217 chargingStation,
1218 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1219 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1220 ) === false
1221 ) {
1222 logger.warn(
1223 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1224 Cannot get diagnostics: feature profile not supported`,
1225 );
1226 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1227 }
1228 const uri = new URL(commandPayload.location);
1229 if (uri.protocol.startsWith('ftp:')) {
1230 let ftpClient: Client | undefined;
1231 try {
1232 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1233 .filter((file) => file.endsWith('.log'))
1234 .map((file) => join('./', file));
1235 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1236 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1237 ftpClient = new Client();
1238 const accessResponse = await ftpClient.access({
1239 host: uri.host,
1240 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1241 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1242 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1243 });
1244 let uploadResponse: FTPResponse | undefined;
1245 if (accessResponse.code === 220) {
1246 ftpClient.trackProgress((info) => {
1247 logger.info(
1248 `${chargingStation.logPrefix()} ${
1249 info.bytes / 1024
1250 } bytes transferred from diagnostics archive ${info.name}`,
1251 );
1252 chargingStation.ocppRequestService
1253 .requestHandler<
1254 OCPP16DiagnosticsStatusNotificationRequest,
1255 OCPP16DiagnosticsStatusNotificationResponse
1256 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1257 status: OCPP16DiagnosticsStatus.Uploading,
1258 })
1259 .catch((error) => {
1260 logger.error(
1261 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1262 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1263 error,
1264 );
1265 });
1266 });
1267 uploadResponse = await ftpClient.uploadFrom(
1268 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1269 `${uri.pathname}${diagnosticsArchive}`,
1270 );
1271 if (uploadResponse.code === 226) {
1272 await chargingStation.ocppRequestService.requestHandler<
1273 OCPP16DiagnosticsStatusNotificationRequest,
1274 OCPP16DiagnosticsStatusNotificationResponse
1275 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1276 status: OCPP16DiagnosticsStatus.Uploaded,
1277 });
1278 if (ftpClient) {
1279 ftpClient.close();
1280 }
1281 return { fileName: diagnosticsArchive };
1282 }
1283 throw new OCPPError(
1284 ErrorType.GENERIC_ERROR,
1285 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1286 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1287 }`,
1288 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1289 );
1290 }
1291 throw new OCPPError(
1292 ErrorType.GENERIC_ERROR,
1293 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1294 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1295 }`,
1296 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1297 );
1298 } catch (error) {
1299 await chargingStation.ocppRequestService.requestHandler<
1300 OCPP16DiagnosticsStatusNotificationRequest,
1301 OCPP16DiagnosticsStatusNotificationResponse
1302 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1303 status: OCPP16DiagnosticsStatus.UploadFailed,
1304 });
1305 if (ftpClient) {
1306 ftpClient.close();
1307 }
1308 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1309 chargingStation,
1310 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1311 error as Error,
1312 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1313 )!;
1314 }
1315 } else {
1316 logger.error(
1317 `${chargingStation.logPrefix()} Unsupported protocol ${
1318 uri.protocol
1319 } to transfer the diagnostic logs archive`,
1320 );
1321 await chargingStation.ocppRequestService.requestHandler<
1322 OCPP16DiagnosticsStatusNotificationRequest,
1323 OCPP16DiagnosticsStatusNotificationResponse
1324 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1325 status: OCPP16DiagnosticsStatus.UploadFailed,
1326 });
1327 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1328 }
1329 }
1330
1331 private handleRequestTriggerMessage(
1332 chargingStation: ChargingStation,
1333 commandPayload: OCPP16TriggerMessageRequest,
1334 ): OCPP16TriggerMessageResponse {
1335 if (
1336 !OCPP16ServiceUtils.checkFeatureProfile(
1337 chargingStation,
1338 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1339 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1340 ) ||
1341 !OCPP16ServiceUtils.isMessageTriggerSupported(
1342 chargingStation,
1343 commandPayload.requestedMessage,
1344 )
1345 ) {
1346 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1347 }
1348 if (
1349 !OCPP16ServiceUtils.isConnectorIdValid(
1350 chargingStation,
1351 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1352 commandPayload.connectorId!,
1353 )
1354 ) {
1355 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1356 }
1357 try {
1358 switch (commandPayload.requestedMessage) {
1359 case OCPP16MessageTrigger.BootNotification:
1360 setTimeout(() => {
1361 chargingStation.ocppRequestService
1362 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1363 chargingStation,
1364 OCPP16RequestCommand.BOOT_NOTIFICATION,
1365 chargingStation.bootNotificationRequest,
1366 { skipBufferingOnError: true, triggerMessage: true },
1367 )
1368 .then((response) => {
1369 chargingStation.bootNotificationResponse = response;
1370 })
1371 .catch(Constants.EMPTY_FUNCTION);
1372 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1373 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1374 case OCPP16MessageTrigger.Heartbeat:
1375 setTimeout(() => {
1376 chargingStation.ocppRequestService
1377 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1378 chargingStation,
1379 OCPP16RequestCommand.HEARTBEAT,
1380 null,
1381 {
1382 triggerMessage: true,
1383 },
1384 )
1385 .catch(Constants.EMPTY_FUNCTION);
1386 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1387 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1388 case OCPP16MessageTrigger.StatusNotification:
1389 setTimeout(() => {
1390 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1391 chargingStation.ocppRequestService
1392 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1393 chargingStation,
1394 OCPP16RequestCommand.STATUS_NOTIFICATION,
1395 {
1396 connectorId: commandPayload.connectorId,
1397 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1398 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1399 },
1400 {
1401 triggerMessage: true,
1402 },
1403 )
1404 .catch(Constants.EMPTY_FUNCTION);
1405 } else {
1406 // eslint-disable-next-line no-lonely-if
1407 if (chargingStation.hasEvses) {
1408 for (const evseStatus of chargingStation.evses.values()) {
1409 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1410 chargingStation.ocppRequestService
1411 .requestHandler<
1412 OCPP16StatusNotificationRequest,
1413 OCPP16StatusNotificationResponse
1414 >(
1415 chargingStation,
1416 OCPP16RequestCommand.STATUS_NOTIFICATION,
1417 {
1418 connectorId,
1419 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1420 status: connectorStatus.status,
1421 },
1422 {
1423 triggerMessage: true,
1424 },
1425 )
1426 .catch(Constants.EMPTY_FUNCTION);
1427 }
1428 }
1429 } else {
1430 for (const connectorId of chargingStation.connectors.keys()) {
1431 chargingStation.ocppRequestService
1432 .requestHandler<
1433 OCPP16StatusNotificationRequest,
1434 OCPP16StatusNotificationResponse
1435 >(
1436 chargingStation,
1437 OCPP16RequestCommand.STATUS_NOTIFICATION,
1438 {
1439 connectorId,
1440 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1441 status: chargingStation.getConnectorStatus(connectorId)?.status,
1442 },
1443 {
1444 triggerMessage: true,
1445 },
1446 )
1447 .catch(Constants.EMPTY_FUNCTION);
1448 }
1449 }
1450 }
1451 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1452 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1453 default:
1454 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1455 }
1456 } catch (error) {
1457 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1458 chargingStation,
1459 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1460 error as Error,
1461 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1462 )!;
1463 }
1464 }
1465
1466 private handleRequestDataTransfer(
1467 chargingStation: ChargingStation,
1468 commandPayload: OCPP16DataTransferRequest,
1469 ): OCPP16DataTransferResponse {
1470 try {
1471 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1472 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1473 }
1474 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1475 } catch (error) {
1476 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1477 chargingStation,
1478 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1479 error as Error,
1480 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1481 )!;
1482 }
1483 }
1484
1485 private async handleRequestReserveNow(
1486 chargingStation: ChargingStation,
1487 commandPayload: OCPP16ReserveNowRequest,
1488 ): Promise<OCPP16ReserveNowResponse> {
1489 if (
1490 !OCPP16ServiceUtils.checkFeatureProfile(
1491 chargingStation,
1492 OCPP16SupportedFeatureProfiles.Reservation,
1493 OCPP16IncomingRequestCommand.RESERVE_NOW,
1494 )
1495 ) {
1496 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1497 }
1498 const { reservationId, idTag, connectorId } = commandPayload;
1499 let response: OCPP16ReserveNowResponse;
1500 try {
1501 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1502 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1503 }
1504 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1505 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1506 }
1507 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1508 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1509 }
1510 await removeExpiredReservations(chargingStation);
1511 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1512 case OCPP16ChargePointStatus.Faulted:
1513 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1514 break;
1515 case OCPP16ChargePointStatus.Preparing:
1516 case OCPP16ChargePointStatus.Charging:
1517 case OCPP16ChargePointStatus.SuspendedEV:
1518 case OCPP16ChargePointStatus.SuspendedEVSE:
1519 case OCPP16ChargePointStatus.Finishing:
1520 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1521 break;
1522 case OCPP16ChargePointStatus.Unavailable:
1523 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1524 break;
1525 case OCPP16ChargePointStatus.Reserved:
1526 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1527 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1528 break;
1529 }
1530 // eslint-disable-next-line no-fallthrough
1531 default:
1532 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1533 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1534 break;
1535 }
1536 await chargingStation.addReservation({
1537 id: commandPayload.reservationId,
1538 ...commandPayload,
1539 });
1540 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1541 break;
1542 }
1543 return response;
1544 } catch (error) {
1545 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1546 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1547 chargingStation,
1548 OCPP16IncomingRequestCommand.RESERVE_NOW,
1549 error as Error,
1550 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1551 )!;
1552 }
1553 }
1554
1555 private async handleRequestCancelReservation(
1556 chargingStation: ChargingStation,
1557 commandPayload: OCPP16CancelReservationRequest,
1558 ): Promise<GenericResponse> {
1559 if (
1560 !OCPP16ServiceUtils.checkFeatureProfile(
1561 chargingStation,
1562 OCPP16SupportedFeatureProfiles.Reservation,
1563 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1564 )
1565 ) {
1566 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1567 }
1568 try {
1569 const { reservationId } = commandPayload;
1570 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1571 if (isUndefined(reservation)) {
1572 logger.debug(
1573 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1574 does not exist on charging station`,
1575 );
1576 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1577 }
1578 await chargingStation.removeReservation(
1579 reservation!,
1580 ReservationTerminationReason.RESERVATION_CANCELED,
1581 );
1582 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1583 } catch (error) {
1584 return this.handleIncomingRequestError<GenericResponse>(
1585 chargingStation,
1586 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1587 error as Error,
1588 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1589 )!;
1590 }
1591 }
1592 }