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