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