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