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