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