refactor: use keyof to build the reservation filter key
[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 { 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 setConfigurationKeyValue,
19 } from '../../../charging-station';
20 import { OCPPError } from '../../../exception';
21 import {
22 type ChangeAvailabilityRequest,
23 type ChangeAvailabilityResponse,
24 type ChangeConfigurationRequest,
25 type ChangeConfigurationResponse,
26 type ClearChargingProfileRequest,
27 type ClearChargingProfileResponse,
28 type ConnectorStatus,
29 ErrorType,
30 type GenericResponse,
31 GenericStatus,
32 type GetConfigurationRequest,
33 type GetConfigurationResponse,
34 type GetDiagnosticsRequest,
35 type GetDiagnosticsResponse,
36 type IncomingRequestHandler,
37 type JsonObject,
38 type JsonType,
39 OCPP16AuthorizationStatus,
40 OCPP16AvailabilityType,
41 type OCPP16BootNotificationRequest,
42 type OCPP16BootNotificationResponse,
43 type OCPP16CancelReservationRequest,
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 StartTransactionRequest,
88 type UnlockConnectorRequest,
89 type UnlockConnectorResponse,
90 } from '../../../types';
91 import {
92 Constants,
93 convertToDate,
94 convertToInt,
95 formatDurationMilliSeconds,
96 getRandomInteger,
97 isEmptyArray,
98 isNotEmptyArray,
99 isNotEmptyString,
100 isNullOrUndefined,
101 isUndefined,
102 logger,
103 sleep,
104 } from '../../../utils';
105 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService';
106
107 const moduleName = 'OCPP16IncomingRequestService';
108
109 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
110 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
111 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
112
113 public constructor() {
114 // if (new.target?.name === moduleName) {
115 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
116 // }
117 super(OCPPVersion.VERSION_16);
118 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
119 [
120 OCPP16IncomingRequestCommand.RESET,
121 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler,
122 ],
123 [
124 OCPP16IncomingRequestCommand.CLEAR_CACHE,
125 this.handleRequestClearCache.bind(this) as IncomingRequestHandler,
126 ],
127 [
128 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
129 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler,
130 ],
131 [
132 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
133 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler,
134 ],
135 [
136 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
137 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler,
138 ],
139 [
140 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
141 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler,
142 ],
143 [
144 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
145 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler,
146 ],
147 [
148 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
149 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler,
150 ],
151 [
152 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
153 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler,
154 ],
155 [
156 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
157 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler,
158 ],
159 [
160 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
161 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler,
162 ],
163 [
164 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
165 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler,
166 ],
167 [
168 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
169 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler,
170 ],
171 [
172 OCPP16IncomingRequestCommand.DATA_TRANSFER,
173 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler,
174 ],
175 [
176 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
177 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler,
178 ],
179 [
180 OCPP16IncomingRequestCommand.RESERVE_NOW,
181 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler,
182 ],
183 [
184 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
185 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler,
186 ],
187 ]);
188 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
189 [
190 OCPP16IncomingRequestCommand.RESET,
191 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
192 'assets/json-schemas/ocpp/1.6/Reset.json',
193 moduleName,
194 'constructor',
195 ),
196 ],
197 [
198 OCPP16IncomingRequestCommand.CLEAR_CACHE,
199 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
200 'assets/json-schemas/ocpp/1.6/ClearCache.json',
201 moduleName,
202 'constructor',
203 ),
204 ],
205 [
206 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
207 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
208 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
209 moduleName,
210 'constructor',
211 ),
212 ],
213 [
214 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
215 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
216 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
217 moduleName,
218 'constructor',
219 ),
220 ],
221 [
222 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
223 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
224 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
225 moduleName,
226 'constructor',
227 ),
228 ],
229 [
230 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
231 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
232 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
233 moduleName,
234 'constructor',
235 ),
236 ],
237 [
238 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
239 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
240 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
241 moduleName,
242 'constructor',
243 ),
244 ],
245 [
246 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
247 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
248 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
249 moduleName,
250 'constructor',
251 ),
252 ],
253 [
254 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
255 OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileRequest>(
256 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
257 moduleName,
258 'constructor',
259 ),
260 ],
261 [
262 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
263 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeAvailabilityRequest>(
264 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
265 moduleName,
266 'constructor',
267 ),
268 ],
269 [
270 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
271 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
272 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
273 moduleName,
274 'constructor',
275 ),
276 ],
277 [
278 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
279 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
280 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
281 moduleName,
282 'constructor',
283 ),
284 ],
285 [
286 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
287 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
288 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
289 moduleName,
290 'constructor',
291 ),
292 ],
293 [
294 OCPP16IncomingRequestCommand.DATA_TRANSFER,
295 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
296 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
297 moduleName,
298 'constructor',
299 ),
300 ],
301 [
302 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
303 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
304 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
305 moduleName,
306 'constructor',
307 ),
308 ],
309 [
310 OCPP16IncomingRequestCommand.RESERVE_NOW,
311 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
312 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
313 moduleName,
314 'constructor',
315 ),
316 ],
317 [
318 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
319 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
320 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
321 moduleName,
322 'constructor',
323 ),
324 ],
325 ]);
326 this.validatePayload = this.validatePayload.bind(this) as (
327 chargingStation: ChargingStation,
328 commandName: OCPP16IncomingRequestCommand,
329 commandPayload: JsonType,
330 ) => boolean;
331 }
332
333 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
334 chargingStation: ChargingStation,
335 messageId: string,
336 commandName: OCPP16IncomingRequestCommand,
337 commandPayload: ReqType,
338 ): Promise<void> {
339 let response: ResType;
340 if (
341 chargingStation.getOcppStrictCompliance() === true &&
342 chargingStation.inPendingState() === true &&
343 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
344 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
345 ) {
346 throw new OCPPError(
347 ErrorType.SECURITY_ERROR,
348 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
349 commandPayload,
350 null,
351 2,
352 )} while the charging station is in pending state on the central server`,
353 commandName,
354 commandPayload,
355 );
356 }
357 if (
358 chargingStation.isRegistered() === true ||
359 (chargingStation.getOcppStrictCompliance() === false &&
360 chargingStation.inUnknownState() === true)
361 ) {
362 if (
363 this.incomingRequestHandlers.has(commandName) === true &&
364 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
365 ) {
366 try {
367 this.validatePayload(chargingStation, commandName, commandPayload);
368 // Call the method to build the response
369 response = (await this.incomingRequestHandlers.get(commandName)!(
370 chargingStation,
371 commandPayload,
372 )) as ResType;
373 } catch (error) {
374 // Log
375 logger.error(
376 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
377 Handle incoming request error:`,
378 error,
379 );
380 throw error;
381 }
382 } else {
383 // Throw exception
384 throw new OCPPError(
385 ErrorType.NOT_IMPLEMENTED,
386 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
387 commandPayload,
388 null,
389 2,
390 )}`,
391 commandName,
392 commandPayload,
393 );
394 }
395 } else {
396 throw new OCPPError(
397 ErrorType.SECURITY_ERROR,
398 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
399 commandPayload,
400 null,
401 2,
402 )} while the charging station is not registered on the central server.`,
403 commandName,
404 commandPayload,
405 );
406 }
407 // Send the built response
408 await chargingStation.ocppRequestService.sendResponse(
409 chargingStation,
410 messageId,
411 response,
412 commandName,
413 );
414 }
415
416 private validatePayload(
417 chargingStation: ChargingStation,
418 commandName: OCPP16IncomingRequestCommand,
419 commandPayload: JsonType,
420 ): boolean {
421 if (this.jsonSchemas.has(commandName) === true) {
422 return this.validateIncomingRequestPayload(
423 chargingStation,
424 commandName,
425 this.jsonSchemas.get(commandName)!,
426 commandPayload,
427 );
428 }
429 logger.warn(
430 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
431 for command '${commandName}' PDU validation`,
432 );
433 return false;
434 }
435
436 // Simulate charging station restart
437 private handleRequestReset(
438 chargingStation: ChargingStation,
439 commandPayload: ResetRequest,
440 ): GenericResponse {
441 this.runInAsyncScope(
442 chargingStation.reset.bind(chargingStation) as (
443 this: ChargingStation,
444 ...args: unknown[]
445 ) => Promise<void>,
446 chargingStation,
447 `${commandPayload.type}Reset` as OCPP16StopTransactionReason,
448 ).catch(Constants.EMPTY_FUNCTION);
449 logger.info(
450 `${chargingStation.logPrefix()} ${
451 commandPayload.type
452 } reset command received, simulating it. The station will be
453 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`,
454 );
455 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
456 }
457
458 private async handleRequestUnlockConnector(
459 chargingStation: ChargingStation,
460 commandPayload: UnlockConnectorRequest,
461 ): Promise<UnlockConnectorResponse> {
462 const connectorId = commandPayload.connectorId;
463 if (chargingStation.hasConnector(connectorId) === false) {
464 logger.error(
465 `${chargingStation.logPrefix()} Trying to unlock a non existing
466 connector id ${connectorId.toString()}`,
467 );
468 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
469 }
470 if (connectorId === 0) {
471 logger.error(
472 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
473 );
474 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
475 }
476 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
477 const stopResponse = await chargingStation.stopTransactionOnConnector(
478 connectorId,
479 OCPP16StopTransactionReason.UNLOCK_COMMAND,
480 );
481 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
482 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
483 }
484 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
485 }
486 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
487 chargingStation,
488 connectorId,
489 OCPP16ChargePointStatus.Available,
490 );
491 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
492 }
493
494 private handleRequestGetConfiguration(
495 chargingStation: ChargingStation,
496 commandPayload: GetConfigurationRequest,
497 ): GetConfigurationResponse {
498 const configurationKey: OCPPConfigurationKey[] = [];
499 const unknownKey: string[] = [];
500 if (isUndefined(commandPayload.key) === true) {
501 for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) {
502 if (isUndefined(configuration.visible) === true) {
503 configuration.visible = true;
504 }
505 if (configuration.visible === false) {
506 continue;
507 }
508 configurationKey.push({
509 key: configuration.key,
510 readonly: configuration.readonly,
511 value: configuration.value,
512 });
513 }
514 } else if (isNotEmptyArray(commandPayload.key) === true) {
515 for (const key of commandPayload.key!) {
516 const keyFound = getConfigurationKey(chargingStation, key, true);
517 if (keyFound) {
518 if (isUndefined(keyFound.visible) === true) {
519 keyFound.visible = true;
520 }
521 if (keyFound.visible === false) {
522 continue;
523 }
524 configurationKey.push({
525 key: keyFound.key,
526 readonly: keyFound.readonly,
527 value: keyFound.value,
528 });
529 } else {
530 unknownKey.push(key);
531 }
532 }
533 }
534 return {
535 configurationKey,
536 unknownKey,
537 };
538 }
539
540 private handleRequestChangeConfiguration(
541 chargingStation: ChargingStation,
542 commandPayload: ChangeConfigurationRequest,
543 ): ChangeConfigurationResponse {
544 const keyToChange = getConfigurationKey(chargingStation, commandPayload.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 !== commandPayload.value) {
550 setConfigurationKeyValue(chargingStation, commandPayload.key, commandPayload.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 commandPayload.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 commandPayload.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 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
610 logger.error(
611 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
612 non existing connector id ${commandPayload.connectorId}`,
613 );
614 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
615 }
616 if (
617 commandPayload.csChargingProfiles.chargingProfilePurpose ===
618 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
619 commandPayload.connectorId !== 0
620 ) {
621 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
622 }
623 if (
624 commandPayload.csChargingProfiles.chargingProfilePurpose ===
625 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
626 (commandPayload.connectorId === 0 ||
627 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
628 false)
629 ) {
630 logger.error(
631 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
632 on connector ${commandPayload.connectorId} without a started transaction`,
633 );
634 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
635 }
636 OCPP16ServiceUtils.setChargingProfile(
637 chargingStation,
638 commandPayload.connectorId,
639 commandPayload.csChargingProfiles,
640 );
641 logger.debug(
642 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
643 commandPayload.connectorId
644 }: %j`,
645 commandPayload.csChargingProfiles,
646 );
647 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
648 }
649
650 private handleRequestGetCompositeSchedule(
651 chargingStation: ChargingStation,
652 commandPayload: OCPP16GetCompositeScheduleRequest,
653 ): OCPP16GetCompositeScheduleResponse {
654 if (
655 OCPP16ServiceUtils.checkFeatureProfile(
656 chargingStation,
657 OCPP16SupportedFeatureProfiles.SmartCharging,
658 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
659 ) === false
660 ) {
661 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
662 }
663 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
664 logger.error(
665 `${chargingStation.logPrefix()} Trying to get composite schedule to a
666 non existing connector id ${commandPayload.connectorId}`,
667 );
668 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
669 }
670 if (
671 isEmptyArray(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles)
672 ) {
673 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
674 }
675 const startDate = new Date();
676 const endDate = new Date(startDate.getTime() + secondsToMilliseconds(commandPayload.duration));
677 let compositeSchedule: OCPP16ChargingSchedule | undefined;
678 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)!
679 .chargingProfiles!) {
680 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
681 if (
682 chargingProfile.chargingSchedule.startSchedule! >= startDate &&
683 chargingProfile.chargingSchedule.startSchedule! <= endDate
684 ) {
685 compositeSchedule = chargingProfile.chargingSchedule;
686 break;
687 }
688 }
689 return {
690 status: GenericStatus.Accepted,
691 scheduleStart: compositeSchedule?.startSchedule,
692 connectorId: commandPayload.connectorId,
693 chargingSchedule: compositeSchedule,
694 };
695 }
696
697 private handleRequestClearChargingProfile(
698 chargingStation: ChargingStation,
699 commandPayload: ClearChargingProfileRequest,
700 ): ClearChargingProfileResponse {
701 if (
702 OCPP16ServiceUtils.checkFeatureProfile(
703 chargingStation,
704 OCPP16SupportedFeatureProfiles.SmartCharging,
705 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
706 ) === false
707 ) {
708 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
709 }
710 if (chargingStation.hasConnector(commandPayload.connectorId!) === false) {
711 logger.error(
712 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
713 a non existing connector id ${commandPayload.connectorId}`,
714 );
715 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
716 }
717 if (
718 !isNullOrUndefined(commandPayload.connectorId) &&
719 isNotEmptyArray(
720 chargingStation.getConnectorStatus(commandPayload.connectorId!)?.chargingProfiles,
721 )
722 ) {
723 chargingStation.getConnectorStatus(commandPayload.connectorId!)!.chargingProfiles = [];
724 logger.debug(
725 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
726 commandPayload.connectorId
727 }`,
728 );
729 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
730 }
731 if (isNullOrUndefined(commandPayload.connectorId)) {
732 let clearedCP = false;
733 if (chargingStation.hasEvses) {
734 for (const evseStatus of chargingStation.evses.values()) {
735 for (const connectorStatus of evseStatus.connectors.values()) {
736 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
737 chargingStation,
738 commandPayload,
739 connectorStatus.chargingProfiles,
740 );
741 }
742 }
743 } else {
744 for (const connectorId of chargingStation.connectors.keys()) {
745 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
746 chargingStation,
747 commandPayload,
748 chargingStation.getConnectorStatus(connectorId)?.chargingProfiles,
749 );
750 }
751 }
752 if (clearedCP) {
753 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
754 }
755 }
756 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
757 }
758
759 private async handleRequestChangeAvailability(
760 chargingStation: ChargingStation,
761 commandPayload: ChangeAvailabilityRequest,
762 ): Promise<ChangeAvailabilityResponse> {
763 const connectorId: number = commandPayload.connectorId;
764 if (chargingStation.hasConnector(connectorId) === false) {
765 logger.error(
766 `${chargingStation.logPrefix()} Trying to change the availability of a
767 non existing connector id ${connectorId.toString()}`,
768 );
769 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
770 }
771 const chargePointStatus: OCPP16ChargePointStatus =
772 commandPayload.type === OCPP16AvailabilityType.Operative
773 ? OCPP16ChargePointStatus.Available
774 : OCPP16ChargePointStatus.Unavailable;
775 if (connectorId === 0) {
776 let response: ChangeAvailabilityResponse =
777 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
778 const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => {
779 if (connectorStatus?.transactionStarted === true) {
780 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
781 }
782 connectorStatus.availability = commandPayload.type;
783 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
784 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
785 chargingStation,
786 id,
787 chargePointStatus,
788 );
789 }
790 };
791 if (chargingStation.hasEvses) {
792 for (const evseStatus of chargingStation.evses.values()) {
793 for (const [id, connectorStatus] of evseStatus.connectors) {
794 await changeAvailability(id, connectorStatus);
795 }
796 }
797 } else {
798 for (const id of chargingStation.connectors.keys()) {
799 await changeAvailability(id, chargingStation.getConnectorStatus(id)!);
800 }
801 }
802 return response;
803 } else if (
804 connectorId > 0 &&
805 (chargingStation.isChargingStationAvailable() === true ||
806 (chargingStation.isChargingStationAvailable() === false &&
807 commandPayload.type === OCPP16AvailabilityType.Inoperative))
808 ) {
809 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
810 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
811 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
812 }
813 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
814 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
815 chargingStation,
816 connectorId,
817 chargePointStatus,
818 );
819 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
820 }
821 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
822 }
823
824 private async handleRequestRemoteStartTransaction(
825 chargingStation: ChargingStation,
826 commandPayload: RemoteStartTransactionRequest,
827 ): Promise<GenericResponse> {
828 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
829 const reserved =
830 chargingStation.getConnectorStatus(transactionConnectorId)!.status ===
831 OCPP16ChargePointStatus.Reserved;
832 const reservedOnConnectorZero =
833 chargingStation.getConnectorStatus(0)!.status === OCPP16ChargePointStatus.Reserved;
834 if (
835 (reserved &&
836 !chargingStation.validateIncomingRequestWithReservation(transactionConnectorId, idTag)) ||
837 (reservedOnConnectorZero && !chargingStation.validateIncomingRequestWithReservation(0, idTag))
838 ) {
839 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
840 }
841 if (chargingStation.hasConnector(transactionConnectorId) === false) {
842 return this.notifyRemoteStartTransactionRejected(
843 chargingStation,
844 transactionConnectorId,
845 idTag,
846 );
847 }
848 if (
849 !chargingStation.isChargingStationAvailable() ||
850 !chargingStation.isConnectorAvailable(transactionConnectorId)
851 ) {
852 return this.notifyRemoteStartTransactionRejected(
853 chargingStation,
854 transactionConnectorId,
855 idTag,
856 );
857 }
858 const remoteStartTransactionLogMsg = `
859 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
860 chargingStation.stationInfo.chargingStationId
861 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
862 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
863 chargingStation,
864 transactionConnectorId,
865 OCPP16ChargePointStatus.Preparing,
866 );
867 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
868 // Check if authorized
869 if (
870 chargingStation.getAuthorizeRemoteTxRequests() &&
871 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
872 ) {
873 // Authorization successful, start transaction
874 if (
875 this.setRemoteStartTransactionChargingProfile(
876 chargingStation,
877 transactionConnectorId,
878 chargingProfile!,
879 ) === true
880 ) {
881 connectorStatus.transactionRemoteStarted = true;
882 const startTransactionPayload: Partial<StartTransactionRequest> = {
883 connectorId: transactionConnectorId,
884 idTag,
885 };
886 if (reserved || reservedOnConnectorZero) {
887 const reservation = chargingStation.getReservationBy(
888 'connectorId',
889 reservedOnConnectorZero ? 0 : transactionConnectorId,
890 )!;
891 startTransactionPayload.reservationId = reservation.reservationId;
892 await chargingStation.removeReservation(
893 reservation,
894 ReservationTerminationReason.TRANSACTION_STARTED,
895 );
896 }
897 if (
898 (
899 await chargingStation.ocppRequestService.requestHandler<
900 OCPP16StartTransactionRequest,
901 OCPP16StartTransactionResponse
902 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, startTransactionPayload)
903 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
904 ) {
905 logger.debug(remoteStartTransactionLogMsg);
906 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
907 }
908 return this.notifyRemoteStartTransactionRejected(
909 chargingStation,
910 transactionConnectorId,
911 idTag,
912 );
913 }
914 return this.notifyRemoteStartTransactionRejected(
915 chargingStation,
916 transactionConnectorId,
917 idTag,
918 );
919 }
920 // No authorization check required, start transaction
921 if (
922 this.setRemoteStartTransactionChargingProfile(
923 chargingStation,
924 transactionConnectorId,
925 chargingProfile!,
926 ) === true
927 ) {
928 connectorStatus.transactionRemoteStarted = true;
929 if (
930 (
931 await chargingStation.ocppRequestService.requestHandler<
932 OCPP16StartTransactionRequest,
933 OCPP16StartTransactionResponse
934 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
935 connectorId: transactionConnectorId,
936 idTag,
937 })
938 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
939 ) {
940 logger.debug(remoteStartTransactionLogMsg);
941 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
942 }
943 return this.notifyRemoteStartTransactionRejected(
944 chargingStation,
945 transactionConnectorId,
946 idTag,
947 );
948 }
949 return this.notifyRemoteStartTransactionRejected(
950 chargingStation,
951 transactionConnectorId,
952 idTag,
953 );
954 }
955
956 private async notifyRemoteStartTransactionRejected(
957 chargingStation: ChargingStation,
958 connectorId: number,
959 idTag: string,
960 ): Promise<GenericResponse> {
961 if (
962 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
963 ) {
964 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
965 chargingStation,
966 connectorId,
967 OCPP16ChargePointStatus.Available,
968 );
969 }
970 logger.warn(
971 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
972 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
973 connectorId,
974 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
975 );
976 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
977 }
978
979 private setRemoteStartTransactionChargingProfile(
980 chargingStation: ChargingStation,
981 connectorId: number,
982 chargingProfile: OCPP16ChargingProfile,
983 ): boolean {
984 if (
985 chargingProfile &&
986 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
987 ) {
988 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
989 logger.debug(
990 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
991 on connector id ${connectorId}: %j`,
992 chargingProfile,
993 );
994 return true;
995 } else if (
996 chargingProfile &&
997 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
998 ) {
999 logger.warn(
1000 `${chargingStation.logPrefix()} Not allowed to set ${
1001 chargingProfile.chargingProfilePurpose
1002 } charging profile(s) at remote start transaction`,
1003 );
1004 return false;
1005 }
1006 return true;
1007 }
1008
1009 private async handleRequestRemoteStopTransaction(
1010 chargingStation: ChargingStation,
1011 commandPayload: RemoteStopTransactionRequest,
1012 ): Promise<GenericResponse> {
1013 const transactionId = commandPayload.transactionId;
1014 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1015 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1016 chargingStation,
1017 connectorId,
1018 OCPP16ChargePointStatus.Finishing,
1019 );
1020 const stopResponse = await chargingStation.stopTransactionOnConnector(
1021 connectorId,
1022 OCPP16StopTransactionReason.REMOTE,
1023 );
1024 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1025 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1026 }
1027 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1028 };
1029 if (chargingStation.hasEvses) {
1030 for (const [evseId, evseStatus] of chargingStation.evses) {
1031 if (evseId > 0) {
1032 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1033 if (connectorStatus.transactionId === transactionId) {
1034 return remoteStopTransaction(connectorId);
1035 }
1036 }
1037 }
1038 }
1039 } else {
1040 for (const connectorId of chargingStation.connectors.keys()) {
1041 if (
1042 connectorId > 0 &&
1043 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1044 ) {
1045 return remoteStopTransaction(connectorId);
1046 }
1047 }
1048 }
1049 logger.warn(
1050 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1051 ${transactionId.toString()}`,
1052 );
1053 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1054 }
1055
1056 private handleRequestUpdateFirmware(
1057 chargingStation: ChargingStation,
1058 commandPayload: OCPP16UpdateFirmwareRequest,
1059 ): OCPP16UpdateFirmwareResponse {
1060 if (
1061 OCPP16ServiceUtils.checkFeatureProfile(
1062 chargingStation,
1063 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1064 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1065 ) === false
1066 ) {
1067 logger.warn(
1068 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1069 Cannot simulate firmware update: feature profile not supported`,
1070 );
1071 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1072 }
1073 if (
1074 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1075 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1076 ) {
1077 logger.warn(
1078 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1079 Cannot simulate firmware update: firmware update is already in progress`,
1080 );
1081 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1082 }
1083 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1084 const now = Date.now();
1085 if (retrieveDate?.getTime() <= now) {
1086 this.runInAsyncScope(
1087 this.updateFirmwareSimulation.bind(this) as (
1088 this: OCPP16IncomingRequestService,
1089 ...args: unknown[]
1090 ) => Promise<void>,
1091 this,
1092 chargingStation,
1093 ).catch(Constants.EMPTY_FUNCTION);
1094 } else {
1095 setTimeout(
1096 () => {
1097 this.runInAsyncScope(
1098 this.updateFirmwareSimulation.bind(this) as (
1099 this: OCPP16IncomingRequestService,
1100 ...args: unknown[]
1101 ) => Promise<void>,
1102 this,
1103 chargingStation,
1104 ).catch(Constants.EMPTY_FUNCTION);
1105 },
1106 retrieveDate?.getTime() - now,
1107 );
1108 }
1109 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1110 }
1111
1112 private async updateFirmwareSimulation(
1113 chargingStation: ChargingStation,
1114 maxDelay = 30,
1115 minDelay = 15,
1116 ): Promise<void> {
1117 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1118 return;
1119 }
1120 if (chargingStation.hasEvses) {
1121 for (const [evseId, evseStatus] of chargingStation.evses) {
1122 if (evseId > 0) {
1123 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1124 if (connectorStatus?.transactionStarted === false) {
1125 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1126 chargingStation,
1127 connectorId,
1128 OCPP16ChargePointStatus.Unavailable,
1129 );
1130 }
1131 }
1132 }
1133 }
1134 } else {
1135 for (const connectorId of chargingStation.connectors.keys()) {
1136 if (
1137 connectorId > 0 &&
1138 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1139 ) {
1140 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1141 chargingStation,
1142 connectorId,
1143 OCPP16ChargePointStatus.Unavailable,
1144 );
1145 }
1146 }
1147 }
1148 await chargingStation.ocppRequestService.requestHandler<
1149 OCPP16FirmwareStatusNotificationRequest,
1150 OCPP16FirmwareStatusNotificationResponse
1151 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1152 status: OCPP16FirmwareStatus.Downloading,
1153 });
1154 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1155 if (
1156 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1157 OCPP16FirmwareStatus.DownloadFailed
1158 ) {
1159 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1160 await chargingStation.ocppRequestService.requestHandler<
1161 OCPP16FirmwareStatusNotificationRequest,
1162 OCPP16FirmwareStatusNotificationResponse
1163 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1164 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1165 });
1166 chargingStation.stationInfo.firmwareStatus =
1167 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1168 return;
1169 }
1170 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1171 await chargingStation.ocppRequestService.requestHandler<
1172 OCPP16FirmwareStatusNotificationRequest,
1173 OCPP16FirmwareStatusNotificationResponse
1174 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1175 status: OCPP16FirmwareStatus.Downloaded,
1176 });
1177 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1178 let wasTransactionsStarted = false;
1179 let transactionsStarted: boolean;
1180 do {
1181 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1182 if (runningTransactions > 0) {
1183 const waitTime = secondsToMilliseconds(15);
1184 logger.debug(
1185 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1186 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1187 waitTime,
1188 )} before continuing firmware update simulation`,
1189 );
1190 await sleep(waitTime);
1191 transactionsStarted = true;
1192 wasTransactionsStarted = true;
1193 } else {
1194 if (chargingStation.hasEvses) {
1195 for (const [evseId, evseStatus] of chargingStation.evses) {
1196 if (evseId > 0) {
1197 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1198 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1199 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1200 chargingStation,
1201 connectorId,
1202 OCPP16ChargePointStatus.Unavailable,
1203 );
1204 }
1205 }
1206 }
1207 }
1208 } else {
1209 for (const connectorId of chargingStation.connectors.keys()) {
1210 if (
1211 connectorId > 0 &&
1212 chargingStation.getConnectorStatus(connectorId)?.status !==
1213 OCPP16ChargePointStatus.Unavailable
1214 ) {
1215 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1216 chargingStation,
1217 connectorId,
1218 OCPP16ChargePointStatus.Unavailable,
1219 );
1220 }
1221 }
1222 }
1223 transactionsStarted = false;
1224 }
1225 } while (transactionsStarted);
1226 !wasTransactionsStarted &&
1227 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1228 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1229 return;
1230 }
1231 await chargingStation.ocppRequestService.requestHandler<
1232 OCPP16FirmwareStatusNotificationRequest,
1233 OCPP16FirmwareStatusNotificationResponse
1234 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1235 status: OCPP16FirmwareStatus.Installing,
1236 });
1237 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1238 if (
1239 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1240 OCPP16FirmwareStatus.InstallationFailed
1241 ) {
1242 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1243 await chargingStation.ocppRequestService.requestHandler<
1244 OCPP16FirmwareStatusNotificationRequest,
1245 OCPP16FirmwareStatusNotificationResponse
1246 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1247 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1248 });
1249 chargingStation.stationInfo.firmwareStatus =
1250 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1251 return;
1252 }
1253 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1254 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1255 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1256 }
1257 }
1258
1259 private async handleRequestGetDiagnostics(
1260 chargingStation: ChargingStation,
1261 commandPayload: GetDiagnosticsRequest,
1262 ): Promise<GetDiagnosticsResponse> {
1263 if (
1264 OCPP16ServiceUtils.checkFeatureProfile(
1265 chargingStation,
1266 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1267 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1268 ) === false
1269 ) {
1270 logger.warn(
1271 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1272 Cannot get diagnostics: feature profile not supported`,
1273 );
1274 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1275 }
1276 const uri = new URL(commandPayload.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()} ${
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 if (
1384 !OCPP16ServiceUtils.checkFeatureProfile(
1385 chargingStation,
1386 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1387 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1388 ) ||
1389 !OCPP16ServiceUtils.isMessageTriggerSupported(
1390 chargingStation,
1391 commandPayload.requestedMessage,
1392 )
1393 ) {
1394 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1395 }
1396 if (
1397 !OCPP16ServiceUtils.isConnectorIdValid(
1398 chargingStation,
1399 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1400 commandPayload.connectorId!,
1401 )
1402 ) {
1403 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1404 }
1405 try {
1406 switch (commandPayload.requestedMessage) {
1407 case OCPP16MessageTrigger.BootNotification:
1408 setTimeout(() => {
1409 chargingStation.ocppRequestService
1410 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1411 chargingStation,
1412 OCPP16RequestCommand.BOOT_NOTIFICATION,
1413 chargingStation.bootNotificationRequest,
1414 { skipBufferingOnError: true, triggerMessage: true },
1415 )
1416 .then((response) => {
1417 chargingStation.bootNotificationResponse = response;
1418 })
1419 .catch(Constants.EMPTY_FUNCTION);
1420 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1421 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1422 case OCPP16MessageTrigger.Heartbeat:
1423 setTimeout(() => {
1424 chargingStation.ocppRequestService
1425 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1426 chargingStation,
1427 OCPP16RequestCommand.HEARTBEAT,
1428 null,
1429 {
1430 triggerMessage: true,
1431 },
1432 )
1433 .catch(Constants.EMPTY_FUNCTION);
1434 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1435 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1436 case OCPP16MessageTrigger.StatusNotification:
1437 setTimeout(() => {
1438 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1439 chargingStation.ocppRequestService
1440 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1441 chargingStation,
1442 OCPP16RequestCommand.STATUS_NOTIFICATION,
1443 {
1444 connectorId: commandPayload.connectorId,
1445 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1446 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1447 },
1448 {
1449 triggerMessage: true,
1450 },
1451 )
1452 .catch(Constants.EMPTY_FUNCTION);
1453 } else {
1454 // eslint-disable-next-line no-lonely-if
1455 if (chargingStation.hasEvses) {
1456 for (const evseStatus of chargingStation.evses.values()) {
1457 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1458 chargingStation.ocppRequestService
1459 .requestHandler<
1460 OCPP16StatusNotificationRequest,
1461 OCPP16StatusNotificationResponse
1462 >(
1463 chargingStation,
1464 OCPP16RequestCommand.STATUS_NOTIFICATION,
1465 {
1466 connectorId,
1467 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1468 status: connectorStatus.status,
1469 },
1470 {
1471 triggerMessage: true,
1472 },
1473 )
1474 .catch(Constants.EMPTY_FUNCTION);
1475 }
1476 }
1477 } else {
1478 for (const connectorId of chargingStation.connectors.keys()) {
1479 chargingStation.ocppRequestService
1480 .requestHandler<
1481 OCPP16StatusNotificationRequest,
1482 OCPP16StatusNotificationResponse
1483 >(
1484 chargingStation,
1485 OCPP16RequestCommand.STATUS_NOTIFICATION,
1486 {
1487 connectorId,
1488 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1489 status: chargingStation.getConnectorStatus(connectorId)?.status,
1490 },
1491 {
1492 triggerMessage: true,
1493 },
1494 )
1495 .catch(Constants.EMPTY_FUNCTION);
1496 }
1497 }
1498 }
1499 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1500 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1501 default:
1502 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1503 }
1504 } catch (error) {
1505 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1506 chargingStation,
1507 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1508 error as Error,
1509 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1510 )!;
1511 }
1512 }
1513
1514 private handleRequestDataTransfer(
1515 chargingStation: ChargingStation,
1516 commandPayload: OCPP16DataTransferRequest,
1517 ): OCPP16DataTransferResponse {
1518 try {
1519 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1520 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1521 }
1522 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1523 } catch (error) {
1524 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1525 chargingStation,
1526 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1527 error as Error,
1528 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1529 )!;
1530 }
1531 }
1532
1533 private async handleRequestReserveNow(
1534 chargingStation: ChargingStation,
1535 commandPayload: OCPP16ReserveNowRequest,
1536 ): Promise<OCPP16ReserveNowResponse> {
1537 if (
1538 !OCPP16ServiceUtils.checkFeatureProfile(
1539 chargingStation,
1540 OCPP16SupportedFeatureProfiles.Reservation,
1541 OCPP16IncomingRequestCommand.RESERVE_NOW,
1542 )
1543 ) {
1544 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1545 }
1546 const { reservationId, idTag, connectorId } = commandPayload;
1547 let response: OCPP16ReserveNowResponse;
1548 try {
1549 if (!chargingStation.isConnectorAvailable(connectorId) && connectorId > 0) {
1550 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1551 }
1552 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
1553 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1554 }
1555 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1556 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1557 }
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.error(
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 }