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