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