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