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