fix: ensure recurring profile duration is valid
[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 { create } from 'tar';
10
11 import { OCPP16Constants } from './OCPP16Constants';
12 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
13 import {
14 type ChargingStation,
15 checkChargingStation,
16 getConfigurationKey,
17 setConfigurationKeyValue,
18 } from '../../../charging-station';
19 import { OCPPError } from '../../../exception';
20 import {
21 type ChangeAvailabilityRequest,
22 type ChangeAvailabilityResponse,
23 type ChangeConfigurationRequest,
24 type ChangeConfigurationResponse,
25 type ClearChargingProfileRequest,
26 type ClearChargingProfileResponse,
27 type ConnectorStatus,
28 ErrorType,
29 type GenericResponse,
30 GenericStatus,
31 type GetConfigurationRequest,
32 type GetConfigurationResponse,
33 type GetDiagnosticsRequest,
34 type GetDiagnosticsResponse,
35 type IncomingRequestHandler,
36 type JsonObject,
37 type JsonType,
38 OCPP16AuthorizationStatus,
39 OCPP16AvailabilityType,
40 type OCPP16BootNotificationRequest,
41 type OCPP16BootNotificationResponse,
42 type OCPP16CancelReservationRequest,
43 OCPP16ChargePointErrorCode,
44 OCPP16ChargePointStatus,
45 type OCPP16ChargingProfile,
46 OCPP16ChargingProfilePurposeType,
47 type OCPP16ChargingSchedule,
48 type OCPP16ClearCacheRequest,
49 type OCPP16DataTransferRequest,
50 type OCPP16DataTransferResponse,
51 OCPP16DataTransferVendorId,
52 OCPP16DiagnosticsStatus,
53 type OCPP16DiagnosticsStatusNotificationRequest,
54 type OCPP16DiagnosticsStatusNotificationResponse,
55 OCPP16FirmwareStatus,
56 type OCPP16FirmwareStatusNotificationRequest,
57 type OCPP16FirmwareStatusNotificationResponse,
58 type OCPP16GetCompositeScheduleRequest,
59 type OCPP16GetCompositeScheduleResponse,
60 type OCPP16HeartbeatRequest,
61 type OCPP16HeartbeatResponse,
62 OCPP16IncomingRequestCommand,
63 OCPP16MessageTrigger,
64 OCPP16RequestCommand,
65 type OCPP16ReserveNowRequest,
66 type OCPP16ReserveNowResponse,
67 OCPP16StandardParametersKey,
68 type OCPP16StartTransactionRequest,
69 type OCPP16StartTransactionResponse,
70 type OCPP16StatusNotificationRequest,
71 type OCPP16StatusNotificationResponse,
72 OCPP16StopTransactionReason,
73 OCPP16SupportedFeatureProfiles,
74 type OCPP16TriggerMessageRequest,
75 type OCPP16TriggerMessageResponse,
76 type OCPP16UpdateFirmwareRequest,
77 type OCPP16UpdateFirmwareResponse,
78 type OCPPConfigurationKey,
79 OCPPVersion,
80 type RemoteStartTransactionRequest,
81 type RemoteStopTransactionRequest,
82 ReservationFilterKey,
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() + commandPayload.duration * 1000);
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 ReservationFilterKey.CONNECTOR_ID,
889 reservedOnConnectorZero ? 0 : transactionConnectorId,
890 )!;
891 startTransactionPayload.reservationId = reservation.id;
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(getRandomInteger(maxDelay, minDelay) * 1000);
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(getRandomInteger(maxDelay, minDelay) * 1000);
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 = 15 * 1000;
1184 logger.debug(
1185 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1186 ${runningTransactions} transaction(s) in progress, waiting ${
1187 waitTime / 1000
1188 } seconds 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 && (await sleep(getRandomInteger(maxDelay, minDelay) * 1000));
1227 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1228 return;
1229 }
1230 await chargingStation.ocppRequestService.requestHandler<
1231 OCPP16FirmwareStatusNotificationRequest,
1232 OCPP16FirmwareStatusNotificationResponse
1233 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1234 status: OCPP16FirmwareStatus.Installing,
1235 });
1236 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1237 if (
1238 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1239 OCPP16FirmwareStatus.InstallationFailed
1240 ) {
1241 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1242 await chargingStation.ocppRequestService.requestHandler<
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1246 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1247 });
1248 chargingStation.stationInfo.firmwareStatus =
1249 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1250 return;
1251 }
1252 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1253 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1254 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1255 }
1256 }
1257
1258 private async handleRequestGetDiagnostics(
1259 chargingStation: ChargingStation,
1260 commandPayload: GetDiagnosticsRequest,
1261 ): Promise<GetDiagnosticsResponse> {
1262 if (
1263 OCPP16ServiceUtils.checkFeatureProfile(
1264 chargingStation,
1265 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1266 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1267 ) === false
1268 ) {
1269 logger.warn(
1270 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1271 Cannot get diagnostics: feature profile not supported`,
1272 );
1273 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1274 }
1275 const uri = new URL(commandPayload.location);
1276 if (uri.protocol.startsWith('ftp:')) {
1277 let ftpClient: Client | undefined;
1278 try {
1279 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1280 .filter((file) => file.endsWith('.log'))
1281 .map((file) => join('./', file));
1282 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1283 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1284 ftpClient = new Client();
1285 const accessResponse = await ftpClient.access({
1286 host: uri.host,
1287 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1288 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1289 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1290 });
1291 let uploadResponse: FTPResponse | undefined;
1292 if (accessResponse.code === 220) {
1293 ftpClient.trackProgress((info) => {
1294 logger.info(
1295 `${chargingStation.logPrefix()} ${
1296 info.bytes / 1024
1297 } bytes transferred from diagnostics archive ${info.name}`,
1298 );
1299 chargingStation.ocppRequestService
1300 .requestHandler<
1301 OCPP16DiagnosticsStatusNotificationRequest,
1302 OCPP16DiagnosticsStatusNotificationResponse
1303 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1304 status: OCPP16DiagnosticsStatus.Uploading,
1305 })
1306 .catch((error) => {
1307 logger.error(
1308 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1309 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1310 error,
1311 );
1312 });
1313 });
1314 uploadResponse = await ftpClient.uploadFrom(
1315 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1316 `${uri.pathname}${diagnosticsArchive}`,
1317 );
1318 if (uploadResponse.code === 226) {
1319 await chargingStation.ocppRequestService.requestHandler<
1320 OCPP16DiagnosticsStatusNotificationRequest,
1321 OCPP16DiagnosticsStatusNotificationResponse
1322 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1323 status: OCPP16DiagnosticsStatus.Uploaded,
1324 });
1325 if (ftpClient) {
1326 ftpClient.close();
1327 }
1328 return { fileName: diagnosticsArchive };
1329 }
1330 throw new OCPPError(
1331 ErrorType.GENERIC_ERROR,
1332 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1333 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1334 }`,
1335 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1336 );
1337 }
1338 throw new OCPPError(
1339 ErrorType.GENERIC_ERROR,
1340 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1341 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1342 }`,
1343 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1344 );
1345 } catch (error) {
1346 await chargingStation.ocppRequestService.requestHandler<
1347 OCPP16DiagnosticsStatusNotificationRequest,
1348 OCPP16DiagnosticsStatusNotificationResponse
1349 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1350 status: OCPP16DiagnosticsStatus.UploadFailed,
1351 });
1352 if (ftpClient) {
1353 ftpClient.close();
1354 }
1355 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1356 chargingStation,
1357 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1358 error as Error,
1359 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1360 )!;
1361 }
1362 } else {
1363 logger.error(
1364 `${chargingStation.logPrefix()} Unsupported protocol ${
1365 uri.protocol
1366 } to transfer the diagnostic logs archive`,
1367 );
1368 await chargingStation.ocppRequestService.requestHandler<
1369 OCPP16DiagnosticsStatusNotificationRequest,
1370 OCPP16DiagnosticsStatusNotificationResponse
1371 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1372 status: OCPP16DiagnosticsStatus.UploadFailed,
1373 });
1374 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1375 }
1376 }
1377
1378 private handleRequestTriggerMessage(
1379 chargingStation: ChargingStation,
1380 commandPayload: OCPP16TriggerMessageRequest,
1381 ): OCPP16TriggerMessageResponse {
1382 if (
1383 !OCPP16ServiceUtils.checkFeatureProfile(
1384 chargingStation,
1385 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1386 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1387 ) ||
1388 !OCPP16ServiceUtils.isMessageTriggerSupported(
1389 chargingStation,
1390 commandPayload.requestedMessage,
1391 )
1392 ) {
1393 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1394 }
1395 if (
1396 !OCPP16ServiceUtils.isConnectorIdValid(
1397 chargingStation,
1398 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1399 commandPayload.connectorId!,
1400 )
1401 ) {
1402 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1403 }
1404 try {
1405 switch (commandPayload.requestedMessage) {
1406 case OCPP16MessageTrigger.BootNotification:
1407 setTimeout(() => {
1408 chargingStation.ocppRequestService
1409 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1410 chargingStation,
1411 OCPP16RequestCommand.BOOT_NOTIFICATION,
1412 chargingStation.bootNotificationRequest,
1413 { skipBufferingOnError: true, triggerMessage: true },
1414 )
1415 .then((response) => {
1416 chargingStation.bootNotificationResponse = response;
1417 })
1418 .catch(Constants.EMPTY_FUNCTION);
1419 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1420 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1421 case OCPP16MessageTrigger.Heartbeat:
1422 setTimeout(() => {
1423 chargingStation.ocppRequestService
1424 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1425 chargingStation,
1426 OCPP16RequestCommand.HEARTBEAT,
1427 null,
1428 {
1429 triggerMessage: true,
1430 },
1431 )
1432 .catch(Constants.EMPTY_FUNCTION);
1433 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1434 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1435 case OCPP16MessageTrigger.StatusNotification:
1436 setTimeout(() => {
1437 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1438 chargingStation.ocppRequestService
1439 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1440 chargingStation,
1441 OCPP16RequestCommand.STATUS_NOTIFICATION,
1442 {
1443 connectorId: commandPayload.connectorId,
1444 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1445 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1446 },
1447 {
1448 triggerMessage: true,
1449 },
1450 )
1451 .catch(Constants.EMPTY_FUNCTION);
1452 } else {
1453 // eslint-disable-next-line no-lonely-if
1454 if (chargingStation.hasEvses) {
1455 for (const evseStatus of chargingStation.evses.values()) {
1456 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1457 chargingStation.ocppRequestService
1458 .requestHandler<
1459 OCPP16StatusNotificationRequest,
1460 OCPP16StatusNotificationResponse
1461 >(
1462 chargingStation,
1463 OCPP16RequestCommand.STATUS_NOTIFICATION,
1464 {
1465 connectorId,
1466 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1467 status: connectorStatus.status,
1468 },
1469 {
1470 triggerMessage: true,
1471 },
1472 )
1473 .catch(Constants.EMPTY_FUNCTION);
1474 }
1475 }
1476 } else {
1477 for (const connectorId of chargingStation.connectors.keys()) {
1478 chargingStation.ocppRequestService
1479 .requestHandler<
1480 OCPP16StatusNotificationRequest,
1481 OCPP16StatusNotificationResponse
1482 >(
1483 chargingStation,
1484 OCPP16RequestCommand.STATUS_NOTIFICATION,
1485 {
1486 connectorId,
1487 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1488 status: chargingStation.getConnectorStatus(connectorId)?.status,
1489 },
1490 {
1491 triggerMessage: true,
1492 },
1493 )
1494 .catch(Constants.EMPTY_FUNCTION);
1495 }
1496 }
1497 }
1498 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1499 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1500 default:
1501 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1502 }
1503 } catch (error) {
1504 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1505 chargingStation,
1506 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1507 error as Error,
1508 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1509 )!;
1510 }
1511 }
1512
1513 private handleRequestDataTransfer(
1514 chargingStation: ChargingStation,
1515 commandPayload: OCPP16DataTransferRequest,
1516 ): OCPP16DataTransferResponse {
1517 try {
1518 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1519 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1520 }
1521 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1522 } catch (error) {
1523 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1524 chargingStation,
1525 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1526 error as Error,
1527 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1528 )!;
1529 }
1530 }
1531
1532 private async handleRequestReserveNow(
1533 chargingStation: ChargingStation,
1534 commandPayload: OCPP16ReserveNowRequest,
1535 ): Promise<OCPP16ReserveNowResponse> {
1536 if (
1537 !OCPP16ServiceUtils.checkFeatureProfile(
1538 chargingStation,
1539 OCPP16SupportedFeatureProfiles.Reservation,
1540 OCPP16IncomingRequestCommand.RESERVE_NOW,
1541 )
1542 ) {
1543 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1544 }
1545 const { reservationId, idTag, connectorId } = commandPayload;
1546 let response: OCPP16ReserveNowResponse;
1547 try {
1548 if (!chargingStation.isConnectorAvailable(connectorId) && connectorId > 0) {
1549 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1550 }
1551 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
1552 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1553 }
1554 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1555 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1556 }
1557 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1558 case OCPP16ChargePointStatus.Faulted:
1559 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1560 break;
1561 case OCPP16ChargePointStatus.Preparing:
1562 case OCPP16ChargePointStatus.Charging:
1563 case OCPP16ChargePointStatus.SuspendedEV:
1564 case OCPP16ChargePointStatus.SuspendedEVSE:
1565 case OCPP16ChargePointStatus.Finishing:
1566 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1567 break;
1568 case OCPP16ChargePointStatus.Unavailable:
1569 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1570 break;
1571 case OCPP16ChargePointStatus.Reserved:
1572 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1573 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1574 break;
1575 }
1576 // eslint-disable-next-line no-fallthrough
1577 default:
1578 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1579 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1580 break;
1581 }
1582 await chargingStation.addReservation({
1583 id: commandPayload.reservationId,
1584 ...commandPayload,
1585 });
1586 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1587 break;
1588 }
1589 return response;
1590 } catch (error) {
1591 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1592 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1593 chargingStation,
1594 OCPP16IncomingRequestCommand.RESERVE_NOW,
1595 error as Error,
1596 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1597 )!;
1598 }
1599 }
1600
1601 private async handleRequestCancelReservation(
1602 chargingStation: ChargingStation,
1603 commandPayload: OCPP16CancelReservationRequest,
1604 ): Promise<GenericResponse> {
1605 if (
1606 !OCPP16ServiceUtils.checkFeatureProfile(
1607 chargingStation,
1608 OCPP16SupportedFeatureProfiles.Reservation,
1609 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1610 )
1611 ) {
1612 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1613 }
1614 try {
1615 const { reservationId } = commandPayload;
1616 const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
1617 if (!exists) {
1618 logger.error(
1619 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1620 does not exist on charging station`,
1621 );
1622 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1623 }
1624 await chargingStation.removeReservation(
1625 reservation!,
1626 ReservationTerminationReason.RESERVATION_CANCELED,
1627 );
1628 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1629 } catch (error) {
1630 return this.handleIncomingRequestError<GenericResponse>(
1631 chargingStation,
1632 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1633 error as Error,
1634 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1635 )!;
1636 }
1637 }
1638 }