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