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