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