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