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