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