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