refactor: cleanup null checks and helpers
[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 (isUndefined(commandPayload.key) === true) {
476 for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) {
477 if (isUndefined(configuration.visible) === true) {
478 configuration.visible = true;
479 }
480 if (configuration.visible === false) {
481 continue;
482 }
483 configurationKey.push({
484 key: configuration.key,
485 readonly: configuration.readonly,
486 value: configuration.value,
487 });
488 }
489 } else if (isNotEmptyArray(commandPayload.key) === true) {
490 for (const key of commandPayload.key!) {
491 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
492 chargingStation,
493 key,
494 true,
495 );
496 if (keyFound) {
497 if (isUndefined(keyFound.visible) === true) {
498 keyFound.visible = true;
499 }
500 if (keyFound.visible === false) {
501 continue;
502 }
503 configurationKey.push({
504 key: keyFound.key,
505 readonly: keyFound.readonly,
506 value: keyFound.value,
507 });
508 } else {
509 unknownKey.push(key);
510 }
511 }
512 }
513 return {
514 configurationKey,
515 unknownKey,
516 };
517 }
518
519 private handleRequestChangeConfiguration(
520 chargingStation: ChargingStation,
521 commandPayload: ChangeConfigurationRequest,
522 ): ChangeConfigurationResponse {
523 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
524 chargingStation,
525 commandPayload.key,
526 true,
527 );
528 if (keyToChange?.readonly === true) {
529 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
530 } else if (keyToChange?.readonly === false) {
531 let valueChanged = false;
532 if (keyToChange.value !== commandPayload.value) {
533 ChargingStationConfigurationUtils.setConfigurationKeyValue(
534 chargingStation,
535 commandPayload.key,
536 commandPayload.value,
537 true,
538 );
539 valueChanged = true;
540 }
541 let triggerHeartbeatRestart = false;
542 if (
543 (keyToChange.key as OCPP16StandardParametersKey) ===
544 OCPP16StandardParametersKey.HeartBeatInterval &&
545 valueChanged
546 ) {
547 ChargingStationConfigurationUtils.setConfigurationKeyValue(
548 chargingStation,
549 OCPP16StandardParametersKey.HeartbeatInterval,
550 commandPayload.value,
551 );
552 triggerHeartbeatRestart = true;
553 }
554 if (
555 (keyToChange.key as OCPP16StandardParametersKey) ===
556 OCPP16StandardParametersKey.HeartbeatInterval &&
557 valueChanged
558 ) {
559 ChargingStationConfigurationUtils.setConfigurationKeyValue(
560 chargingStation,
561 OCPP16StandardParametersKey.HeartBeatInterval,
562 commandPayload.value,
563 );
564 triggerHeartbeatRestart = true;
565 }
566 if (triggerHeartbeatRestart) {
567 chargingStation.restartHeartbeat();
568 }
569 if (
570 (keyToChange.key as OCPP16StandardParametersKey) ===
571 OCPP16StandardParametersKey.WebSocketPingInterval &&
572 valueChanged
573 ) {
574 chargingStation.restartWebSocketPing();
575 }
576 if (keyToChange.reboot) {
577 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
578 }
579 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
580 }
581 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
582 }
583
584 private handleRequestSetChargingProfile(
585 chargingStation: ChargingStation,
586 commandPayload: SetChargingProfileRequest,
587 ): SetChargingProfileResponse {
588 if (
589 OCPP16ServiceUtils.checkFeatureProfile(
590 chargingStation,
591 OCPP16SupportedFeatureProfiles.SmartCharging,
592 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
593 ) === false
594 ) {
595 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
596 }
597 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
598 logger.error(
599 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
600 non existing connector id ${commandPayload.connectorId}`,
601 );
602 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
603 }
604 if (
605 commandPayload.csChargingProfiles.chargingProfilePurpose ===
606 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
607 commandPayload.connectorId !== 0
608 ) {
609 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
610 }
611 if (
612 commandPayload.csChargingProfiles.chargingProfilePurpose ===
613 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
614 (commandPayload.connectorId === 0 ||
615 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
616 false)
617 ) {
618 logger.error(
619 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
620 on connector ${commandPayload.connectorId} without a started transaction`,
621 );
622 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
623 }
624 OCPP16ServiceUtils.setChargingProfile(
625 chargingStation,
626 commandPayload.connectorId,
627 commandPayload.csChargingProfiles,
628 );
629 logger.debug(
630 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
631 commandPayload.connectorId
632 }: %j`,
633 commandPayload.csChargingProfiles,
634 );
635 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
636 }
637
638 private handleRequestGetCompositeSchedule(
639 chargingStation: ChargingStation,
640 commandPayload: OCPP16GetCompositeScheduleRequest,
641 ): OCPP16GetCompositeScheduleResponse {
642 if (
643 OCPP16ServiceUtils.checkFeatureProfile(
644 chargingStation,
645 OCPP16SupportedFeatureProfiles.SmartCharging,
646 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
647 ) === false
648 ) {
649 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
650 }
651 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
652 logger.error(
653 `${chargingStation.logPrefix()} Trying to get composite schedule to a
654 non existing connector id ${commandPayload.connectorId}`,
655 );
656 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
657 }
658 if (
659 isEmptyArray(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles)
660 ) {
661 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
662 }
663 const startDate = new Date();
664 const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000);
665 let compositeSchedule: OCPP16ChargingSchedule | undefined;
666 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)!
667 .chargingProfiles!) {
668 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
669 if (
670 chargingProfile.chargingSchedule.startSchedule! >= startDate &&
671 chargingProfile.chargingSchedule.startSchedule! <= endDate
672 ) {
673 compositeSchedule = chargingProfile.chargingSchedule;
674 break;
675 }
676 }
677 return {
678 status: GenericStatus.Accepted,
679 scheduleStart: compositeSchedule?.startSchedule,
680 connectorId: commandPayload.connectorId,
681 chargingSchedule: compositeSchedule,
682 };
683 }
684
685 private handleRequestClearChargingProfile(
686 chargingStation: ChargingStation,
687 commandPayload: ClearChargingProfileRequest,
688 ): ClearChargingProfileResponse {
689 if (
690 OCPP16ServiceUtils.checkFeatureProfile(
691 chargingStation,
692 OCPP16SupportedFeatureProfiles.SmartCharging,
693 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
694 ) === false
695 ) {
696 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
697 }
698 if (chargingStation.hasConnector(commandPayload.connectorId!) === false) {
699 logger.error(
700 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
701 a non existing connector id ${commandPayload.connectorId}`,
702 );
703 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
704 }
705 if (
706 !isNullOrUndefined(commandPayload.connectorId) &&
707 isNotEmptyArray(
708 chargingStation.getConnectorStatus(commandPayload.connectorId!)?.chargingProfiles,
709 )
710 ) {
711 chargingStation.getConnectorStatus(commandPayload.connectorId!)!.chargingProfiles = [];
712 logger.debug(
713 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
714 commandPayload.connectorId
715 }`,
716 );
717 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
718 }
719 if (isNullOrUndefined(commandPayload.connectorId)) {
720 let clearedCP = false;
721 const clearChargingProfiles = (connectorStatus: ConnectorStatus) => {
722 if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
723 connectorStatus?.chargingProfiles?.forEach(
724 (chargingProfile: OCPP16ChargingProfile, index: number) => {
725 let clearCurrentCP = false;
726 if (chargingProfile.chargingProfileId === commandPayload.id) {
727 clearCurrentCP = true;
728 }
729 if (
730 !commandPayload.chargingProfilePurpose &&
731 chargingProfile.stackLevel === commandPayload.stackLevel
732 ) {
733 clearCurrentCP = true;
734 }
735 if (
736 !chargingProfile.stackLevel &&
737 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
738 ) {
739 clearCurrentCP = true;
740 }
741 if (
742 chargingProfile.stackLevel === commandPayload.stackLevel &&
743 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
744 ) {
745 clearCurrentCP = true;
746 }
747 if (clearCurrentCP) {
748 connectorStatus?.chargingProfiles?.splice(index, 1);
749 logger.debug(
750 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
751 chargingProfile,
752 );
753 clearedCP = true;
754 }
755 },
756 );
757 }
758 };
759 if (chargingStation.hasEvses) {
760 for (const evseStatus of chargingStation.evses.values()) {
761 for (const connectorStatus of evseStatus.connectors.values()) {
762 clearChargingProfiles(connectorStatus);
763 }
764 }
765 } else {
766 for (const connectorId of chargingStation.connectors.keys()) {
767 clearChargingProfiles(chargingStation.getConnectorStatus(connectorId)!);
768 }
769 }
770 if (clearedCP) {
771 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
772 }
773 }
774 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
775 }
776
777 private async handleRequestChangeAvailability(
778 chargingStation: ChargingStation,
779 commandPayload: ChangeAvailabilityRequest,
780 ): Promise<ChangeAvailabilityResponse> {
781 const connectorId: number = commandPayload.connectorId;
782 if (chargingStation.hasConnector(connectorId) === false) {
783 logger.error(
784 `${chargingStation.logPrefix()} Trying to change the availability of a
785 non existing connector id ${connectorId.toString()}`,
786 );
787 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
788 }
789 const chargePointStatus: OCPP16ChargePointStatus =
790 commandPayload.type === OCPP16AvailabilityType.Operative
791 ? OCPP16ChargePointStatus.Available
792 : OCPP16ChargePointStatus.Unavailable;
793 if (connectorId === 0) {
794 let response: ChangeAvailabilityResponse =
795 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
796 const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => {
797 if (connectorStatus?.transactionStarted === true) {
798 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
799 }
800 connectorStatus.availability = commandPayload.type;
801 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
802 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
803 chargingStation,
804 id,
805 chargePointStatus,
806 );
807 }
808 };
809 if (chargingStation.hasEvses) {
810 for (const evseStatus of chargingStation.evses.values()) {
811 for (const [id, connectorStatus] of evseStatus.connectors) {
812 await changeAvailability(id, connectorStatus);
813 }
814 }
815 } else {
816 for (const id of chargingStation.connectors.keys()) {
817 await changeAvailability(id, chargingStation.getConnectorStatus(id)!);
818 }
819 }
820 return response;
821 } else if (
822 connectorId > 0 &&
823 (chargingStation.isChargingStationAvailable() === true ||
824 (chargingStation.isChargingStationAvailable() === false &&
825 commandPayload.type === OCPP16AvailabilityType.Inoperative))
826 ) {
827 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
828 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
829 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
830 }
831 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
832 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
833 chargingStation,
834 connectorId,
835 chargePointStatus,
836 );
837 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
838 }
839 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
840 }
841
842 private async handleRequestRemoteStartTransaction(
843 chargingStation: ChargingStation,
844 commandPayload: RemoteStartTransactionRequest,
845 ): Promise<GenericResponse> {
846 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
847 const reserved =
848 chargingStation.getConnectorStatus(transactionConnectorId)!.status ===
849 OCPP16ChargePointStatus.Reserved;
850 const reservedOnConnectorZero =
851 chargingStation.getConnectorStatus(0)!.status === OCPP16ChargePointStatus.Reserved;
852 if (
853 (reserved &&
854 !chargingStation.validateIncomingRequestWithReservation(transactionConnectorId, idTag)) ||
855 (reservedOnConnectorZero && !chargingStation.validateIncomingRequestWithReservation(0, idTag))
856 ) {
857 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
858 }
859 if (chargingStation.hasConnector(transactionConnectorId) === false) {
860 return this.notifyRemoteStartTransactionRejected(
861 chargingStation,
862 transactionConnectorId,
863 idTag,
864 );
865 }
866 if (
867 !chargingStation.isChargingStationAvailable() ||
868 !chargingStation.isConnectorAvailable(transactionConnectorId)
869 ) {
870 return this.notifyRemoteStartTransactionRejected(
871 chargingStation,
872 transactionConnectorId,
873 idTag,
874 );
875 }
876 const remoteStartTransactionLogMsg = `
877 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
878 chargingStation.stationInfo.chargingStationId
879 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
880 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
881 chargingStation,
882 transactionConnectorId,
883 OCPP16ChargePointStatus.Preparing,
884 );
885 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
886 // Check if authorized
887 if (
888 chargingStation.getAuthorizeRemoteTxRequests() &&
889 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
890 ) {
891 // Authorization successful, start transaction
892 if (
893 this.setRemoteStartTransactionChargingProfile(
894 chargingStation,
895 transactionConnectorId,
896 chargingProfile!,
897 ) === true
898 ) {
899 connectorStatus.transactionRemoteStarted = true;
900 const startTransactionPayload: Partial<StartTransactionRequest> = {
901 connectorId: transactionConnectorId,
902 idTag,
903 };
904 if (reserved || reservedOnConnectorZero) {
905 const reservation = chargingStation.getReservationBy(
906 ReservationFilterKey.CONNECTOR_ID,
907 reservedOnConnectorZero ? 0 : transactionConnectorId,
908 )!;
909 startTransactionPayload.reservationId = reservation.id;
910 await chargingStation.removeReservation(
911 reservation,
912 ReservationTerminationReason.TRANSACTION_STARTED,
913 );
914 }
915 if (
916 (
917 await chargingStation.ocppRequestService.requestHandler<
918 OCPP16StartTransactionRequest,
919 OCPP16StartTransactionResponse
920 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, startTransactionPayload)
921 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
922 ) {
923 logger.debug(remoteStartTransactionLogMsg);
924 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
925 }
926 return this.notifyRemoteStartTransactionRejected(
927 chargingStation,
928 transactionConnectorId,
929 idTag,
930 );
931 }
932 return this.notifyRemoteStartTransactionRejected(
933 chargingStation,
934 transactionConnectorId,
935 idTag,
936 );
937 }
938 // No authorization check required, start transaction
939 if (
940 this.setRemoteStartTransactionChargingProfile(
941 chargingStation,
942 transactionConnectorId,
943 chargingProfile!,
944 ) === true
945 ) {
946 connectorStatus.transactionRemoteStarted = true;
947 if (
948 (
949 await chargingStation.ocppRequestService.requestHandler<
950 OCPP16StartTransactionRequest,
951 OCPP16StartTransactionResponse
952 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
953 connectorId: transactionConnectorId,
954 idTag,
955 })
956 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
957 ) {
958 logger.debug(remoteStartTransactionLogMsg);
959 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
960 }
961 return this.notifyRemoteStartTransactionRejected(
962 chargingStation,
963 transactionConnectorId,
964 idTag,
965 );
966 }
967 return this.notifyRemoteStartTransactionRejected(
968 chargingStation,
969 transactionConnectorId,
970 idTag,
971 );
972 }
973
974 private async notifyRemoteStartTransactionRejected(
975 chargingStation: ChargingStation,
976 connectorId: number,
977 idTag: string,
978 ): Promise<GenericResponse> {
979 if (
980 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
981 ) {
982 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
983 chargingStation,
984 connectorId,
985 OCPP16ChargePointStatus.Available,
986 );
987 }
988 logger.warn(
989 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
990 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
991 connectorId,
992 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
993 );
994 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
995 }
996
997 private setRemoteStartTransactionChargingProfile(
998 chargingStation: ChargingStation,
999 connectorId: number,
1000 cp: OCPP16ChargingProfile,
1001 ): boolean {
1002 if (cp && cp.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1003 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
1004 logger.debug(
1005 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
1006 on connector id ${connectorId}: %j`,
1007 cp,
1008 );
1009 return true;
1010 } else if (cp && cp.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1011 logger.warn(
1012 `${chargingStation.logPrefix()} Not allowed to set ${
1013 cp.chargingProfilePurpose
1014 } charging profile(s) at remote start transaction`,
1015 );
1016 return false;
1017 }
1018 return true;
1019 }
1020
1021 private async handleRequestRemoteStopTransaction(
1022 chargingStation: ChargingStation,
1023 commandPayload: RemoteStopTransactionRequest,
1024 ): Promise<GenericResponse> {
1025 const transactionId = commandPayload.transactionId;
1026 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1027 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1028 chargingStation,
1029 connectorId,
1030 OCPP16ChargePointStatus.Finishing,
1031 );
1032 const stopResponse = await chargingStation.stopTransactionOnConnector(
1033 connectorId,
1034 OCPP16StopTransactionReason.REMOTE,
1035 );
1036 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1037 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1038 }
1039 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1040 };
1041 if (chargingStation.hasEvses) {
1042 for (const [evseId, evseStatus] of chargingStation.evses) {
1043 if (evseId > 0) {
1044 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1045 if (connectorStatus.transactionId === transactionId) {
1046 return remoteStopTransaction(connectorId);
1047 }
1048 }
1049 }
1050 }
1051 } else {
1052 for (const connectorId of chargingStation.connectors.keys()) {
1053 if (
1054 connectorId > 0 &&
1055 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1056 ) {
1057 return remoteStopTransaction(connectorId);
1058 }
1059 }
1060 }
1061 logger.warn(
1062 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1063 ${transactionId.toString()}`,
1064 );
1065 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1066 }
1067
1068 private handleRequestUpdateFirmware(
1069 chargingStation: ChargingStation,
1070 commandPayload: OCPP16UpdateFirmwareRequest,
1071 ): OCPP16UpdateFirmwareResponse {
1072 if (
1073 OCPP16ServiceUtils.checkFeatureProfile(
1074 chargingStation,
1075 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1076 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1077 ) === false
1078 ) {
1079 logger.warn(
1080 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1081 Cannot simulate firmware update: feature profile not supported`,
1082 );
1083 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1084 }
1085 if (
1086 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1087 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1088 ) {
1089 logger.warn(
1090 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1091 Cannot simulate firmware update: firmware update is already in progress`,
1092 );
1093 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1094 }
1095 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1096 const now = Date.now();
1097 if (retrieveDate?.getTime() <= now) {
1098 this.runInAsyncScope(
1099 this.updateFirmwareSimulation.bind(this) as (
1100 this: OCPP16IncomingRequestService,
1101 ...args: unknown[]
1102 ) => Promise<void>,
1103 this,
1104 chargingStation,
1105 ).catch(Constants.EMPTY_FUNCTION);
1106 } else {
1107 setTimeout(
1108 () => {
1109 this.runInAsyncScope(
1110 this.updateFirmwareSimulation.bind(this) as (
1111 this: OCPP16IncomingRequestService,
1112 ...args: unknown[]
1113 ) => Promise<void>,
1114 this,
1115 chargingStation,
1116 ).catch(Constants.EMPTY_FUNCTION);
1117 },
1118 retrieveDate?.getTime() - now,
1119 );
1120 }
1121 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1122 }
1123
1124 private async updateFirmwareSimulation(
1125 chargingStation: ChargingStation,
1126 maxDelay = 30,
1127 minDelay = 15,
1128 ): Promise<void> {
1129 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1130 return;
1131 }
1132 if (chargingStation.hasEvses) {
1133 for (const [evseId, evseStatus] of chargingStation.evses) {
1134 if (evseId > 0) {
1135 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1136 if (connectorStatus?.transactionStarted === false) {
1137 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1138 chargingStation,
1139 connectorId,
1140 OCPP16ChargePointStatus.Unavailable,
1141 );
1142 }
1143 }
1144 }
1145 }
1146 } else {
1147 for (const connectorId of chargingStation.connectors.keys()) {
1148 if (
1149 connectorId > 0 &&
1150 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1151 ) {
1152 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1153 chargingStation,
1154 connectorId,
1155 OCPP16ChargePointStatus.Unavailable,
1156 );
1157 }
1158 }
1159 }
1160 await chargingStation.ocppRequestService.requestHandler<
1161 OCPP16FirmwareStatusNotificationRequest,
1162 OCPP16FirmwareStatusNotificationResponse
1163 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1164 status: OCPP16FirmwareStatus.Downloading,
1165 });
1166 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1167 if (
1168 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1169 OCPP16FirmwareStatus.DownloadFailed
1170 ) {
1171 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1172 await chargingStation.ocppRequestService.requestHandler<
1173 OCPP16FirmwareStatusNotificationRequest,
1174 OCPP16FirmwareStatusNotificationResponse
1175 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1176 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1177 });
1178 chargingStation.stationInfo.firmwareStatus =
1179 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1180 return;
1181 }
1182 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1183 await chargingStation.ocppRequestService.requestHandler<
1184 OCPP16FirmwareStatusNotificationRequest,
1185 OCPP16FirmwareStatusNotificationResponse
1186 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1187 status: OCPP16FirmwareStatus.Downloaded,
1188 });
1189 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1190 let wasTransactionsStarted = false;
1191 let transactionsStarted: boolean;
1192 do {
1193 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1194 if (runningTransactions > 0) {
1195 const waitTime = 15 * 1000;
1196 logger.debug(
1197 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1198 ${runningTransactions} transaction(s) in progress, waiting ${
1199 waitTime / 1000
1200 } seconds before continuing firmware update simulation`,
1201 );
1202 await sleep(waitTime);
1203 transactionsStarted = true;
1204 wasTransactionsStarted = true;
1205 } else {
1206 if (chargingStation.hasEvses) {
1207 for (const [evseId, evseStatus] of chargingStation.evses) {
1208 if (evseId > 0) {
1209 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1210 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1211 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1212 chargingStation,
1213 connectorId,
1214 OCPP16ChargePointStatus.Unavailable,
1215 );
1216 }
1217 }
1218 }
1219 }
1220 } else {
1221 for (const connectorId of chargingStation.connectors.keys()) {
1222 if (
1223 connectorId > 0 &&
1224 chargingStation.getConnectorStatus(connectorId)?.status !==
1225 OCPP16ChargePointStatus.Unavailable
1226 ) {
1227 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1228 chargingStation,
1229 connectorId,
1230 OCPP16ChargePointStatus.Unavailable,
1231 );
1232 }
1233 }
1234 }
1235 transactionsStarted = false;
1236 }
1237 } while (transactionsStarted);
1238 !wasTransactionsStarted && (await sleep(getRandomInteger(maxDelay, minDelay) * 1000));
1239 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1240 return;
1241 }
1242 await chargingStation.ocppRequestService.requestHandler<
1243 OCPP16FirmwareStatusNotificationRequest,
1244 OCPP16FirmwareStatusNotificationResponse
1245 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1246 status: OCPP16FirmwareStatus.Installing,
1247 });
1248 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1249 if (
1250 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1251 OCPP16FirmwareStatus.InstallationFailed
1252 ) {
1253 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1254 await chargingStation.ocppRequestService.requestHandler<
1255 OCPP16FirmwareStatusNotificationRequest,
1256 OCPP16FirmwareStatusNotificationResponse
1257 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1258 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1259 });
1260 chargingStation.stationInfo.firmwareStatus =
1261 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1262 return;
1263 }
1264 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1265 await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
1266 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1267 }
1268 }
1269
1270 private async handleRequestGetDiagnostics(
1271 chargingStation: ChargingStation,
1272 commandPayload: GetDiagnosticsRequest,
1273 ): Promise<GetDiagnosticsResponse> {
1274 if (
1275 OCPP16ServiceUtils.checkFeatureProfile(
1276 chargingStation,
1277 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1278 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1279 ) === false
1280 ) {
1281 logger.warn(
1282 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1283 Cannot get diagnostics: feature profile not supported`,
1284 );
1285 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1286 }
1287 const uri = new URL(commandPayload.location);
1288 if (uri.protocol.startsWith('ftp:')) {
1289 let ftpClient: Client | undefined;
1290 try {
1291 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1292 .filter((file) => file.endsWith('.log'))
1293 .map((file) => join('./', file));
1294 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1295 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1296 ftpClient = new Client();
1297 const accessResponse = await ftpClient.access({
1298 host: uri.host,
1299 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1300 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1301 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1302 });
1303 let uploadResponse: FTPResponse | undefined;
1304 if (accessResponse.code === 220) {
1305 ftpClient.trackProgress((info) => {
1306 logger.info(
1307 `${chargingStation.logPrefix()} ${
1308 info.bytes / 1024
1309 } bytes transferred from diagnostics archive ${info.name}`,
1310 );
1311 chargingStation.ocppRequestService
1312 .requestHandler<
1313 OCPP16DiagnosticsStatusNotificationRequest,
1314 OCPP16DiagnosticsStatusNotificationResponse
1315 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1316 status: OCPP16DiagnosticsStatus.Uploading,
1317 })
1318 .catch((error) => {
1319 logger.error(
1320 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1321 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1322 error,
1323 );
1324 });
1325 });
1326 uploadResponse = await ftpClient.uploadFrom(
1327 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1328 `${uri.pathname}${diagnosticsArchive}`,
1329 );
1330 if (uploadResponse.code === 226) {
1331 await chargingStation.ocppRequestService.requestHandler<
1332 OCPP16DiagnosticsStatusNotificationRequest,
1333 OCPP16DiagnosticsStatusNotificationResponse
1334 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1335 status: OCPP16DiagnosticsStatus.Uploaded,
1336 });
1337 if (ftpClient) {
1338 ftpClient.close();
1339 }
1340 return { fileName: diagnosticsArchive };
1341 }
1342 throw new OCPPError(
1343 ErrorType.GENERIC_ERROR,
1344 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1345 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1346 }`,
1347 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1348 );
1349 }
1350 throw new OCPPError(
1351 ErrorType.GENERIC_ERROR,
1352 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1353 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1354 }`,
1355 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1356 );
1357 } catch (error) {
1358 await chargingStation.ocppRequestService.requestHandler<
1359 OCPP16DiagnosticsStatusNotificationRequest,
1360 OCPP16DiagnosticsStatusNotificationResponse
1361 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1362 status: OCPP16DiagnosticsStatus.UploadFailed,
1363 });
1364 if (ftpClient) {
1365 ftpClient.close();
1366 }
1367 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1368 chargingStation,
1369 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1370 error as Error,
1371 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1372 )!;
1373 }
1374 } else {
1375 logger.error(
1376 `${chargingStation.logPrefix()} Unsupported protocol ${
1377 uri.protocol
1378 } to transfer the diagnostic logs archive`,
1379 );
1380 await chargingStation.ocppRequestService.requestHandler<
1381 OCPP16DiagnosticsStatusNotificationRequest,
1382 OCPP16DiagnosticsStatusNotificationResponse
1383 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1384 status: OCPP16DiagnosticsStatus.UploadFailed,
1385 });
1386 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1387 }
1388 }
1389
1390 private handleRequestTriggerMessage(
1391 chargingStation: ChargingStation,
1392 commandPayload: OCPP16TriggerMessageRequest,
1393 ): OCPP16TriggerMessageResponse {
1394 if (
1395 !OCPP16ServiceUtils.checkFeatureProfile(
1396 chargingStation,
1397 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1398 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1399 ) ||
1400 !OCPP16ServiceUtils.isMessageTriggerSupported(
1401 chargingStation,
1402 commandPayload.requestedMessage,
1403 )
1404 ) {
1405 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1406 }
1407 if (
1408 !OCPP16ServiceUtils.isConnectorIdValid(
1409 chargingStation,
1410 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1411 commandPayload.connectorId!,
1412 )
1413 ) {
1414 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1415 }
1416 try {
1417 switch (commandPayload.requestedMessage) {
1418 case OCPP16MessageTrigger.BootNotification:
1419 setTimeout(() => {
1420 chargingStation.ocppRequestService
1421 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1422 chargingStation,
1423 OCPP16RequestCommand.BOOT_NOTIFICATION,
1424 chargingStation.bootNotificationRequest,
1425 { skipBufferingOnError: true, triggerMessage: true },
1426 )
1427 .then((response) => {
1428 chargingStation.bootNotificationResponse = response;
1429 })
1430 .catch(Constants.EMPTY_FUNCTION);
1431 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1432 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1433 case OCPP16MessageTrigger.Heartbeat:
1434 setTimeout(() => {
1435 chargingStation.ocppRequestService
1436 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1437 chargingStation,
1438 OCPP16RequestCommand.HEARTBEAT,
1439 null,
1440 {
1441 triggerMessage: true,
1442 },
1443 )
1444 .catch(Constants.EMPTY_FUNCTION);
1445 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1446 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1447 case OCPP16MessageTrigger.StatusNotification:
1448 setTimeout(() => {
1449 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1450 chargingStation.ocppRequestService
1451 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1452 chargingStation,
1453 OCPP16RequestCommand.STATUS_NOTIFICATION,
1454 {
1455 connectorId: commandPayload.connectorId,
1456 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1457 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1458 },
1459 {
1460 triggerMessage: true,
1461 },
1462 )
1463 .catch(Constants.EMPTY_FUNCTION);
1464 } else {
1465 // eslint-disable-next-line no-lonely-if
1466 if (chargingStation.hasEvses) {
1467 for (const evseStatus of chargingStation.evses.values()) {
1468 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1469 chargingStation.ocppRequestService
1470 .requestHandler<
1471 OCPP16StatusNotificationRequest,
1472 OCPP16StatusNotificationResponse
1473 >(
1474 chargingStation,
1475 OCPP16RequestCommand.STATUS_NOTIFICATION,
1476 {
1477 connectorId,
1478 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1479 status: connectorStatus.status,
1480 },
1481 {
1482 triggerMessage: true,
1483 },
1484 )
1485 .catch(Constants.EMPTY_FUNCTION);
1486 }
1487 }
1488 } else {
1489 for (const connectorId of chargingStation.connectors.keys()) {
1490 chargingStation.ocppRequestService
1491 .requestHandler<
1492 OCPP16StatusNotificationRequest,
1493 OCPP16StatusNotificationResponse
1494 >(
1495 chargingStation,
1496 OCPP16RequestCommand.STATUS_NOTIFICATION,
1497 {
1498 connectorId,
1499 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1500 status: chargingStation.getConnectorStatus(connectorId)?.status,
1501 },
1502 {
1503 triggerMessage: true,
1504 },
1505 )
1506 .catch(Constants.EMPTY_FUNCTION);
1507 }
1508 }
1509 }
1510 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1511 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1512 default:
1513 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1514 }
1515 } catch (error) {
1516 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1517 chargingStation,
1518 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1519 error as Error,
1520 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1521 )!;
1522 }
1523 }
1524
1525 private handleRequestDataTransfer(
1526 chargingStation: ChargingStation,
1527 commandPayload: OCPP16DataTransferRequest,
1528 ): OCPP16DataTransferResponse {
1529 try {
1530 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1531 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1532 }
1533 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1534 } catch (error) {
1535 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1536 chargingStation,
1537 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1538 error as Error,
1539 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1540 )!;
1541 }
1542 }
1543
1544 private async handleRequestReserveNow(
1545 chargingStation: ChargingStation,
1546 commandPayload: OCPP16ReserveNowRequest,
1547 ): Promise<OCPP16ReserveNowResponse> {
1548 if (
1549 !OCPP16ServiceUtils.checkFeatureProfile(
1550 chargingStation,
1551 OCPP16SupportedFeatureProfiles.Reservation,
1552 OCPP16IncomingRequestCommand.RESERVE_NOW,
1553 )
1554 ) {
1555 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1556 }
1557 const { reservationId, idTag, connectorId } = commandPayload;
1558 let response: OCPP16ReserveNowResponse;
1559 try {
1560 if (!chargingStation.isConnectorAvailable(connectorId) && connectorId > 0) {
1561 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1562 }
1563 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
1564 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1565 }
1566 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1567 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1568 }
1569 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1570 case OCPP16ChargePointStatus.Faulted:
1571 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1572 break;
1573 case OCPP16ChargePointStatus.Preparing:
1574 case OCPP16ChargePointStatus.Charging:
1575 case OCPP16ChargePointStatus.SuspendedEV:
1576 case OCPP16ChargePointStatus.SuspendedEVSE:
1577 case OCPP16ChargePointStatus.Finishing:
1578 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1579 break;
1580 case OCPP16ChargePointStatus.Unavailable:
1581 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1582 break;
1583 case OCPP16ChargePointStatus.Reserved:
1584 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1585 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1586 break;
1587 }
1588 // eslint-disable-next-line no-fallthrough
1589 default:
1590 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1591 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1592 break;
1593 }
1594 await chargingStation.addReservation({
1595 id: commandPayload.reservationId,
1596 ...commandPayload,
1597 });
1598 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1599 break;
1600 }
1601 return response;
1602 } catch (error) {
1603 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1604 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1605 chargingStation,
1606 OCPP16IncomingRequestCommand.RESERVE_NOW,
1607 error as Error,
1608 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1609 )!;
1610 }
1611 }
1612
1613 private async handleRequestCancelReservation(
1614 chargingStation: ChargingStation,
1615 commandPayload: OCPP16CancelReservationRequest,
1616 ): Promise<GenericResponse> {
1617 if (
1618 !OCPP16ServiceUtils.checkFeatureProfile(
1619 chargingStation,
1620 OCPP16SupportedFeatureProfiles.Reservation,
1621 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1622 )
1623 ) {
1624 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1625 }
1626 try {
1627 const { reservationId } = commandPayload;
1628 const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
1629 if (!exists) {
1630 logger.error(
1631 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1632 does not exist on charging station`,
1633 );
1634 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1635 }
1636 await chargingStation.removeReservation(
1637 reservation!,
1638 ReservationTerminationReason.RESERVATION_CANCELED,
1639 );
1640 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1641 } catch (error) {
1642 return this.handleIncomingRequestError<GenericResponse>(
1643 chargingStation,
1644 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1645 error as Error,
1646 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1647 )!;
1648 }
1649 }
1650 }