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