fix: typo in condition check at remote start transaction
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import { createWriteStream, readdirSync } from 'node:fs';
4 import { dirname, join, resolve } from 'node:path';
5 import { URL, fileURLToPath } from 'node:url';
6
7 import type { JSONSchemaType } from 'ajv';
8 import { Client, type FTPResponse } from 'basic-ftp';
9 import { 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 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
959 ) === true) ||
960 !chargingProfile
961 ) {
962 connectorStatus.transactionRemoteStarted = true;
963 if (
964 (
965 await chargingStation.ocppRequestService.requestHandler<
966 OCPP16StartTransactionRequest,
967 OCPP16StartTransactionResponse
968 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
969 connectorId: transactionConnectorId,
970 idTag,
971 })
972 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
973 ) {
974 logger.debug(remoteStartTransactionLogMsg);
975 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
976 }
977 return this.notifyRemoteStartTransactionRejected(
978 chargingStation,
979 transactionConnectorId,
980 idTag,
981 );
982 }
983 return this.notifyRemoteStartTransactionRejected(
984 chargingStation,
985 transactionConnectorId,
986 idTag,
987 );
988 }
989 // No authorization check required, start transaction
990 if (
991 (chargingProfile &&
992 this.setRemoteStartTransactionChargingProfile(
993 chargingStation,
994 transactionConnectorId,
995 chargingProfile,
996 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
997 ) === true) ||
998 !chargingProfile
999 ) {
1000 connectorStatus.transactionRemoteStarted = true;
1001 if (
1002 (
1003 await chargingStation.ocppRequestService.requestHandler<
1004 OCPP16StartTransactionRequest,
1005 OCPP16StartTransactionResponse
1006 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
1007 connectorId: transactionConnectorId,
1008 idTag,
1009 })
1010 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
1011 ) {
1012 logger.debug(remoteStartTransactionLogMsg);
1013 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1014 }
1015 return this.notifyRemoteStartTransactionRejected(
1016 chargingStation,
1017 transactionConnectorId,
1018 idTag,
1019 );
1020 }
1021 return this.notifyRemoteStartTransactionRejected(
1022 chargingStation,
1023 transactionConnectorId,
1024 idTag,
1025 );
1026 }
1027
1028 private async notifyRemoteStartTransactionRejected(
1029 chargingStation: ChargingStation,
1030 connectorId: number,
1031 idTag: string,
1032 ): Promise<GenericResponse> {
1033 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
1034 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
1035 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1036 chargingStation,
1037 connectorId,
1038 OCPP16ChargePointStatus.Available,
1039 );
1040 }
1041 logger.warn(
1042 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
1043 ${connectorId}, idTag '${idTag}', availability '${connectorStatus?.availability}', status '${connectorStatus?.status}'`,
1044 );
1045 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1046 }
1047
1048 private setRemoteStartTransactionChargingProfile(
1049 chargingStation: ChargingStation,
1050 connectorId: number,
1051 chargingProfile: OCPP16ChargingProfile,
1052 ): boolean {
1053 if (chargingProfile?.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1054 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
1055 logger.debug(
1056 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1057 on connector id ${connectorId}: %j`,
1058 chargingProfile,
1059 );
1060 return true;
1061 }
1062 logger.warn(
1063 `${chargingStation.logPrefix()} Not allowed to set ${
1064 chargingProfile.chargingProfilePurpose
1065 } charging profile(s) at remote start transaction`,
1066 );
1067 return false;
1068 }
1069
1070 private async handleRequestRemoteStopTransaction(
1071 chargingStation: ChargingStation,
1072 commandPayload: RemoteStopTransactionRequest,
1073 ): Promise<GenericResponse> {
1074 const { transactionId } = commandPayload;
1075 if (chargingStation.hasEvses) {
1076 for (const [evseId, evseStatus] of chargingStation.evses) {
1077 if (evseId > 0) {
1078 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1079 if (connectorStatus.transactionId === transactionId) {
1080 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1081 }
1082 }
1083 }
1084 }
1085 } else {
1086 for (const connectorId of chargingStation.connectors.keys()) {
1087 if (
1088 connectorId > 0 &&
1089 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1090 ) {
1091 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1092 }
1093 }
1094 }
1095 logger.warn(
1096 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
1097 ${transactionId}`,
1098 );
1099 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1100 }
1101
1102 private handleRequestUpdateFirmware(
1103 chargingStation: ChargingStation,
1104 commandPayload: OCPP16UpdateFirmwareRequest,
1105 ): OCPP16UpdateFirmwareResponse {
1106 if (
1107 OCPP16ServiceUtils.checkFeatureProfile(
1108 chargingStation,
1109 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1110 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1111 ) === false
1112 ) {
1113 logger.warn(
1114 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1115 Cannot simulate firmware update: feature profile not supported`,
1116 );
1117 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1118 }
1119 let { retrieveDate } = commandPayload;
1120 if (
1121 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1122 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1123 ) {
1124 logger.warn(
1125 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1126 Cannot simulate firmware update: firmware update is already in progress`,
1127 );
1128 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1129 }
1130 retrieveDate = convertToDate(retrieveDate)!;
1131 const now = Date.now();
1132 if (retrieveDate?.getTime() <= now) {
1133 this.runInAsyncScope(
1134 this.updateFirmwareSimulation.bind(this) as (
1135 this: OCPP16IncomingRequestService,
1136 ...args: unknown[]
1137 ) => Promise<void>,
1138 this,
1139 chargingStation,
1140 ).catch(Constants.EMPTY_FUNCTION);
1141 } else {
1142 setTimeout(
1143 () => {
1144 this.runInAsyncScope(
1145 this.updateFirmwareSimulation.bind(this) as (
1146 this: OCPP16IncomingRequestService,
1147 ...args: unknown[]
1148 ) => Promise<void>,
1149 this,
1150 chargingStation,
1151 ).catch(Constants.EMPTY_FUNCTION);
1152 },
1153 retrieveDate?.getTime() - now,
1154 );
1155 }
1156 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1157 }
1158
1159 private async updateFirmwareSimulation(
1160 chargingStation: ChargingStation,
1161 maxDelay = 30,
1162 minDelay = 15,
1163 ): Promise<void> {
1164 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1165 return;
1166 }
1167 if (chargingStation.hasEvses) {
1168 for (const [evseId, evseStatus] of chargingStation.evses) {
1169 if (evseId > 0) {
1170 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1171 if (connectorStatus?.transactionStarted === false) {
1172 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1173 chargingStation,
1174 connectorId,
1175 OCPP16ChargePointStatus.Unavailable,
1176 );
1177 }
1178 }
1179 }
1180 }
1181 } else {
1182 for (const connectorId of chargingStation.connectors.keys()) {
1183 if (
1184 connectorId > 0 &&
1185 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1186 ) {
1187 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1188 chargingStation,
1189 connectorId,
1190 OCPP16ChargePointStatus.Unavailable,
1191 );
1192 }
1193 }
1194 }
1195 await chargingStation.ocppRequestService.requestHandler<
1196 OCPP16FirmwareStatusNotificationRequest,
1197 OCPP16FirmwareStatusNotificationResponse
1198 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1199 status: OCPP16FirmwareStatus.Downloading,
1200 });
1201 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1202 if (
1203 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1204 OCPP16FirmwareStatus.DownloadFailed
1205 ) {
1206 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1207 await chargingStation.ocppRequestService.requestHandler<
1208 OCPP16FirmwareStatusNotificationRequest,
1209 OCPP16FirmwareStatusNotificationResponse
1210 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1211 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1212 });
1213 chargingStation.stationInfo.firmwareStatus =
1214 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1215 return;
1216 }
1217 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1218 await chargingStation.ocppRequestService.requestHandler<
1219 OCPP16FirmwareStatusNotificationRequest,
1220 OCPP16FirmwareStatusNotificationResponse
1221 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1222 status: OCPP16FirmwareStatus.Downloaded,
1223 });
1224 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1225 let wasTransactionsStarted = false;
1226 let transactionsStarted: boolean;
1227 do {
1228 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1229 if (runningTransactions > 0) {
1230 const waitTime = secondsToMilliseconds(15);
1231 logger.debug(
1232 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1233 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1234 waitTime,
1235 )} before continuing firmware update simulation`,
1236 );
1237 await sleep(waitTime);
1238 transactionsStarted = true;
1239 wasTransactionsStarted = true;
1240 } else {
1241 if (chargingStation.hasEvses) {
1242 for (const [evseId, evseStatus] of chargingStation.evses) {
1243 if (evseId > 0) {
1244 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1245 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1246 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1247 chargingStation,
1248 connectorId,
1249 OCPP16ChargePointStatus.Unavailable,
1250 );
1251 }
1252 }
1253 }
1254 }
1255 } else {
1256 for (const connectorId of chargingStation.connectors.keys()) {
1257 if (
1258 connectorId > 0 &&
1259 chargingStation.getConnectorStatus(connectorId)?.status !==
1260 OCPP16ChargePointStatus.Unavailable
1261 ) {
1262 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1263 chargingStation,
1264 connectorId,
1265 OCPP16ChargePointStatus.Unavailable,
1266 );
1267 }
1268 }
1269 }
1270 transactionsStarted = false;
1271 }
1272 } while (transactionsStarted);
1273 !wasTransactionsStarted &&
1274 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1275 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1276 return;
1277 }
1278 await chargingStation.ocppRequestService.requestHandler<
1279 OCPP16FirmwareStatusNotificationRequest,
1280 OCPP16FirmwareStatusNotificationResponse
1281 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1282 status: OCPP16FirmwareStatus.Installing,
1283 });
1284 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1285 if (
1286 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1287 OCPP16FirmwareStatus.InstallationFailed
1288 ) {
1289 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1290 await chargingStation.ocppRequestService.requestHandler<
1291 OCPP16FirmwareStatusNotificationRequest,
1292 OCPP16FirmwareStatusNotificationResponse
1293 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1294 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1295 });
1296 chargingStation.stationInfo.firmwareStatus =
1297 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1298 return;
1299 }
1300 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1301 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1302 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1303 }
1304 }
1305
1306 private async handleRequestGetDiagnostics(
1307 chargingStation: ChargingStation,
1308 commandPayload: GetDiagnosticsRequest,
1309 ): Promise<GetDiagnosticsResponse> {
1310 if (
1311 OCPP16ServiceUtils.checkFeatureProfile(
1312 chargingStation,
1313 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1314 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1315 ) === false
1316 ) {
1317 logger.warn(
1318 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1319 Cannot get diagnostics: feature profile not supported`,
1320 );
1321 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1322 }
1323 const { location } = commandPayload;
1324 const uri = new URL(location);
1325 if (uri.protocol.startsWith('ftp:')) {
1326 let ftpClient: Client | undefined;
1327 try {
1328 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1329 .filter((file) => file.endsWith('.log'))
1330 .map((file) => join('./', file));
1331 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1332 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1333 ftpClient = new Client();
1334 const accessResponse = await ftpClient.access({
1335 host: uri.host,
1336 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1337 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1338 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1339 });
1340 let uploadResponse: FTPResponse | undefined;
1341 if (accessResponse.code === 220) {
1342 ftpClient.trackProgress((info) => {
1343 logger.info(
1344 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1345 info.bytes / 1024
1346 } bytes transferred from diagnostics archive ${info.name}`,
1347 );
1348 chargingStation.ocppRequestService
1349 .requestHandler<
1350 OCPP16DiagnosticsStatusNotificationRequest,
1351 OCPP16DiagnosticsStatusNotificationResponse
1352 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1353 status: OCPP16DiagnosticsStatus.Uploading,
1354 })
1355 .catch((error) => {
1356 logger.error(
1357 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1358 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1359 error,
1360 );
1361 });
1362 });
1363 uploadResponse = await ftpClient.uploadFrom(
1364 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1365 `${uri.pathname}${diagnosticsArchive}`,
1366 );
1367 if (uploadResponse.code === 226) {
1368 await chargingStation.ocppRequestService.requestHandler<
1369 OCPP16DiagnosticsStatusNotificationRequest,
1370 OCPP16DiagnosticsStatusNotificationResponse
1371 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1372 status: OCPP16DiagnosticsStatus.Uploaded,
1373 });
1374 if (ftpClient) {
1375 ftpClient.close();
1376 }
1377 return { fileName: diagnosticsArchive };
1378 }
1379 throw new OCPPError(
1380 ErrorType.GENERIC_ERROR,
1381 `Diagnostics transfer failed with error code ${accessResponse.code}${
1382 uploadResponse?.code && `|${uploadResponse?.code}`
1383 }`,
1384 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1385 );
1386 }
1387 throw new OCPPError(
1388 ErrorType.GENERIC_ERROR,
1389 `Diagnostics transfer failed with error code ${accessResponse.code}${
1390 uploadResponse?.code && `|${uploadResponse?.code}`
1391 }`,
1392 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1393 );
1394 } catch (error) {
1395 await chargingStation.ocppRequestService.requestHandler<
1396 OCPP16DiagnosticsStatusNotificationRequest,
1397 OCPP16DiagnosticsStatusNotificationResponse
1398 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1399 status: OCPP16DiagnosticsStatus.UploadFailed,
1400 });
1401 if (ftpClient) {
1402 ftpClient.close();
1403 }
1404 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1405 chargingStation,
1406 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1407 error as Error,
1408 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1409 )!;
1410 }
1411 } else {
1412 logger.error(
1413 `${chargingStation.logPrefix()} Unsupported protocol ${
1414 uri.protocol
1415 } to transfer the diagnostic logs archive`,
1416 );
1417 await chargingStation.ocppRequestService.requestHandler<
1418 OCPP16DiagnosticsStatusNotificationRequest,
1419 OCPP16DiagnosticsStatusNotificationResponse
1420 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1421 status: OCPP16DiagnosticsStatus.UploadFailed,
1422 });
1423 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1424 }
1425 }
1426
1427 private handleRequestTriggerMessage(
1428 chargingStation: ChargingStation,
1429 commandPayload: OCPP16TriggerMessageRequest,
1430 ): OCPP16TriggerMessageResponse {
1431 const { requestedMessage, connectorId } = commandPayload;
1432 if (
1433 !OCPP16ServiceUtils.checkFeatureProfile(
1434 chargingStation,
1435 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1436 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1437 ) ||
1438 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1439 ) {
1440 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1441 }
1442 if (
1443 !OCPP16ServiceUtils.isConnectorIdValid(
1444 chargingStation,
1445 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1446 connectorId!,
1447 )
1448 ) {
1449 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1450 }
1451 try {
1452 switch (requestedMessage) {
1453 case OCPP16MessageTrigger.BootNotification:
1454 setTimeout(() => {
1455 chargingStation.ocppRequestService
1456 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1457 chargingStation,
1458 OCPP16RequestCommand.BOOT_NOTIFICATION,
1459 chargingStation.bootNotificationRequest,
1460 { skipBufferingOnError: true, triggerMessage: true },
1461 )
1462 .then((response) => {
1463 chargingStation.bootNotificationResponse = response;
1464 })
1465 .catch(Constants.EMPTY_FUNCTION);
1466 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1467 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1468 case OCPP16MessageTrigger.Heartbeat:
1469 setTimeout(() => {
1470 chargingStation.ocppRequestService
1471 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1472 chargingStation,
1473 OCPP16RequestCommand.HEARTBEAT,
1474 null,
1475 {
1476 triggerMessage: true,
1477 },
1478 )
1479 .catch(Constants.EMPTY_FUNCTION);
1480 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1481 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1482 case OCPP16MessageTrigger.StatusNotification:
1483 setTimeout(() => {
1484 if (!isNullOrUndefined(connectorId)) {
1485 chargingStation.ocppRequestService
1486 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1487 chargingStation,
1488 OCPP16RequestCommand.STATUS_NOTIFICATION,
1489 {
1490 connectorId,
1491 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1492 status: chargingStation.getConnectorStatus(connectorId!)?.status,
1493 },
1494 {
1495 triggerMessage: true,
1496 },
1497 )
1498 .catch(Constants.EMPTY_FUNCTION);
1499 } else {
1500 // eslint-disable-next-line no-lonely-if
1501 if (chargingStation.hasEvses) {
1502 for (const evseStatus of chargingStation.evses.values()) {
1503 for (const [id, connectorStatus] of evseStatus.connectors) {
1504 chargingStation.ocppRequestService
1505 .requestHandler<
1506 OCPP16StatusNotificationRequest,
1507 OCPP16StatusNotificationResponse
1508 >(
1509 chargingStation,
1510 OCPP16RequestCommand.STATUS_NOTIFICATION,
1511 {
1512 connectorId: id,
1513 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1514 status: connectorStatus.status,
1515 },
1516 {
1517 triggerMessage: true,
1518 },
1519 )
1520 .catch(Constants.EMPTY_FUNCTION);
1521 }
1522 }
1523 } else {
1524 for (const id of chargingStation.connectors.keys()) {
1525 chargingStation.ocppRequestService
1526 .requestHandler<
1527 OCPP16StatusNotificationRequest,
1528 OCPP16StatusNotificationResponse
1529 >(
1530 chargingStation,
1531 OCPP16RequestCommand.STATUS_NOTIFICATION,
1532 {
1533 connectorId: id,
1534 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1535 status: chargingStation.getConnectorStatus(id)?.status,
1536 },
1537 {
1538 triggerMessage: true,
1539 },
1540 )
1541 .catch(Constants.EMPTY_FUNCTION);
1542 }
1543 }
1544 }
1545 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1546 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1547 default:
1548 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1549 }
1550 } catch (error) {
1551 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1552 chargingStation,
1553 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1554 error as Error,
1555 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1556 )!;
1557 }
1558 }
1559
1560 private handleRequestDataTransfer(
1561 chargingStation: ChargingStation,
1562 commandPayload: OCPP16DataTransferRequest,
1563 ): OCPP16DataTransferResponse {
1564 const { vendorId } = commandPayload;
1565 try {
1566 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1567 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1568 }
1569 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1570 } catch (error) {
1571 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1572 chargingStation,
1573 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1574 error as Error,
1575 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1576 )!;
1577 }
1578 }
1579
1580 private async handleRequestReserveNow(
1581 chargingStation: ChargingStation,
1582 commandPayload: OCPP16ReserveNowRequest,
1583 ): Promise<OCPP16ReserveNowResponse> {
1584 if (
1585 !OCPP16ServiceUtils.checkFeatureProfile(
1586 chargingStation,
1587 OCPP16SupportedFeatureProfiles.Reservation,
1588 OCPP16IncomingRequestCommand.RESERVE_NOW,
1589 )
1590 ) {
1591 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1592 }
1593 const { reservationId, idTag, connectorId } = commandPayload;
1594 let response: OCPP16ReserveNowResponse;
1595 try {
1596 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1597 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1598 }
1599 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1600 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1601 }
1602 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1603 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1604 }
1605 await removeExpiredReservations(chargingStation);
1606 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1607 case OCPP16ChargePointStatus.Faulted:
1608 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1609 break;
1610 case OCPP16ChargePointStatus.Preparing:
1611 case OCPP16ChargePointStatus.Charging:
1612 case OCPP16ChargePointStatus.SuspendedEV:
1613 case OCPP16ChargePointStatus.SuspendedEVSE:
1614 case OCPP16ChargePointStatus.Finishing:
1615 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1616 break;
1617 case OCPP16ChargePointStatus.Unavailable:
1618 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1619 break;
1620 case OCPP16ChargePointStatus.Reserved:
1621 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1622 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1623 break;
1624 }
1625 // eslint-disable-next-line no-fallthrough
1626 default:
1627 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1628 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1629 break;
1630 }
1631 await chargingStation.addReservation({
1632 id: commandPayload.reservationId,
1633 ...commandPayload,
1634 });
1635 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1636 break;
1637 }
1638 return response;
1639 } catch (error) {
1640 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1641 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1642 chargingStation,
1643 OCPP16IncomingRequestCommand.RESERVE_NOW,
1644 error as Error,
1645 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1646 )!;
1647 }
1648 }
1649
1650 private async handleRequestCancelReservation(
1651 chargingStation: ChargingStation,
1652 commandPayload: OCPP16CancelReservationRequest,
1653 ): Promise<GenericResponse> {
1654 if (
1655 !OCPP16ServiceUtils.checkFeatureProfile(
1656 chargingStation,
1657 OCPP16SupportedFeatureProfiles.Reservation,
1658 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1659 )
1660 ) {
1661 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1662 }
1663 try {
1664 const { reservationId } = commandPayload;
1665 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1666 if (isUndefined(reservation)) {
1667 logger.debug(
1668 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1669 does not exist on charging station`,
1670 );
1671 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1672 }
1673 await chargingStation.removeReservation(
1674 reservation!,
1675 ReservationTerminationReason.RESERVATION_CANCELED,
1676 );
1677 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1678 } catch (error) {
1679 return this.handleIncomingRequestError<GenericResponse>(
1680 chargingStation,
1681 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1682 error as Error,
1683 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1684 )!;
1685 }
1686 }
1687 }