refactor: use object destructuration for incoming requests payload
[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 { isWithinInterval, 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 const { type } = commandPayload;
441 this.runInAsyncScope(
442 chargingStation.reset.bind(chargingStation) as (
443 this: ChargingStation,
444 ...args: unknown[]
445 ) => Promise<void>,
446 chargingStation,
447 `${type}Reset` as OCPP16StopTransactionReason,
448 ).catch(Constants.EMPTY_FUNCTION);
449 logger.info(
450 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be
451 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`,
452 );
453 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
454 }
455
456 private async handleRequestUnlockConnector(
457 chargingStation: ChargingStation,
458 commandPayload: UnlockConnectorRequest,
459 ): Promise<UnlockConnectorResponse> {
460 const { connectorId } = commandPayload;
461 if (chargingStation.hasConnector(connectorId) === false) {
462 logger.error(
463 `${chargingStation.logPrefix()} Trying to unlock a non existing
464 connector id ${connectorId.toString()}`,
465 );
466 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
467 }
468 if (connectorId === 0) {
469 logger.error(
470 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
471 );
472 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
473 }
474 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
475 const stopResponse = await chargingStation.stopTransactionOnConnector(
476 connectorId,
477 OCPP16StopTransactionReason.UNLOCK_COMMAND,
478 );
479 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
480 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
481 }
482 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
483 }
484 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
485 chargingStation,
486 connectorId,
487 OCPP16ChargePointStatus.Available,
488 );
489 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
490 }
491
492 private handleRequestGetConfiguration(
493 chargingStation: ChargingStation,
494 commandPayload: GetConfigurationRequest,
495 ): GetConfigurationResponse {
496 const { key } = commandPayload;
497 const configurationKey: OCPPConfigurationKey[] = [];
498 const unknownKey: string[] = [];
499 if (isUndefined(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(key) === true) {
514 for (const k of key!) {
515 const keyFound = getConfigurationKey(chargingStation, k, 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(k);
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 { key, value } = commandPayload;
544 const keyToChange = getConfigurationKey(chargingStation, key, true);
545 if (keyToChange?.readonly === true) {
546 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
547 } else if (keyToChange?.readonly === false) {
548 let valueChanged = false;
549 if (keyToChange.value !== value) {
550 setConfigurationKeyValue(chargingStation, key, value, true);
551 valueChanged = true;
552 }
553 let triggerHeartbeatRestart = false;
554 if (
555 (keyToChange.key as OCPP16StandardParametersKey) ===
556 OCPP16StandardParametersKey.HeartBeatInterval &&
557 valueChanged
558 ) {
559 setConfigurationKeyValue(
560 chargingStation,
561 OCPP16StandardParametersKey.HeartbeatInterval,
562 value,
563 );
564 triggerHeartbeatRestart = true;
565 }
566 if (
567 (keyToChange.key as OCPP16StandardParametersKey) ===
568 OCPP16StandardParametersKey.HeartbeatInterval &&
569 valueChanged
570 ) {
571 setConfigurationKeyValue(
572 chargingStation,
573 OCPP16StandardParametersKey.HeartBeatInterval,
574 value,
575 );
576 triggerHeartbeatRestart = true;
577 }
578 if (triggerHeartbeatRestart) {
579 chargingStation.restartHeartbeat();
580 }
581 if (
582 (keyToChange.key as OCPP16StandardParametersKey) ===
583 OCPP16StandardParametersKey.WebSocketPingInterval &&
584 valueChanged
585 ) {
586 chargingStation.restartWebSocketPing();
587 }
588 if (keyToChange.reboot) {
589 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
590 }
591 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
592 }
593 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
594 }
595
596 private handleRequestSetChargingProfile(
597 chargingStation: ChargingStation,
598 commandPayload: SetChargingProfileRequest,
599 ): SetChargingProfileResponse {
600 if (
601 OCPP16ServiceUtils.checkFeatureProfile(
602 chargingStation,
603 OCPP16SupportedFeatureProfiles.SmartCharging,
604 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
605 ) === false
606 ) {
607 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
608 }
609 const { connectorId, csChargingProfiles } = commandPayload;
610 if (chargingStation.hasConnector(connectorId) === false) {
611 logger.error(
612 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
613 non existing connector id ${connectorId}`,
614 );
615 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
616 }
617 if (
618 csChargingProfiles.chargingProfilePurpose ===
619 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
620 connectorId !== 0
621 ) {
622 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
623 }
624 if (
625 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
626 (connectorId === 0 ||
627 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false)
628 ) {
629 logger.error(
630 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
631 on connector ${connectorId} without a started transaction`,
632 );
633 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
634 }
635 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles);
636 logger.debug(
637 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
638 csChargingProfiles,
639 );
640 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
641 }
642
643 private handleRequestGetCompositeSchedule(
644 chargingStation: ChargingStation,
645 commandPayload: OCPP16GetCompositeScheduleRequest,
646 ): OCPP16GetCompositeScheduleResponse {
647 if (
648 OCPP16ServiceUtils.checkFeatureProfile(
649 chargingStation,
650 OCPP16SupportedFeatureProfiles.SmartCharging,
651 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
652 ) === false
653 ) {
654 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
655 }
656 const { connectorId, duration } = commandPayload;
657 if (chargingStation.hasConnector(connectorId) === false) {
658 logger.error(
659 `${chargingStation.logPrefix()} Trying to get composite schedule to a
660 non existing connector id ${connectorId}`,
661 );
662 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
663 }
664 if (isEmptyArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)) {
665 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
666 }
667 const startDate = new Date();
668 const endDate = new Date(startDate.getTime() + secondsToMilliseconds(duration));
669 let compositeSchedule: OCPP16ChargingSchedule | undefined;
670 for (const chargingProfile of chargingStation.getConnectorStatus(connectorId)!
671 .chargingProfiles!) {
672 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
673 if (
674 isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, {
675 start: startDate,
676 end: endDate,
677 })
678 ) {
679 compositeSchedule = chargingProfile.chargingSchedule;
680 break;
681 }
682 }
683 return {
684 status: GenericStatus.Accepted,
685 scheduleStart: compositeSchedule?.startSchedule,
686 connectorId,
687 chargingSchedule: compositeSchedule,
688 };
689 }
690
691 private handleRequestClearChargingProfile(
692 chargingStation: ChargingStation,
693 commandPayload: ClearChargingProfileRequest,
694 ): ClearChargingProfileResponse {
695 if (
696 OCPP16ServiceUtils.checkFeatureProfile(
697 chargingStation,
698 OCPP16SupportedFeatureProfiles.SmartCharging,
699 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
700 ) === false
701 ) {
702 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
703 }
704 const { connectorId } = commandPayload;
705 if (chargingStation.hasConnector(connectorId!) === false) {
706 logger.error(
707 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
708 a non existing connector id ${connectorId}`,
709 );
710 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
711 }
712 if (
713 !isNullOrUndefined(connectorId) &&
714 isNotEmptyArray(chargingStation.getConnectorStatus(connectorId!)?.chargingProfiles)
715 ) {
716 chargingStation.getConnectorStatus(connectorId!)!.chargingProfiles = [];
717 logger.debug(
718 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
719 );
720 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
721 }
722 if (isNullOrUndefined(connectorId)) {
723 let clearedCP = false;
724 if (chargingStation.hasEvses) {
725 for (const evseStatus of chargingStation.evses.values()) {
726 for (const connectorStatus of evseStatus.connectors.values()) {
727 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
728 chargingStation,
729 commandPayload,
730 connectorStatus.chargingProfiles,
731 );
732 }
733 }
734 } else {
735 for (const id of chargingStation.connectors.keys()) {
736 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
737 chargingStation,
738 commandPayload,
739 chargingStation.getConnectorStatus(id)?.chargingProfiles,
740 );
741 }
742 }
743 if (clearedCP) {
744 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
745 }
746 }
747 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
748 }
749
750 private async handleRequestChangeAvailability(
751 chargingStation: ChargingStation,
752 commandPayload: OCPP16ChangeAvailabilityRequest,
753 ): Promise<OCPP16ChangeAvailabilityResponse> {
754 const { connectorId, type } = commandPayload;
755 if (chargingStation.hasConnector(connectorId) === false) {
756 logger.error(
757 `${chargingStation.logPrefix()} Trying to change the availability of a
758 non existing connector id ${connectorId.toString()}`,
759 );
760 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
761 }
762 const chargePointStatus: OCPP16ChargePointStatus =
763 type === OCPP16AvailabilityType.Operative
764 ? OCPP16ChargePointStatus.Available
765 : OCPP16ChargePointStatus.Unavailable;
766 if (connectorId === 0) {
767 let response: OCPP16ChangeAvailabilityResponse;
768 if (chargingStation.hasEvses) {
769 for (const evseStatus of chargingStation.evses.values()) {
770 response = await OCPP16ServiceUtils.changeAvailability(
771 chargingStation,
772 [...evseStatus.connectors.keys()],
773 chargePointStatus,
774 type,
775 );
776 }
777 } else {
778 response = await OCPP16ServiceUtils.changeAvailability(
779 chargingStation,
780 [...chargingStation.connectors.keys()],
781 chargePointStatus,
782 type,
783 );
784 }
785 return response!;
786 } else if (
787 connectorId > 0 &&
788 (chargingStation.isChargingStationAvailable() === true ||
789 (chargingStation.isChargingStationAvailable() === false &&
790 type === OCPP16AvailabilityType.Inoperative))
791 ) {
792 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
793 chargingStation.getConnectorStatus(connectorId)!.availability = type;
794 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
795 }
796 chargingStation.getConnectorStatus(connectorId)!.availability = type;
797 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
798 chargingStation,
799 connectorId,
800 chargePointStatus,
801 );
802 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
803 }
804 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
805 }
806
807 private async handleRequestRemoteStartTransaction(
808 chargingStation: ChargingStation,
809 commandPayload: RemoteStartTransactionRequest,
810 ): Promise<GenericResponse> {
811 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
812 if (chargingStation.hasConnector(transactionConnectorId) === false) {
813 return this.notifyRemoteStartTransactionRejected(
814 chargingStation,
815 transactionConnectorId,
816 idTag,
817 );
818 }
819 if (
820 !chargingStation.isChargingStationAvailable() ||
821 !chargingStation.isConnectorAvailable(transactionConnectorId)
822 ) {
823 return this.notifyRemoteStartTransactionRejected(
824 chargingStation,
825 transactionConnectorId,
826 idTag,
827 );
828 }
829 const remoteStartTransactionLogMsg = `
830 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
831 chargingStation.stationInfo.chargingStationId
832 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
833 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
834 chargingStation,
835 transactionConnectorId,
836 OCPP16ChargePointStatus.Preparing,
837 );
838 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
839 // Authorization check required
840 if (
841 chargingStation.getAuthorizeRemoteTxRequests() === true &&
842 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
843 ) {
844 // Authorization successful, start transaction
845 if (
846 this.setRemoteStartTransactionChargingProfile(
847 chargingStation,
848 transactionConnectorId,
849 chargingProfile!,
850 ) === true
851 ) {
852 connectorStatus.transactionRemoteStarted = true;
853 if (
854 (
855 await chargingStation.ocppRequestService.requestHandler<
856 OCPP16StartTransactionRequest,
857 OCPP16StartTransactionResponse
858 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
859 connectorId: transactionConnectorId,
860 idTag,
861 })
862 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
863 ) {
864 logger.debug(remoteStartTransactionLogMsg);
865 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
866 }
867 return this.notifyRemoteStartTransactionRejected(
868 chargingStation,
869 transactionConnectorId,
870 idTag,
871 );
872 }
873 return this.notifyRemoteStartTransactionRejected(
874 chargingStation,
875 transactionConnectorId,
876 idTag,
877 );
878 }
879 // No authorization check required, start transaction
880 if (
881 this.setRemoteStartTransactionChargingProfile(
882 chargingStation,
883 transactionConnectorId,
884 chargingProfile!,
885 ) === true
886 ) {
887 connectorStatus.transactionRemoteStarted = true;
888 if (
889 (
890 await chargingStation.ocppRequestService.requestHandler<
891 OCPP16StartTransactionRequest,
892 OCPP16StartTransactionResponse
893 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
894 connectorId: transactionConnectorId,
895 idTag,
896 })
897 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
898 ) {
899 logger.debug(remoteStartTransactionLogMsg);
900 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
901 }
902 return this.notifyRemoteStartTransactionRejected(
903 chargingStation,
904 transactionConnectorId,
905 idTag,
906 );
907 }
908 return this.notifyRemoteStartTransactionRejected(
909 chargingStation,
910 transactionConnectorId,
911 idTag,
912 );
913 }
914
915 private async notifyRemoteStartTransactionRejected(
916 chargingStation: ChargingStation,
917 connectorId: number,
918 idTag: string,
919 ): Promise<GenericResponse> {
920 if (
921 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
922 ) {
923 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
924 chargingStation,
925 connectorId,
926 OCPP16ChargePointStatus.Available,
927 );
928 }
929 logger.warn(
930 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
931 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
932 connectorId,
933 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
934 );
935 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
936 }
937
938 private setRemoteStartTransactionChargingProfile(
939 chargingStation: ChargingStation,
940 connectorId: number,
941 chargingProfile: OCPP16ChargingProfile,
942 ): boolean {
943 if (
944 chargingProfile &&
945 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
946 ) {
947 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
948 logger.debug(
949 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
950 on connector id ${connectorId}: %j`,
951 chargingProfile,
952 );
953 return true;
954 } else if (
955 chargingProfile &&
956 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
957 ) {
958 logger.warn(
959 `${chargingStation.logPrefix()} Not allowed to set ${
960 chargingProfile.chargingProfilePurpose
961 } charging profile(s) at remote start transaction`,
962 );
963 return false;
964 }
965 return true;
966 }
967
968 private async handleRequestRemoteStopTransaction(
969 chargingStation: ChargingStation,
970 commandPayload: RemoteStopTransactionRequest,
971 ): Promise<GenericResponse> {
972 const { transactionId } = commandPayload;
973 if (chargingStation.hasEvses) {
974 for (const [evseId, evseStatus] of chargingStation.evses) {
975 if (evseId > 0) {
976 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
977 if (connectorStatus.transactionId === transactionId) {
978 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
979 }
980 }
981 }
982 }
983 } else {
984 for (const connectorId of chargingStation.connectors.keys()) {
985 if (
986 connectorId > 0 &&
987 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
988 ) {
989 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
990 }
991 }
992 }
993 logger.warn(
994 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
995 ${transactionId.toString()}`,
996 );
997 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
998 }
999
1000 private handleRequestUpdateFirmware(
1001 chargingStation: ChargingStation,
1002 commandPayload: OCPP16UpdateFirmwareRequest,
1003 ): OCPP16UpdateFirmwareResponse {
1004 if (
1005 OCPP16ServiceUtils.checkFeatureProfile(
1006 chargingStation,
1007 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1008 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1009 ) === false
1010 ) {
1011 logger.warn(
1012 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1013 Cannot simulate firmware update: feature profile not supported`,
1014 );
1015 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1016 }
1017 let { retrieveDate } = commandPayload;
1018 if (
1019 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1020 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1021 ) {
1022 logger.warn(
1023 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1024 Cannot simulate firmware update: firmware update is already in progress`,
1025 );
1026 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1027 }
1028 retrieveDate = convertToDate(retrieveDate)!;
1029 const now = Date.now();
1030 if (retrieveDate?.getTime() <= now) {
1031 this.runInAsyncScope(
1032 this.updateFirmwareSimulation.bind(this) as (
1033 this: OCPP16IncomingRequestService,
1034 ...args: unknown[]
1035 ) => Promise<void>,
1036 this,
1037 chargingStation,
1038 ).catch(Constants.EMPTY_FUNCTION);
1039 } else {
1040 setTimeout(
1041 () => {
1042 this.runInAsyncScope(
1043 this.updateFirmwareSimulation.bind(this) as (
1044 this: OCPP16IncomingRequestService,
1045 ...args: unknown[]
1046 ) => Promise<void>,
1047 this,
1048 chargingStation,
1049 ).catch(Constants.EMPTY_FUNCTION);
1050 },
1051 retrieveDate?.getTime() - now,
1052 );
1053 }
1054 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1055 }
1056
1057 private async updateFirmwareSimulation(
1058 chargingStation: ChargingStation,
1059 maxDelay = 30,
1060 minDelay = 15,
1061 ): Promise<void> {
1062 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1063 return;
1064 }
1065 if (chargingStation.hasEvses) {
1066 for (const [evseId, evseStatus] of chargingStation.evses) {
1067 if (evseId > 0) {
1068 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1069 if (connectorStatus?.transactionStarted === false) {
1070 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1071 chargingStation,
1072 connectorId,
1073 OCPP16ChargePointStatus.Unavailable,
1074 );
1075 }
1076 }
1077 }
1078 }
1079 } else {
1080 for (const connectorId of chargingStation.connectors.keys()) {
1081 if (
1082 connectorId > 0 &&
1083 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1084 ) {
1085 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1086 chargingStation,
1087 connectorId,
1088 OCPP16ChargePointStatus.Unavailable,
1089 );
1090 }
1091 }
1092 }
1093 await chargingStation.ocppRequestService.requestHandler<
1094 OCPP16FirmwareStatusNotificationRequest,
1095 OCPP16FirmwareStatusNotificationResponse
1096 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1097 status: OCPP16FirmwareStatus.Downloading,
1098 });
1099 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1100 if (
1101 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1102 OCPP16FirmwareStatus.DownloadFailed
1103 ) {
1104 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1105 await chargingStation.ocppRequestService.requestHandler<
1106 OCPP16FirmwareStatusNotificationRequest,
1107 OCPP16FirmwareStatusNotificationResponse
1108 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1109 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1110 });
1111 chargingStation.stationInfo.firmwareStatus =
1112 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1113 return;
1114 }
1115 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1116 await chargingStation.ocppRequestService.requestHandler<
1117 OCPP16FirmwareStatusNotificationRequest,
1118 OCPP16FirmwareStatusNotificationResponse
1119 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1120 status: OCPP16FirmwareStatus.Downloaded,
1121 });
1122 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1123 let wasTransactionsStarted = false;
1124 let transactionsStarted: boolean;
1125 do {
1126 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1127 if (runningTransactions > 0) {
1128 const waitTime = secondsToMilliseconds(15);
1129 logger.debug(
1130 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1131 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1132 waitTime,
1133 )} before continuing firmware update simulation`,
1134 );
1135 await sleep(waitTime);
1136 transactionsStarted = true;
1137 wasTransactionsStarted = true;
1138 } else {
1139 if (chargingStation.hasEvses) {
1140 for (const [evseId, evseStatus] of chargingStation.evses) {
1141 if (evseId > 0) {
1142 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1143 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1144 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1145 chargingStation,
1146 connectorId,
1147 OCPP16ChargePointStatus.Unavailable,
1148 );
1149 }
1150 }
1151 }
1152 }
1153 } else {
1154 for (const connectorId of chargingStation.connectors.keys()) {
1155 if (
1156 connectorId > 0 &&
1157 chargingStation.getConnectorStatus(connectorId)?.status !==
1158 OCPP16ChargePointStatus.Unavailable
1159 ) {
1160 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1161 chargingStation,
1162 connectorId,
1163 OCPP16ChargePointStatus.Unavailable,
1164 );
1165 }
1166 }
1167 }
1168 transactionsStarted = false;
1169 }
1170 } while (transactionsStarted);
1171 !wasTransactionsStarted &&
1172 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1173 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1174 return;
1175 }
1176 await chargingStation.ocppRequestService.requestHandler<
1177 OCPP16FirmwareStatusNotificationRequest,
1178 OCPP16FirmwareStatusNotificationResponse
1179 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1180 status: OCPP16FirmwareStatus.Installing,
1181 });
1182 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1183 if (
1184 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1185 OCPP16FirmwareStatus.InstallationFailed
1186 ) {
1187 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1188 await chargingStation.ocppRequestService.requestHandler<
1189 OCPP16FirmwareStatusNotificationRequest,
1190 OCPP16FirmwareStatusNotificationResponse
1191 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1192 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1193 });
1194 chargingStation.stationInfo.firmwareStatus =
1195 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1196 return;
1197 }
1198 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1199 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1200 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1201 }
1202 }
1203
1204 private async handleRequestGetDiagnostics(
1205 chargingStation: ChargingStation,
1206 commandPayload: GetDiagnosticsRequest,
1207 ): Promise<GetDiagnosticsResponse> {
1208 if (
1209 OCPP16ServiceUtils.checkFeatureProfile(
1210 chargingStation,
1211 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1212 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1213 ) === false
1214 ) {
1215 logger.warn(
1216 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1217 Cannot get diagnostics: feature profile not supported`,
1218 );
1219 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1220 }
1221 const { location } = commandPayload;
1222 const uri = new URL(location);
1223 if (uri.protocol.startsWith('ftp:')) {
1224 let ftpClient: Client | undefined;
1225 try {
1226 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1227 .filter((file) => file.endsWith('.log'))
1228 .map((file) => join('./', file));
1229 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1230 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1231 ftpClient = new Client();
1232 const accessResponse = await ftpClient.access({
1233 host: uri.host,
1234 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1235 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1236 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1237 });
1238 let uploadResponse: FTPResponse | undefined;
1239 if (accessResponse.code === 220) {
1240 ftpClient.trackProgress((info) => {
1241 logger.info(
1242 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1243 info.bytes / 1024
1244 } bytes transferred from diagnostics archive ${info.name}`,
1245 );
1246 chargingStation.ocppRequestService
1247 .requestHandler<
1248 OCPP16DiagnosticsStatusNotificationRequest,
1249 OCPP16DiagnosticsStatusNotificationResponse
1250 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1251 status: OCPP16DiagnosticsStatus.Uploading,
1252 })
1253 .catch((error) => {
1254 logger.error(
1255 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1256 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1257 error,
1258 );
1259 });
1260 });
1261 uploadResponse = await ftpClient.uploadFrom(
1262 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1263 `${uri.pathname}${diagnosticsArchive}`,
1264 );
1265 if (uploadResponse.code === 226) {
1266 await chargingStation.ocppRequestService.requestHandler<
1267 OCPP16DiagnosticsStatusNotificationRequest,
1268 OCPP16DiagnosticsStatusNotificationResponse
1269 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1270 status: OCPP16DiagnosticsStatus.Uploaded,
1271 });
1272 if (ftpClient) {
1273 ftpClient.close();
1274 }
1275 return { fileName: diagnosticsArchive };
1276 }
1277 throw new OCPPError(
1278 ErrorType.GENERIC_ERROR,
1279 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1280 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1281 }`,
1282 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1283 );
1284 }
1285 throw new OCPPError(
1286 ErrorType.GENERIC_ERROR,
1287 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1288 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1289 }`,
1290 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1291 );
1292 } catch (error) {
1293 await chargingStation.ocppRequestService.requestHandler<
1294 OCPP16DiagnosticsStatusNotificationRequest,
1295 OCPP16DiagnosticsStatusNotificationResponse
1296 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1297 status: OCPP16DiagnosticsStatus.UploadFailed,
1298 });
1299 if (ftpClient) {
1300 ftpClient.close();
1301 }
1302 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1303 chargingStation,
1304 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1305 error as Error,
1306 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1307 )!;
1308 }
1309 } else {
1310 logger.error(
1311 `${chargingStation.logPrefix()} Unsupported protocol ${
1312 uri.protocol
1313 } to transfer the diagnostic logs archive`,
1314 );
1315 await chargingStation.ocppRequestService.requestHandler<
1316 OCPP16DiagnosticsStatusNotificationRequest,
1317 OCPP16DiagnosticsStatusNotificationResponse
1318 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1319 status: OCPP16DiagnosticsStatus.UploadFailed,
1320 });
1321 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1322 }
1323 }
1324
1325 private handleRequestTriggerMessage(
1326 chargingStation: ChargingStation,
1327 commandPayload: OCPP16TriggerMessageRequest,
1328 ): OCPP16TriggerMessageResponse {
1329 const { requestedMessage, connectorId } = commandPayload;
1330 if (
1331 !OCPP16ServiceUtils.checkFeatureProfile(
1332 chargingStation,
1333 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1334 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1335 ) ||
1336 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1337 ) {
1338 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1339 }
1340 if (
1341 !OCPP16ServiceUtils.isConnectorIdValid(
1342 chargingStation,
1343 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1344 connectorId!,
1345 )
1346 ) {
1347 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1348 }
1349 try {
1350 switch (requestedMessage) {
1351 case OCPP16MessageTrigger.BootNotification:
1352 setTimeout(() => {
1353 chargingStation.ocppRequestService
1354 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1355 chargingStation,
1356 OCPP16RequestCommand.BOOT_NOTIFICATION,
1357 chargingStation.bootNotificationRequest,
1358 { skipBufferingOnError: true, triggerMessage: true },
1359 )
1360 .then((response) => {
1361 chargingStation.bootNotificationResponse = response;
1362 })
1363 .catch(Constants.EMPTY_FUNCTION);
1364 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1365 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1366 case OCPP16MessageTrigger.Heartbeat:
1367 setTimeout(() => {
1368 chargingStation.ocppRequestService
1369 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1370 chargingStation,
1371 OCPP16RequestCommand.HEARTBEAT,
1372 null,
1373 {
1374 triggerMessage: true,
1375 },
1376 )
1377 .catch(Constants.EMPTY_FUNCTION);
1378 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1379 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1380 case OCPP16MessageTrigger.StatusNotification:
1381 setTimeout(() => {
1382 if (!isNullOrUndefined(connectorId)) {
1383 chargingStation.ocppRequestService
1384 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1385 chargingStation,
1386 OCPP16RequestCommand.STATUS_NOTIFICATION,
1387 {
1388 connectorId,
1389 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1390 status: chargingStation.getConnectorStatus(connectorId!)?.status,
1391 },
1392 {
1393 triggerMessage: true,
1394 },
1395 )
1396 .catch(Constants.EMPTY_FUNCTION);
1397 } else {
1398 // eslint-disable-next-line no-lonely-if
1399 if (chargingStation.hasEvses) {
1400 for (const evseStatus of chargingStation.evses.values()) {
1401 for (const [id, connectorStatus] of evseStatus.connectors) {
1402 chargingStation.ocppRequestService
1403 .requestHandler<
1404 OCPP16StatusNotificationRequest,
1405 OCPP16StatusNotificationResponse
1406 >(
1407 chargingStation,
1408 OCPP16RequestCommand.STATUS_NOTIFICATION,
1409 {
1410 connectorId: id,
1411 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1412 status: connectorStatus.status,
1413 },
1414 {
1415 triggerMessage: true,
1416 },
1417 )
1418 .catch(Constants.EMPTY_FUNCTION);
1419 }
1420 }
1421 } else {
1422 for (const id of chargingStation.connectors.keys()) {
1423 chargingStation.ocppRequestService
1424 .requestHandler<
1425 OCPP16StatusNotificationRequest,
1426 OCPP16StatusNotificationResponse
1427 >(
1428 chargingStation,
1429 OCPP16RequestCommand.STATUS_NOTIFICATION,
1430 {
1431 connectorId: id,
1432 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1433 status: chargingStation.getConnectorStatus(id)?.status,
1434 },
1435 {
1436 triggerMessage: true,
1437 },
1438 )
1439 .catch(Constants.EMPTY_FUNCTION);
1440 }
1441 }
1442 }
1443 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1444 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1445 default:
1446 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1447 }
1448 } catch (error) {
1449 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1450 chargingStation,
1451 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1452 error as Error,
1453 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1454 )!;
1455 }
1456 }
1457
1458 private handleRequestDataTransfer(
1459 chargingStation: ChargingStation,
1460 commandPayload: OCPP16DataTransferRequest,
1461 ): OCPP16DataTransferResponse {
1462 const { vendorId } = commandPayload;
1463 try {
1464 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1465 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1466 }
1467 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1468 } catch (error) {
1469 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1470 chargingStation,
1471 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1472 error as Error,
1473 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1474 )!;
1475 }
1476 }
1477
1478 private async handleRequestReserveNow(
1479 chargingStation: ChargingStation,
1480 commandPayload: OCPP16ReserveNowRequest,
1481 ): Promise<OCPP16ReserveNowResponse> {
1482 if (
1483 !OCPP16ServiceUtils.checkFeatureProfile(
1484 chargingStation,
1485 OCPP16SupportedFeatureProfiles.Reservation,
1486 OCPP16IncomingRequestCommand.RESERVE_NOW,
1487 )
1488 ) {
1489 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1490 }
1491 const { reservationId, idTag, connectorId } = commandPayload;
1492 let response: OCPP16ReserveNowResponse;
1493 try {
1494 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1495 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1496 }
1497 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1498 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1499 }
1500 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1501 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1502 }
1503 await removeExpiredReservations(chargingStation);
1504 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1505 case OCPP16ChargePointStatus.Faulted:
1506 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1507 break;
1508 case OCPP16ChargePointStatus.Preparing:
1509 case OCPP16ChargePointStatus.Charging:
1510 case OCPP16ChargePointStatus.SuspendedEV:
1511 case OCPP16ChargePointStatus.SuspendedEVSE:
1512 case OCPP16ChargePointStatus.Finishing:
1513 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1514 break;
1515 case OCPP16ChargePointStatus.Unavailable:
1516 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1517 break;
1518 case OCPP16ChargePointStatus.Reserved:
1519 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1520 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1521 break;
1522 }
1523 // eslint-disable-next-line no-fallthrough
1524 default:
1525 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1526 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1527 break;
1528 }
1529 await chargingStation.addReservation({
1530 id: commandPayload.reservationId,
1531 ...commandPayload,
1532 });
1533 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1534 break;
1535 }
1536 return response;
1537 } catch (error) {
1538 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1539 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1540 chargingStation,
1541 OCPP16IncomingRequestCommand.RESERVE_NOW,
1542 error as Error,
1543 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1544 )!;
1545 }
1546 }
1547
1548 private async handleRequestCancelReservation(
1549 chargingStation: ChargingStation,
1550 commandPayload: OCPP16CancelReservationRequest,
1551 ): Promise<GenericResponse> {
1552 if (
1553 !OCPP16ServiceUtils.checkFeatureProfile(
1554 chargingStation,
1555 OCPP16SupportedFeatureProfiles.Reservation,
1556 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1557 )
1558 ) {
1559 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1560 }
1561 try {
1562 const { reservationId } = commandPayload;
1563 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1564 if (isUndefined(reservation)) {
1565 logger.debug(
1566 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1567 does not exist on charging station`,
1568 );
1569 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1570 }
1571 await chargingStation.removeReservation(
1572 reservation!,
1573 ReservationTerminationReason.RESERVATION_CANCELED,
1574 );
1575 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1576 } catch (error) {
1577 return this.handleIncomingRequestError<GenericResponse>(
1578 chargingStation,
1579 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1580 error as Error,
1581 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1582 )!;
1583 }
1584 }
1585 }