feat: improve get composite schedule
[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 endDate = addSeconds(startDate, duration);
681 let compositeSchedule: OCPP16ChargingSchedule | undefined;
682 for (const chargingProfile of chargingStation.getConnectorStatus(connectorId)!
683 .chargingProfiles!) {
684 if (
685 compositeSchedule?.chargingRateUnit &&
686 compositeSchedule.chargingRateUnit !== chargingProfile.chargingSchedule.chargingRateUnit
687 ) {
688 logger.error(
689 `${chargingStation.logPrefix()} Building composite schedule with different charging rate units is not yet supported, skipping charging profile id ${
690 chargingProfile.chargingProfileId
691 }`,
692 );
693 continue;
694 }
695 if (
696 isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, {
697 start: startDate,
698 end: endDate,
699 }) &&
700 isWithinInterval(
701 addSeconds(
702 chargingProfile.chargingSchedule.startSchedule!,
703 chargingProfile.chargingSchedule.duration!,
704 ),
705 {
706 start: startDate,
707 end: endDate,
708 },
709 )
710 ) {
711 compositeSchedule = {
712 startSchedule: max([
713 compositeSchedule?.startSchedule ?? startDate,
714 chargingProfile.chargingSchedule.startSchedule!,
715 ]),
716 duration: Math.max(
717 compositeSchedule?.duration ?? -Infinity,
718 chargingProfile.chargingSchedule.duration!,
719 ),
720 chargingRateUnit: chargingProfile.chargingSchedule.chargingRateUnit,
721 ...(compositeSchedule?.chargingSchedulePeriod === undefined
722 ? { chargingSchedulePeriod: [] }
723 : {
724 chargingSchedulePeriod: compositeSchedule.chargingSchedulePeriod.concat(
725 ...chargingProfile.chargingSchedule.chargingSchedulePeriod,
726 ),
727 }),
728 ...(chargingProfile.chargingSchedule.minChargeRate && {
729 minChargeRate: Math.min(
730 compositeSchedule?.minChargeRate ?? Infinity,
731 chargingProfile.chargingSchedule.minChargeRate,
732 ),
733 }),
734 };
735 }
736 }
737 return {
738 status: GenericStatus.Accepted,
739 scheduleStart: compositeSchedule?.startSchedule,
740 connectorId,
741 chargingSchedule: compositeSchedule,
742 };
743 }
744
745 private handleRequestClearChargingProfile(
746 chargingStation: ChargingStation,
747 commandPayload: ClearChargingProfileRequest,
748 ): ClearChargingProfileResponse {
749 if (
750 OCPP16ServiceUtils.checkFeatureProfile(
751 chargingStation,
752 OCPP16SupportedFeatureProfiles.SmartCharging,
753 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
754 ) === false
755 ) {
756 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
757 }
758 const { connectorId } = commandPayload;
759 if (chargingStation.hasConnector(connectorId!) === false) {
760 logger.error(
761 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
762 a non existing connector id ${connectorId}`,
763 );
764 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
765 }
766 if (
767 !isNullOrUndefined(connectorId) &&
768 isNotEmptyArray(chargingStation.getConnectorStatus(connectorId!)?.chargingProfiles)
769 ) {
770 chargingStation.getConnectorStatus(connectorId!)!.chargingProfiles = [];
771 logger.debug(
772 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`,
773 );
774 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
775 }
776 if (isNullOrUndefined(connectorId)) {
777 let clearedCP = false;
778 if (chargingStation.hasEvses) {
779 for (const evseStatus of chargingStation.evses.values()) {
780 for (const connectorStatus of evseStatus.connectors.values()) {
781 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
782 chargingStation,
783 commandPayload,
784 connectorStatus.chargingProfiles,
785 );
786 }
787 }
788 } else {
789 for (const id of chargingStation.connectors.keys()) {
790 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
791 chargingStation,
792 commandPayload,
793 chargingStation.getConnectorStatus(id)?.chargingProfiles,
794 );
795 }
796 }
797 if (clearedCP) {
798 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
799 }
800 }
801 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
802 }
803
804 private async handleRequestChangeAvailability(
805 chargingStation: ChargingStation,
806 commandPayload: OCPP16ChangeAvailabilityRequest,
807 ): Promise<OCPP16ChangeAvailabilityResponse> {
808 const { connectorId, type } = commandPayload;
809 if (chargingStation.hasConnector(connectorId) === false) {
810 logger.error(
811 `${chargingStation.logPrefix()} Trying to change the availability of a
812 non existing connector id ${connectorId.toString()}`,
813 );
814 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
815 }
816 const chargePointStatus: OCPP16ChargePointStatus =
817 type === OCPP16AvailabilityType.Operative
818 ? OCPP16ChargePointStatus.Available
819 : OCPP16ChargePointStatus.Unavailable;
820 if (connectorId === 0) {
821 let response: OCPP16ChangeAvailabilityResponse;
822 if (chargingStation.hasEvses) {
823 for (const evseStatus of chargingStation.evses.values()) {
824 response = await OCPP16ServiceUtils.changeAvailability(
825 chargingStation,
826 [...evseStatus.connectors.keys()],
827 chargePointStatus,
828 type,
829 );
830 }
831 } else {
832 response = await OCPP16ServiceUtils.changeAvailability(
833 chargingStation,
834 [...chargingStation.connectors.keys()],
835 chargePointStatus,
836 type,
837 );
838 }
839 return response!;
840 } else if (
841 connectorId > 0 &&
842 (chargingStation.isChargingStationAvailable() === true ||
843 (chargingStation.isChargingStationAvailable() === false &&
844 type === OCPP16AvailabilityType.Inoperative))
845 ) {
846 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
847 chargingStation.getConnectorStatus(connectorId)!.availability = type;
848 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
849 }
850 chargingStation.getConnectorStatus(connectorId)!.availability = type;
851 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
852 chargingStation,
853 connectorId,
854 chargePointStatus,
855 );
856 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
857 }
858 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
859 }
860
861 private async handleRequestRemoteStartTransaction(
862 chargingStation: ChargingStation,
863 commandPayload: RemoteStartTransactionRequest,
864 ): Promise<GenericResponse> {
865 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
866 if (chargingStation.hasConnector(transactionConnectorId) === false) {
867 return this.notifyRemoteStartTransactionRejected(
868 chargingStation,
869 transactionConnectorId,
870 idTag,
871 );
872 }
873 if (
874 !chargingStation.isChargingStationAvailable() ||
875 !chargingStation.isConnectorAvailable(transactionConnectorId)
876 ) {
877 return this.notifyRemoteStartTransactionRejected(
878 chargingStation,
879 transactionConnectorId,
880 idTag,
881 );
882 }
883 const remoteStartTransactionLogMsg = `
884 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
885 chargingStation.stationInfo.chargingStationId
886 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
887 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
888 chargingStation,
889 transactionConnectorId,
890 OCPP16ChargePointStatus.Preparing,
891 );
892 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
893 // Authorization check required
894 if (
895 chargingStation.getAuthorizeRemoteTxRequests() === true &&
896 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
897 ) {
898 // Authorization successful, start transaction
899 if (
900 this.setRemoteStartTransactionChargingProfile(
901 chargingStation,
902 transactionConnectorId,
903 chargingProfile!,
904 ) === true
905 ) {
906 connectorStatus.transactionRemoteStarted = true;
907 if (
908 (
909 await chargingStation.ocppRequestService.requestHandler<
910 OCPP16StartTransactionRequest,
911 OCPP16StartTransactionResponse
912 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
913 connectorId: transactionConnectorId,
914 idTag,
915 })
916 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
917 ) {
918 logger.debug(remoteStartTransactionLogMsg);
919 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
920 }
921 return this.notifyRemoteStartTransactionRejected(
922 chargingStation,
923 transactionConnectorId,
924 idTag,
925 );
926 }
927 return this.notifyRemoteStartTransactionRejected(
928 chargingStation,
929 transactionConnectorId,
930 idTag,
931 );
932 }
933 // No authorization check required, start transaction
934 if (
935 this.setRemoteStartTransactionChargingProfile(
936 chargingStation,
937 transactionConnectorId,
938 chargingProfile!,
939 ) === true
940 ) {
941 connectorStatus.transactionRemoteStarted = true;
942 if (
943 (
944 await chargingStation.ocppRequestService.requestHandler<
945 OCPP16StartTransactionRequest,
946 OCPP16StartTransactionResponse
947 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
948 connectorId: transactionConnectorId,
949 idTag,
950 })
951 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
952 ) {
953 logger.debug(remoteStartTransactionLogMsg);
954 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
955 }
956 return this.notifyRemoteStartTransactionRejected(
957 chargingStation,
958 transactionConnectorId,
959 idTag,
960 );
961 }
962 return this.notifyRemoteStartTransactionRejected(
963 chargingStation,
964 transactionConnectorId,
965 idTag,
966 );
967 }
968
969 private async notifyRemoteStartTransactionRejected(
970 chargingStation: ChargingStation,
971 connectorId: number,
972 idTag: string,
973 ): Promise<GenericResponse> {
974 if (
975 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
976 ) {
977 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
978 chargingStation,
979 connectorId,
980 OCPP16ChargePointStatus.Available,
981 );
982 }
983 logger.warn(
984 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
985 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
986 connectorId,
987 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
988 );
989 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
990 }
991
992 private setRemoteStartTransactionChargingProfile(
993 chargingStation: ChargingStation,
994 connectorId: number,
995 chargingProfile: OCPP16ChargingProfile,
996 ): boolean {
997 if (
998 chargingProfile &&
999 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
1000 ) {
1001 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
1002 logger.debug(
1003 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1004 on connector id ${connectorId}: %j`,
1005 chargingProfile,
1006 );
1007 return true;
1008 } else if (
1009 chargingProfile &&
1010 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
1011 ) {
1012 logger.warn(
1013 `${chargingStation.logPrefix()} Not allowed to set ${
1014 chargingProfile.chargingProfilePurpose
1015 } charging profile(s) at remote start transaction`,
1016 );
1017 return false;
1018 }
1019 return true;
1020 }
1021
1022 private async handleRequestRemoteStopTransaction(
1023 chargingStation: ChargingStation,
1024 commandPayload: RemoteStopTransactionRequest,
1025 ): Promise<GenericResponse> {
1026 const { transactionId } = commandPayload;
1027 if (chargingStation.hasEvses) {
1028 for (const [evseId, evseStatus] of chargingStation.evses) {
1029 if (evseId > 0) {
1030 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1031 if (connectorStatus.transactionId === transactionId) {
1032 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1033 }
1034 }
1035 }
1036 }
1037 } else {
1038 for (const connectorId of chargingStation.connectors.keys()) {
1039 if (
1040 connectorId > 0 &&
1041 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1042 ) {
1043 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1044 }
1045 }
1046 }
1047 logger.warn(
1048 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
1049 ${transactionId.toString()}`,
1050 );
1051 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1052 }
1053
1054 private handleRequestUpdateFirmware(
1055 chargingStation: ChargingStation,
1056 commandPayload: OCPP16UpdateFirmwareRequest,
1057 ): OCPP16UpdateFirmwareResponse {
1058 if (
1059 OCPP16ServiceUtils.checkFeatureProfile(
1060 chargingStation,
1061 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1062 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1063 ) === false
1064 ) {
1065 logger.warn(
1066 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1067 Cannot simulate firmware update: feature profile not supported`,
1068 );
1069 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1070 }
1071 let { retrieveDate } = commandPayload;
1072 if (
1073 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1074 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1075 ) {
1076 logger.warn(
1077 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1078 Cannot simulate firmware update: firmware update is already in progress`,
1079 );
1080 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1081 }
1082 retrieveDate = convertToDate(retrieveDate)!;
1083 const now = Date.now();
1084 if (retrieveDate?.getTime() <= now) {
1085 this.runInAsyncScope(
1086 this.updateFirmwareSimulation.bind(this) as (
1087 this: OCPP16IncomingRequestService,
1088 ...args: unknown[]
1089 ) => Promise<void>,
1090 this,
1091 chargingStation,
1092 ).catch(Constants.EMPTY_FUNCTION);
1093 } else {
1094 setTimeout(
1095 () => {
1096 this.runInAsyncScope(
1097 this.updateFirmwareSimulation.bind(this) as (
1098 this: OCPP16IncomingRequestService,
1099 ...args: unknown[]
1100 ) => Promise<void>,
1101 this,
1102 chargingStation,
1103 ).catch(Constants.EMPTY_FUNCTION);
1104 },
1105 retrieveDate?.getTime() - now,
1106 );
1107 }
1108 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1109 }
1110
1111 private async updateFirmwareSimulation(
1112 chargingStation: ChargingStation,
1113 maxDelay = 30,
1114 minDelay = 15,
1115 ): Promise<void> {
1116 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1117 return;
1118 }
1119 if (chargingStation.hasEvses) {
1120 for (const [evseId, evseStatus] of chargingStation.evses) {
1121 if (evseId > 0) {
1122 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1123 if (connectorStatus?.transactionStarted === false) {
1124 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1125 chargingStation,
1126 connectorId,
1127 OCPP16ChargePointStatus.Unavailable,
1128 );
1129 }
1130 }
1131 }
1132 }
1133 } else {
1134 for (const connectorId of chargingStation.connectors.keys()) {
1135 if (
1136 connectorId > 0 &&
1137 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1138 ) {
1139 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1140 chargingStation,
1141 connectorId,
1142 OCPP16ChargePointStatus.Unavailable,
1143 );
1144 }
1145 }
1146 }
1147 await chargingStation.ocppRequestService.requestHandler<
1148 OCPP16FirmwareStatusNotificationRequest,
1149 OCPP16FirmwareStatusNotificationResponse
1150 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1151 status: OCPP16FirmwareStatus.Downloading,
1152 });
1153 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1154 if (
1155 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1156 OCPP16FirmwareStatus.DownloadFailed
1157 ) {
1158 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1159 await chargingStation.ocppRequestService.requestHandler<
1160 OCPP16FirmwareStatusNotificationRequest,
1161 OCPP16FirmwareStatusNotificationResponse
1162 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1163 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1164 });
1165 chargingStation.stationInfo.firmwareStatus =
1166 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1167 return;
1168 }
1169 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1170 await chargingStation.ocppRequestService.requestHandler<
1171 OCPP16FirmwareStatusNotificationRequest,
1172 OCPP16FirmwareStatusNotificationResponse
1173 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1174 status: OCPP16FirmwareStatus.Downloaded,
1175 });
1176 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1177 let wasTransactionsStarted = false;
1178 let transactionsStarted: boolean;
1179 do {
1180 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1181 if (runningTransactions > 0) {
1182 const waitTime = secondsToMilliseconds(15);
1183 logger.debug(
1184 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1185 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1186 waitTime,
1187 )} before continuing firmware update simulation`,
1188 );
1189 await sleep(waitTime);
1190 transactionsStarted = true;
1191 wasTransactionsStarted = true;
1192 } else {
1193 if (chargingStation.hasEvses) {
1194 for (const [evseId, evseStatus] of chargingStation.evses) {
1195 if (evseId > 0) {
1196 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1197 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1198 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1199 chargingStation,
1200 connectorId,
1201 OCPP16ChargePointStatus.Unavailable,
1202 );
1203 }
1204 }
1205 }
1206 }
1207 } else {
1208 for (const connectorId of chargingStation.connectors.keys()) {
1209 if (
1210 connectorId > 0 &&
1211 chargingStation.getConnectorStatus(connectorId)?.status !==
1212 OCPP16ChargePointStatus.Unavailable
1213 ) {
1214 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1215 chargingStation,
1216 connectorId,
1217 OCPP16ChargePointStatus.Unavailable,
1218 );
1219 }
1220 }
1221 }
1222 transactionsStarted = false;
1223 }
1224 } while (transactionsStarted);
1225 !wasTransactionsStarted &&
1226 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1227 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1228 return;
1229 }
1230 await chargingStation.ocppRequestService.requestHandler<
1231 OCPP16FirmwareStatusNotificationRequest,
1232 OCPP16FirmwareStatusNotificationResponse
1233 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1234 status: OCPP16FirmwareStatus.Installing,
1235 });
1236 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1237 if (
1238 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1239 OCPP16FirmwareStatus.InstallationFailed
1240 ) {
1241 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1242 await chargingStation.ocppRequestService.requestHandler<
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1246 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1247 });
1248 chargingStation.stationInfo.firmwareStatus =
1249 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1250 return;
1251 }
1252 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1253 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1254 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1255 }
1256 }
1257
1258 private async handleRequestGetDiagnostics(
1259 chargingStation: ChargingStation,
1260 commandPayload: GetDiagnosticsRequest,
1261 ): Promise<GetDiagnosticsResponse> {
1262 if (
1263 OCPP16ServiceUtils.checkFeatureProfile(
1264 chargingStation,
1265 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1266 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1267 ) === false
1268 ) {
1269 logger.warn(
1270 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1271 Cannot get diagnostics: feature profile not supported`,
1272 );
1273 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1274 }
1275 const { location } = commandPayload;
1276 const uri = new URL(location);
1277 if (uri.protocol.startsWith('ftp:')) {
1278 let ftpClient: Client | undefined;
1279 try {
1280 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1281 .filter((file) => file.endsWith('.log'))
1282 .map((file) => join('./', file));
1283 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1284 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1285 ftpClient = new Client();
1286 const accessResponse = await ftpClient.access({
1287 host: uri.host,
1288 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1289 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1290 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1291 });
1292 let uploadResponse: FTPResponse | undefined;
1293 if (accessResponse.code === 220) {
1294 ftpClient.trackProgress((info) => {
1295 logger.info(
1296 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1297 info.bytes / 1024
1298 } bytes transferred from diagnostics archive ${info.name}`,
1299 );
1300 chargingStation.ocppRequestService
1301 .requestHandler<
1302 OCPP16DiagnosticsStatusNotificationRequest,
1303 OCPP16DiagnosticsStatusNotificationResponse
1304 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1305 status: OCPP16DiagnosticsStatus.Uploading,
1306 })
1307 .catch((error) => {
1308 logger.error(
1309 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1310 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1311 error,
1312 );
1313 });
1314 });
1315 uploadResponse = await ftpClient.uploadFrom(
1316 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1317 `${uri.pathname}${diagnosticsArchive}`,
1318 );
1319 if (uploadResponse.code === 226) {
1320 await chargingStation.ocppRequestService.requestHandler<
1321 OCPP16DiagnosticsStatusNotificationRequest,
1322 OCPP16DiagnosticsStatusNotificationResponse
1323 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1324 status: OCPP16DiagnosticsStatus.Uploaded,
1325 });
1326 if (ftpClient) {
1327 ftpClient.close();
1328 }
1329 return { fileName: diagnosticsArchive };
1330 }
1331 throw new OCPPError(
1332 ErrorType.GENERIC_ERROR,
1333 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1334 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1335 }`,
1336 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1337 );
1338 }
1339 throw new OCPPError(
1340 ErrorType.GENERIC_ERROR,
1341 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1342 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1343 }`,
1344 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1345 );
1346 } catch (error) {
1347 await chargingStation.ocppRequestService.requestHandler<
1348 OCPP16DiagnosticsStatusNotificationRequest,
1349 OCPP16DiagnosticsStatusNotificationResponse
1350 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1351 status: OCPP16DiagnosticsStatus.UploadFailed,
1352 });
1353 if (ftpClient) {
1354 ftpClient.close();
1355 }
1356 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1357 chargingStation,
1358 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1359 error as Error,
1360 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1361 )!;
1362 }
1363 } else {
1364 logger.error(
1365 `${chargingStation.logPrefix()} Unsupported protocol ${
1366 uri.protocol
1367 } to transfer the diagnostic logs archive`,
1368 );
1369 await chargingStation.ocppRequestService.requestHandler<
1370 OCPP16DiagnosticsStatusNotificationRequest,
1371 OCPP16DiagnosticsStatusNotificationResponse
1372 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1373 status: OCPP16DiagnosticsStatus.UploadFailed,
1374 });
1375 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1376 }
1377 }
1378
1379 private handleRequestTriggerMessage(
1380 chargingStation: ChargingStation,
1381 commandPayload: OCPP16TriggerMessageRequest,
1382 ): OCPP16TriggerMessageResponse {
1383 const { requestedMessage, connectorId } = commandPayload;
1384 if (
1385 !OCPP16ServiceUtils.checkFeatureProfile(
1386 chargingStation,
1387 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1388 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1389 ) ||
1390 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1391 ) {
1392 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1393 }
1394 if (
1395 !OCPP16ServiceUtils.isConnectorIdValid(
1396 chargingStation,
1397 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1398 connectorId!,
1399 )
1400 ) {
1401 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1402 }
1403 try {
1404 switch (requestedMessage) {
1405 case OCPP16MessageTrigger.BootNotification:
1406 setTimeout(() => {
1407 chargingStation.ocppRequestService
1408 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1409 chargingStation,
1410 OCPP16RequestCommand.BOOT_NOTIFICATION,
1411 chargingStation.bootNotificationRequest,
1412 { skipBufferingOnError: true, triggerMessage: true },
1413 )
1414 .then((response) => {
1415 chargingStation.bootNotificationResponse = response;
1416 })
1417 .catch(Constants.EMPTY_FUNCTION);
1418 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1419 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1420 case OCPP16MessageTrigger.Heartbeat:
1421 setTimeout(() => {
1422 chargingStation.ocppRequestService
1423 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1424 chargingStation,
1425 OCPP16RequestCommand.HEARTBEAT,
1426 null,
1427 {
1428 triggerMessage: true,
1429 },
1430 )
1431 .catch(Constants.EMPTY_FUNCTION);
1432 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1433 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1434 case OCPP16MessageTrigger.StatusNotification:
1435 setTimeout(() => {
1436 if (!isNullOrUndefined(connectorId)) {
1437 chargingStation.ocppRequestService
1438 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1439 chargingStation,
1440 OCPP16RequestCommand.STATUS_NOTIFICATION,
1441 {
1442 connectorId,
1443 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1444 status: chargingStation.getConnectorStatus(connectorId!)?.status,
1445 },
1446 {
1447 triggerMessage: true,
1448 },
1449 )
1450 .catch(Constants.EMPTY_FUNCTION);
1451 } else {
1452 // eslint-disable-next-line no-lonely-if
1453 if (chargingStation.hasEvses) {
1454 for (const evseStatus of chargingStation.evses.values()) {
1455 for (const [id, connectorStatus] of evseStatus.connectors) {
1456 chargingStation.ocppRequestService
1457 .requestHandler<
1458 OCPP16StatusNotificationRequest,
1459 OCPP16StatusNotificationResponse
1460 >(
1461 chargingStation,
1462 OCPP16RequestCommand.STATUS_NOTIFICATION,
1463 {
1464 connectorId: id,
1465 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1466 status: connectorStatus.status,
1467 },
1468 {
1469 triggerMessage: true,
1470 },
1471 )
1472 .catch(Constants.EMPTY_FUNCTION);
1473 }
1474 }
1475 } else {
1476 for (const id of chargingStation.connectors.keys()) {
1477 chargingStation.ocppRequestService
1478 .requestHandler<
1479 OCPP16StatusNotificationRequest,
1480 OCPP16StatusNotificationResponse
1481 >(
1482 chargingStation,
1483 OCPP16RequestCommand.STATUS_NOTIFICATION,
1484 {
1485 connectorId: id,
1486 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1487 status: chargingStation.getConnectorStatus(id)?.status,
1488 },
1489 {
1490 triggerMessage: true,
1491 },
1492 )
1493 .catch(Constants.EMPTY_FUNCTION);
1494 }
1495 }
1496 }
1497 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1498 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1499 default:
1500 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1501 }
1502 } catch (error) {
1503 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1504 chargingStation,
1505 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1506 error as Error,
1507 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1508 )!;
1509 }
1510 }
1511
1512 private handleRequestDataTransfer(
1513 chargingStation: ChargingStation,
1514 commandPayload: OCPP16DataTransferRequest,
1515 ): OCPP16DataTransferResponse {
1516 const { vendorId } = commandPayload;
1517 try {
1518 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1519 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1520 }
1521 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1522 } catch (error) {
1523 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1524 chargingStation,
1525 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1526 error as Error,
1527 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1528 )!;
1529 }
1530 }
1531
1532 private async handleRequestReserveNow(
1533 chargingStation: ChargingStation,
1534 commandPayload: OCPP16ReserveNowRequest,
1535 ): Promise<OCPP16ReserveNowResponse> {
1536 if (
1537 !OCPP16ServiceUtils.checkFeatureProfile(
1538 chargingStation,
1539 OCPP16SupportedFeatureProfiles.Reservation,
1540 OCPP16IncomingRequestCommand.RESERVE_NOW,
1541 )
1542 ) {
1543 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1544 }
1545 const { reservationId, idTag, connectorId } = commandPayload;
1546 let response: OCPP16ReserveNowResponse;
1547 try {
1548 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1549 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1550 }
1551 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1552 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1553 }
1554 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1555 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1556 }
1557 await removeExpiredReservations(chargingStation);
1558 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1559 case OCPP16ChargePointStatus.Faulted:
1560 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1561 break;
1562 case OCPP16ChargePointStatus.Preparing:
1563 case OCPP16ChargePointStatus.Charging:
1564 case OCPP16ChargePointStatus.SuspendedEV:
1565 case OCPP16ChargePointStatus.SuspendedEVSE:
1566 case OCPP16ChargePointStatus.Finishing:
1567 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1568 break;
1569 case OCPP16ChargePointStatus.Unavailable:
1570 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1571 break;
1572 case OCPP16ChargePointStatus.Reserved:
1573 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1574 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1575 break;
1576 }
1577 // eslint-disable-next-line no-fallthrough
1578 default:
1579 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1580 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1581 break;
1582 }
1583 await chargingStation.addReservation({
1584 id: commandPayload.reservationId,
1585 ...commandPayload,
1586 });
1587 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1588 break;
1589 }
1590 return response;
1591 } catch (error) {
1592 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1593 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1594 chargingStation,
1595 OCPP16IncomingRequestCommand.RESERVE_NOW,
1596 error as Error,
1597 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1598 )!;
1599 }
1600 }
1601
1602 private async handleRequestCancelReservation(
1603 chargingStation: ChargingStation,
1604 commandPayload: OCPP16CancelReservationRequest,
1605 ): Promise<GenericResponse> {
1606 if (
1607 !OCPP16ServiceUtils.checkFeatureProfile(
1608 chargingStation,
1609 OCPP16SupportedFeatureProfiles.Reservation,
1610 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1611 )
1612 ) {
1613 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1614 }
1615 try {
1616 const { reservationId } = commandPayload;
1617 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1618 if (isUndefined(reservation)) {
1619 logger.debug(
1620 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1621 does not exist on charging station`,
1622 );
1623 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1624 }
1625 await chargingStation.removeReservation(
1626 reservation!,
1627 ReservationTerminationReason.RESERVATION_CANCELED,
1628 );
1629 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1630 } catch (error) {
1631 return this.handleIncomingRequestError<GenericResponse>(
1632 chargingStation,
1633 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1634 error as Error,
1635 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1636 )!;
1637 }
1638 }
1639 }