fix: fix reservable connector detection
[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 { secondsToMilliseconds } from 'date-fns';
10 import { create } from 'tar';
11
12 import { OCPP16Constants } from './OCPP16Constants';
13 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
14 import {
15 type ChargingStation,
16 checkChargingStation,
17 getConfigurationKey,
18 setConfigurationKeyValue,
19 } from '../../../charging-station';
20 import { OCPPError } from '../../../exception';
21 import {
22 type ChangeAvailabilityRequest,
23 type ChangeAvailabilityResponse,
24 type ChangeConfigurationRequest,
25 type ChangeConfigurationResponse,
26 type ClearChargingProfileRequest,
27 type ClearChargingProfileResponse,
28 type ConnectorStatus,
29 ErrorType,
30 type GenericResponse,
31 GenericStatus,
32 type GetConfigurationRequest,
33 type GetConfigurationResponse,
34 type GetDiagnosticsRequest,
35 type GetDiagnosticsResponse,
36 type IncomingRequestHandler,
37 type JsonObject,
38 type JsonType,
39 OCPP16AuthorizationStatus,
40 OCPP16AvailabilityType,
41 type OCPP16BootNotificationRequest,
42 type OCPP16BootNotificationResponse,
43 type OCPP16CancelReservationRequest,
44 OCPP16ChargePointErrorCode,
45 OCPP16ChargePointStatus,
46 type OCPP16ChargingProfile,
47 OCPP16ChargingProfilePurposeType,
48 type OCPP16ChargingSchedule,
49 type OCPP16ClearCacheRequest,
50 type OCPP16DataTransferRequest,
51 type OCPP16DataTransferResponse,
52 OCPP16DataTransferVendorId,
53 OCPP16DiagnosticsStatus,
54 type OCPP16DiagnosticsStatusNotificationRequest,
55 type OCPP16DiagnosticsStatusNotificationResponse,
56 OCPP16FirmwareStatus,
57 type OCPP16FirmwareStatusNotificationRequest,
58 type OCPP16FirmwareStatusNotificationResponse,
59 type OCPP16GetCompositeScheduleRequest,
60 type OCPP16GetCompositeScheduleResponse,
61 type OCPP16HeartbeatRequest,
62 type OCPP16HeartbeatResponse,
63 OCPP16IncomingRequestCommand,
64 OCPP16MessageTrigger,
65 OCPP16RequestCommand,
66 type OCPP16ReserveNowRequest,
67 type OCPP16ReserveNowResponse,
68 OCPP16StandardParametersKey,
69 type OCPP16StartTransactionRequest,
70 type OCPP16StartTransactionResponse,
71 type OCPP16StatusNotificationRequest,
72 type OCPP16StatusNotificationResponse,
73 OCPP16StopTransactionReason,
74 OCPP16SupportedFeatureProfiles,
75 type OCPP16TriggerMessageRequest,
76 type OCPP16TriggerMessageResponse,
77 type OCPP16UpdateFirmwareRequest,
78 type OCPP16UpdateFirmwareResponse,
79 type OCPPConfigurationKey,
80 OCPPVersion,
81 type RemoteStartTransactionRequest,
82 type RemoteStopTransactionRequest,
83 ReservationFilterKey,
84 ReservationTerminationReason,
85 type ResetRequest,
86 type SetChargingProfileRequest,
87 type SetChargingProfileResponse,
88 type StartTransactionRequest,
89 type UnlockConnectorRequest,
90 type UnlockConnectorResponse,
91 } from '../../../types';
92 import {
93 Constants,
94 convertToDate,
95 convertToInt,
96 formatDurationMilliSeconds,
97 getRandomInteger,
98 isEmptyArray,
99 isNotEmptyArray,
100 isNotEmptyString,
101 isNullOrUndefined,
102 isUndefined,
103 logger,
104 sleep,
105 } from '../../../utils';
106 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService';
107
108 const moduleName = 'OCPP16IncomingRequestService';
109
110 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
111 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
112 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
113
114 public constructor() {
115 // if (new.target?.name === moduleName) {
116 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
117 // }
118 super(OCPPVersion.VERSION_16);
119 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
120 [
121 OCPP16IncomingRequestCommand.RESET,
122 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler,
123 ],
124 [
125 OCPP16IncomingRequestCommand.CLEAR_CACHE,
126 this.handleRequestClearCache.bind(this) as IncomingRequestHandler,
127 ],
128 [
129 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
130 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler,
131 ],
132 [
133 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
134 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler,
135 ],
136 [
137 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
138 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler,
139 ],
140 [
141 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
142 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler,
143 ],
144 [
145 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
146 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler,
147 ],
148 [
149 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
150 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler,
151 ],
152 [
153 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
154 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler,
155 ],
156 [
157 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
158 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler,
159 ],
160 [
161 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
162 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler,
163 ],
164 [
165 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
166 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler,
167 ],
168 [
169 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
170 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler,
171 ],
172 [
173 OCPP16IncomingRequestCommand.DATA_TRANSFER,
174 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler,
175 ],
176 [
177 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
178 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler,
179 ],
180 [
181 OCPP16IncomingRequestCommand.RESERVE_NOW,
182 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler,
183 ],
184 [
185 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
186 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler,
187 ],
188 ]);
189 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
190 [
191 OCPP16IncomingRequestCommand.RESET,
192 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
193 'assets/json-schemas/ocpp/1.6/Reset.json',
194 moduleName,
195 'constructor',
196 ),
197 ],
198 [
199 OCPP16IncomingRequestCommand.CLEAR_CACHE,
200 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
201 'assets/json-schemas/ocpp/1.6/ClearCache.json',
202 moduleName,
203 'constructor',
204 ),
205 ],
206 [
207 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
208 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
209 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
210 moduleName,
211 'constructor',
212 ),
213 ],
214 [
215 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
216 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
217 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
218 moduleName,
219 'constructor',
220 ),
221 ],
222 [
223 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
224 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
225 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
226 moduleName,
227 'constructor',
228 ),
229 ],
230 [
231 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
232 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
233 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
234 moduleName,
235 'constructor',
236 ),
237 ],
238 [
239 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
240 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
241 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
242 moduleName,
243 'constructor',
244 ),
245 ],
246 [
247 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
248 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
249 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
250 moduleName,
251 'constructor',
252 ),
253 ],
254 [
255 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
256 OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileRequest>(
257 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
258 moduleName,
259 'constructor',
260 ),
261 ],
262 [
263 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
264 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeAvailabilityRequest>(
265 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
266 moduleName,
267 'constructor',
268 ),
269 ],
270 [
271 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
272 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
273 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
274 moduleName,
275 'constructor',
276 ),
277 ],
278 [
279 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
280 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
281 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
282 moduleName,
283 'constructor',
284 ),
285 ],
286 [
287 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
288 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
289 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
290 moduleName,
291 'constructor',
292 ),
293 ],
294 [
295 OCPP16IncomingRequestCommand.DATA_TRANSFER,
296 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
297 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
298 moduleName,
299 'constructor',
300 ),
301 ],
302 [
303 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
304 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
305 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
306 moduleName,
307 'constructor',
308 ),
309 ],
310 [
311 OCPP16IncomingRequestCommand.RESERVE_NOW,
312 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
313 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
314 moduleName,
315 'constructor',
316 ),
317 ],
318 [
319 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
320 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
321 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
322 moduleName,
323 'constructor',
324 ),
325 ],
326 ]);
327 this.validatePayload = this.validatePayload.bind(this) as (
328 chargingStation: ChargingStation,
329 commandName: OCPP16IncomingRequestCommand,
330 commandPayload: JsonType,
331 ) => boolean;
332 }
333
334 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
335 chargingStation: ChargingStation,
336 messageId: string,
337 commandName: OCPP16IncomingRequestCommand,
338 commandPayload: ReqType,
339 ): Promise<void> {
340 let response: ResType;
341 if (
342 chargingStation.getOcppStrictCompliance() === true &&
343 chargingStation.inPendingState() === true &&
344 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
345 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
346 ) {
347 throw new OCPPError(
348 ErrorType.SECURITY_ERROR,
349 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
350 commandPayload,
351 null,
352 2,
353 )} while the charging station is in pending state on the central server`,
354 commandName,
355 commandPayload,
356 );
357 }
358 if (
359 chargingStation.isRegistered() === true ||
360 (chargingStation.getOcppStrictCompliance() === false &&
361 chargingStation.inUnknownState() === true)
362 ) {
363 if (
364 this.incomingRequestHandlers.has(commandName) === true &&
365 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
366 ) {
367 try {
368 this.validatePayload(chargingStation, commandName, commandPayload);
369 // Call the method to build the response
370 response = (await this.incomingRequestHandlers.get(commandName)!(
371 chargingStation,
372 commandPayload,
373 )) as ResType;
374 } catch (error) {
375 // Log
376 logger.error(
377 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
378 Handle incoming request error:`,
379 error,
380 );
381 throw error;
382 }
383 } else {
384 // Throw exception
385 throw new OCPPError(
386 ErrorType.NOT_IMPLEMENTED,
387 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
388 commandPayload,
389 null,
390 2,
391 )}`,
392 commandName,
393 commandPayload,
394 );
395 }
396 } else {
397 throw new OCPPError(
398 ErrorType.SECURITY_ERROR,
399 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
400 commandPayload,
401 null,
402 2,
403 )} while the charging station is not registered on the central server.`,
404 commandName,
405 commandPayload,
406 );
407 }
408 // Send the built response
409 await chargingStation.ocppRequestService.sendResponse(
410 chargingStation,
411 messageId,
412 response,
413 commandName,
414 );
415 }
416
417 private validatePayload(
418 chargingStation: ChargingStation,
419 commandName: OCPP16IncomingRequestCommand,
420 commandPayload: JsonType,
421 ): boolean {
422 if (this.jsonSchemas.has(commandName) === true) {
423 return this.validateIncomingRequestPayload(
424 chargingStation,
425 commandName,
426 this.jsonSchemas.get(commandName)!,
427 commandPayload,
428 );
429 }
430 logger.warn(
431 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
432 for command '${commandName}' PDU validation`,
433 );
434 return false;
435 }
436
437 // Simulate charging station restart
438 private handleRequestReset(
439 chargingStation: ChargingStation,
440 commandPayload: ResetRequest,
441 ): GenericResponse {
442 this.runInAsyncScope(
443 chargingStation.reset.bind(chargingStation) as (
444 this: ChargingStation,
445 ...args: unknown[]
446 ) => Promise<void>,
447 chargingStation,
448 `${commandPayload.type}Reset` as OCPP16StopTransactionReason,
449 ).catch(Constants.EMPTY_FUNCTION);
450 logger.info(
451 `${chargingStation.logPrefix()} ${
452 commandPayload.type
453 } reset command received, simulating it. The station will be
454 back online in ${formatDurationMilliSeconds(chargingStation.stationInfo.resetTime!)}`,
455 );
456 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
457 }
458
459 private async handleRequestUnlockConnector(
460 chargingStation: ChargingStation,
461 commandPayload: UnlockConnectorRequest,
462 ): Promise<UnlockConnectorResponse> {
463 const connectorId = commandPayload.connectorId;
464 if (chargingStation.hasConnector(connectorId) === false) {
465 logger.error(
466 `${chargingStation.logPrefix()} Trying to unlock a non existing
467 connector id ${connectorId.toString()}`,
468 );
469 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
470 }
471 if (connectorId === 0) {
472 logger.error(
473 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`,
474 );
475 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
476 }
477 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
478 const stopResponse = await chargingStation.stopTransactionOnConnector(
479 connectorId,
480 OCPP16StopTransactionReason.UNLOCK_COMMAND,
481 );
482 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
483 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
484 }
485 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
486 }
487 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
488 chargingStation,
489 connectorId,
490 OCPP16ChargePointStatus.Available,
491 );
492 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
493 }
494
495 private handleRequestGetConfiguration(
496 chargingStation: ChargingStation,
497 commandPayload: GetConfigurationRequest,
498 ): GetConfigurationResponse {
499 const configurationKey: OCPPConfigurationKey[] = [];
500 const unknownKey: string[] = [];
501 if (isUndefined(commandPayload.key) === true) {
502 for (const configuration of chargingStation.ocppConfiguration!.configurationKey!) {
503 if (isUndefined(configuration.visible) === true) {
504 configuration.visible = true;
505 }
506 if (configuration.visible === false) {
507 continue;
508 }
509 configurationKey.push({
510 key: configuration.key,
511 readonly: configuration.readonly,
512 value: configuration.value,
513 });
514 }
515 } else if (isNotEmptyArray(commandPayload.key) === true) {
516 for (const key of commandPayload.key!) {
517 const keyFound = getConfigurationKey(chargingStation, key, true);
518 if (keyFound) {
519 if (isUndefined(keyFound.visible) === true) {
520 keyFound.visible = true;
521 }
522 if (keyFound.visible === false) {
523 continue;
524 }
525 configurationKey.push({
526 key: keyFound.key,
527 readonly: keyFound.readonly,
528 value: keyFound.value,
529 });
530 } else {
531 unknownKey.push(key);
532 }
533 }
534 }
535 return {
536 configurationKey,
537 unknownKey,
538 };
539 }
540
541 private handleRequestChangeConfiguration(
542 chargingStation: ChargingStation,
543 commandPayload: ChangeConfigurationRequest,
544 ): ChangeConfigurationResponse {
545 const keyToChange = getConfigurationKey(chargingStation, commandPayload.key, true);
546 if (keyToChange?.readonly === true) {
547 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
548 } else if (keyToChange?.readonly === false) {
549 let valueChanged = false;
550 if (keyToChange.value !== commandPayload.value) {
551 setConfigurationKeyValue(chargingStation, commandPayload.key, commandPayload.value, true);
552 valueChanged = true;
553 }
554 let triggerHeartbeatRestart = false;
555 if (
556 (keyToChange.key as OCPP16StandardParametersKey) ===
557 OCPP16StandardParametersKey.HeartBeatInterval &&
558 valueChanged
559 ) {
560 setConfigurationKeyValue(
561 chargingStation,
562 OCPP16StandardParametersKey.HeartbeatInterval,
563 commandPayload.value,
564 );
565 triggerHeartbeatRestart = true;
566 }
567 if (
568 (keyToChange.key as OCPP16StandardParametersKey) ===
569 OCPP16StandardParametersKey.HeartbeatInterval &&
570 valueChanged
571 ) {
572 setConfigurationKeyValue(
573 chargingStation,
574 OCPP16StandardParametersKey.HeartBeatInterval,
575 commandPayload.value,
576 );
577 triggerHeartbeatRestart = true;
578 }
579 if (triggerHeartbeatRestart) {
580 chargingStation.restartHeartbeat();
581 }
582 if (
583 (keyToChange.key as OCPP16StandardParametersKey) ===
584 OCPP16StandardParametersKey.WebSocketPingInterval &&
585 valueChanged
586 ) {
587 chargingStation.restartWebSocketPing();
588 }
589 if (keyToChange.reboot) {
590 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
591 }
592 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
593 }
594 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
595 }
596
597 private handleRequestSetChargingProfile(
598 chargingStation: ChargingStation,
599 commandPayload: SetChargingProfileRequest,
600 ): SetChargingProfileResponse {
601 if (
602 OCPP16ServiceUtils.checkFeatureProfile(
603 chargingStation,
604 OCPP16SupportedFeatureProfiles.SmartCharging,
605 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
606 ) === false
607 ) {
608 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
609 }
610 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
611 logger.error(
612 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
613 non existing connector id ${commandPayload.connectorId}`,
614 );
615 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
616 }
617 if (
618 commandPayload.csChargingProfiles.chargingProfilePurpose ===
619 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
620 commandPayload.connectorId !== 0
621 ) {
622 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
623 }
624 if (
625 commandPayload.csChargingProfiles.chargingProfilePurpose ===
626 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
627 (commandPayload.connectorId === 0 ||
628 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
629 false)
630 ) {
631 logger.error(
632 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
633 on connector ${commandPayload.connectorId} without a started transaction`,
634 );
635 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
636 }
637 OCPP16ServiceUtils.setChargingProfile(
638 chargingStation,
639 commandPayload.connectorId,
640 commandPayload.csChargingProfiles,
641 );
642 logger.debug(
643 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
644 commandPayload.connectorId
645 }: %j`,
646 commandPayload.csChargingProfiles,
647 );
648 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
649 }
650
651 private handleRequestGetCompositeSchedule(
652 chargingStation: ChargingStation,
653 commandPayload: OCPP16GetCompositeScheduleRequest,
654 ): OCPP16GetCompositeScheduleResponse {
655 if (
656 OCPP16ServiceUtils.checkFeatureProfile(
657 chargingStation,
658 OCPP16SupportedFeatureProfiles.SmartCharging,
659 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
660 ) === false
661 ) {
662 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
663 }
664 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
665 logger.error(
666 `${chargingStation.logPrefix()} Trying to get composite schedule to a
667 non existing connector id ${commandPayload.connectorId}`,
668 );
669 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
670 }
671 if (
672 isEmptyArray(chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles)
673 ) {
674 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
675 }
676 const startDate = new Date();
677 const endDate = new Date(startDate.getTime() + secondsToMilliseconds(commandPayload.duration));
678 let compositeSchedule: OCPP16ChargingSchedule | undefined;
679 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)!
680 .chargingProfiles!) {
681 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
682 if (
683 chargingProfile.chargingSchedule.startSchedule! >= startDate &&
684 chargingProfile.chargingSchedule.startSchedule! <= endDate
685 ) {
686 compositeSchedule = chargingProfile.chargingSchedule;
687 break;
688 }
689 }
690 return {
691 status: GenericStatus.Accepted,
692 scheduleStart: compositeSchedule?.startSchedule,
693 connectorId: commandPayload.connectorId,
694 chargingSchedule: compositeSchedule,
695 };
696 }
697
698 private handleRequestClearChargingProfile(
699 chargingStation: ChargingStation,
700 commandPayload: ClearChargingProfileRequest,
701 ): ClearChargingProfileResponse {
702 if (
703 OCPP16ServiceUtils.checkFeatureProfile(
704 chargingStation,
705 OCPP16SupportedFeatureProfiles.SmartCharging,
706 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
707 ) === false
708 ) {
709 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
710 }
711 if (chargingStation.hasConnector(commandPayload.connectorId!) === false) {
712 logger.error(
713 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
714 a non existing connector id ${commandPayload.connectorId}`,
715 );
716 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
717 }
718 if (
719 !isNullOrUndefined(commandPayload.connectorId) &&
720 isNotEmptyArray(
721 chargingStation.getConnectorStatus(commandPayload.connectorId!)?.chargingProfiles,
722 )
723 ) {
724 chargingStation.getConnectorStatus(commandPayload.connectorId!)!.chargingProfiles = [];
725 logger.debug(
726 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
727 commandPayload.connectorId
728 }`,
729 );
730 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
731 }
732 if (isNullOrUndefined(commandPayload.connectorId)) {
733 let clearedCP = false;
734 if (chargingStation.hasEvses) {
735 for (const evseStatus of chargingStation.evses.values()) {
736 for (const connectorStatus of evseStatus.connectors.values()) {
737 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
738 chargingStation,
739 commandPayload,
740 connectorStatus.chargingProfiles,
741 );
742 }
743 }
744 } else {
745 for (const connectorId of chargingStation.connectors.keys()) {
746 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
747 chargingStation,
748 commandPayload,
749 chargingStation.getConnectorStatus(connectorId)?.chargingProfiles,
750 );
751 }
752 }
753 if (clearedCP) {
754 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
755 }
756 }
757 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
758 }
759
760 private async handleRequestChangeAvailability(
761 chargingStation: ChargingStation,
762 commandPayload: ChangeAvailabilityRequest,
763 ): Promise<ChangeAvailabilityResponse> {
764 const connectorId: number = commandPayload.connectorId;
765 if (chargingStation.hasConnector(connectorId) === false) {
766 logger.error(
767 `${chargingStation.logPrefix()} Trying to change the availability of a
768 non existing connector id ${connectorId.toString()}`,
769 );
770 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
771 }
772 const chargePointStatus: OCPP16ChargePointStatus =
773 commandPayload.type === OCPP16AvailabilityType.Operative
774 ? OCPP16ChargePointStatus.Available
775 : OCPP16ChargePointStatus.Unavailable;
776 if (connectorId === 0) {
777 let response: ChangeAvailabilityResponse =
778 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
779 const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => {
780 if (connectorStatus?.transactionStarted === true) {
781 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
782 }
783 connectorStatus.availability = commandPayload.type;
784 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
785 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
786 chargingStation,
787 id,
788 chargePointStatus,
789 );
790 }
791 };
792 if (chargingStation.hasEvses) {
793 for (const evseStatus of chargingStation.evses.values()) {
794 for (const [id, connectorStatus] of evseStatus.connectors) {
795 await changeAvailability(id, connectorStatus);
796 }
797 }
798 } else {
799 for (const id of chargingStation.connectors.keys()) {
800 await changeAvailability(id, chargingStation.getConnectorStatus(id)!);
801 }
802 }
803 return response;
804 } else if (
805 connectorId > 0 &&
806 (chargingStation.isChargingStationAvailable() === true ||
807 (chargingStation.isChargingStationAvailable() === false &&
808 commandPayload.type === OCPP16AvailabilityType.Inoperative))
809 ) {
810 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
811 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
812 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
813 }
814 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
815 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
816 chargingStation,
817 connectorId,
818 chargePointStatus,
819 );
820 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
821 }
822 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
823 }
824
825 private async handleRequestRemoteStartTransaction(
826 chargingStation: ChargingStation,
827 commandPayload: RemoteStartTransactionRequest,
828 ): Promise<GenericResponse> {
829 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
830 const reserved =
831 chargingStation.getConnectorStatus(transactionConnectorId)!.status ===
832 OCPP16ChargePointStatus.Reserved;
833 const reservedOnConnectorZero =
834 chargingStation.getConnectorStatus(0)!.status === OCPP16ChargePointStatus.Reserved;
835 if (
836 (reserved &&
837 !chargingStation.validateIncomingRequestWithReservation(transactionConnectorId, idTag)) ||
838 (reservedOnConnectorZero && !chargingStation.validateIncomingRequestWithReservation(0, idTag))
839 ) {
840 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
841 }
842 if (chargingStation.hasConnector(transactionConnectorId) === false) {
843 return this.notifyRemoteStartTransactionRejected(
844 chargingStation,
845 transactionConnectorId,
846 idTag,
847 );
848 }
849 if (
850 !chargingStation.isChargingStationAvailable() ||
851 !chargingStation.isConnectorAvailable(transactionConnectorId)
852 ) {
853 return this.notifyRemoteStartTransactionRejected(
854 chargingStation,
855 transactionConnectorId,
856 idTag,
857 );
858 }
859 const remoteStartTransactionLogMsg = `
860 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
861 chargingStation.stationInfo.chargingStationId
862 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
863 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
864 chargingStation,
865 transactionConnectorId,
866 OCPP16ChargePointStatus.Preparing,
867 );
868 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
869 // Check if authorized
870 if (
871 chargingStation.getAuthorizeRemoteTxRequests() &&
872 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
873 ) {
874 // Authorization successful, start transaction
875 if (
876 this.setRemoteStartTransactionChargingProfile(
877 chargingStation,
878 transactionConnectorId,
879 chargingProfile!,
880 ) === true
881 ) {
882 connectorStatus.transactionRemoteStarted = true;
883 const startTransactionPayload: Partial<StartTransactionRequest> = {
884 connectorId: transactionConnectorId,
885 idTag,
886 };
887 if (reserved || reservedOnConnectorZero) {
888 const reservation = chargingStation.getReservationBy(
889 ReservationFilterKey.CONNECTOR_ID,
890 reservedOnConnectorZero ? 0 : transactionConnectorId,
891 )!;
892 startTransactionPayload.reservationId = reservation.reservationId;
893 await chargingStation.removeReservation(
894 reservation,
895 ReservationTerminationReason.TRANSACTION_STARTED,
896 );
897 }
898 if (
899 (
900 await chargingStation.ocppRequestService.requestHandler<
901 OCPP16StartTransactionRequest,
902 OCPP16StartTransactionResponse
903 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, startTransactionPayload)
904 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
905 ) {
906 logger.debug(remoteStartTransactionLogMsg);
907 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
908 }
909 return this.notifyRemoteStartTransactionRejected(
910 chargingStation,
911 transactionConnectorId,
912 idTag,
913 );
914 }
915 return this.notifyRemoteStartTransactionRejected(
916 chargingStation,
917 transactionConnectorId,
918 idTag,
919 );
920 }
921 // No authorization check required, start transaction
922 if (
923 this.setRemoteStartTransactionChargingProfile(
924 chargingStation,
925 transactionConnectorId,
926 chargingProfile!,
927 ) === true
928 ) {
929 connectorStatus.transactionRemoteStarted = true;
930 if (
931 (
932 await chargingStation.ocppRequestService.requestHandler<
933 OCPP16StartTransactionRequest,
934 OCPP16StartTransactionResponse
935 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
936 connectorId: transactionConnectorId,
937 idTag,
938 })
939 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
940 ) {
941 logger.debug(remoteStartTransactionLogMsg);
942 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
943 }
944 return this.notifyRemoteStartTransactionRejected(
945 chargingStation,
946 transactionConnectorId,
947 idTag,
948 );
949 }
950 return this.notifyRemoteStartTransactionRejected(
951 chargingStation,
952 transactionConnectorId,
953 idTag,
954 );
955 }
956
957 private async notifyRemoteStartTransactionRejected(
958 chargingStation: ChargingStation,
959 connectorId: number,
960 idTag: string,
961 ): Promise<GenericResponse> {
962 if (
963 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
964 ) {
965 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
966 chargingStation,
967 connectorId,
968 OCPP16ChargePointStatus.Available,
969 );
970 }
971 logger.warn(
972 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
973 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
974 connectorId,
975 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
976 );
977 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
978 }
979
980 private setRemoteStartTransactionChargingProfile(
981 chargingStation: ChargingStation,
982 connectorId: number,
983 chargingProfile: OCPP16ChargingProfile,
984 ): boolean {
985 if (
986 chargingProfile &&
987 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
988 ) {
989 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
990 logger.debug(
991 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
992 on connector id ${connectorId}: %j`,
993 chargingProfile,
994 );
995 return true;
996 } else if (
997 chargingProfile &&
998 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
999 ) {
1000 logger.warn(
1001 `${chargingStation.logPrefix()} Not allowed to set ${
1002 chargingProfile.chargingProfilePurpose
1003 } charging profile(s) at remote start transaction`,
1004 );
1005 return false;
1006 }
1007 return true;
1008 }
1009
1010 private async handleRequestRemoteStopTransaction(
1011 chargingStation: ChargingStation,
1012 commandPayload: RemoteStopTransactionRequest,
1013 ): Promise<GenericResponse> {
1014 const transactionId = commandPayload.transactionId;
1015 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1016 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1017 chargingStation,
1018 connectorId,
1019 OCPP16ChargePointStatus.Finishing,
1020 );
1021 const stopResponse = await chargingStation.stopTransactionOnConnector(
1022 connectorId,
1023 OCPP16StopTransactionReason.REMOTE,
1024 );
1025 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1026 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1027 }
1028 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1029 };
1030 if (chargingStation.hasEvses) {
1031 for (const [evseId, evseStatus] of chargingStation.evses) {
1032 if (evseId > 0) {
1033 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1034 if (connectorStatus.transactionId === transactionId) {
1035 return remoteStopTransaction(connectorId);
1036 }
1037 }
1038 }
1039 }
1040 } else {
1041 for (const connectorId of chargingStation.connectors.keys()) {
1042 if (
1043 connectorId > 0 &&
1044 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1045 ) {
1046 return remoteStopTransaction(connectorId);
1047 }
1048 }
1049 }
1050 logger.warn(
1051 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1052 ${transactionId.toString()}`,
1053 );
1054 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1055 }
1056
1057 private handleRequestUpdateFirmware(
1058 chargingStation: ChargingStation,
1059 commandPayload: OCPP16UpdateFirmwareRequest,
1060 ): OCPP16UpdateFirmwareResponse {
1061 if (
1062 OCPP16ServiceUtils.checkFeatureProfile(
1063 chargingStation,
1064 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1065 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1066 ) === false
1067 ) {
1068 logger.warn(
1069 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1070 Cannot simulate firmware update: feature profile not supported`,
1071 );
1072 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1073 }
1074 if (
1075 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1076 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1077 ) {
1078 logger.warn(
1079 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1080 Cannot simulate firmware update: firmware update is already in progress`,
1081 );
1082 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1083 }
1084 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1085 const now = Date.now();
1086 if (retrieveDate?.getTime() <= now) {
1087 this.runInAsyncScope(
1088 this.updateFirmwareSimulation.bind(this) as (
1089 this: OCPP16IncomingRequestService,
1090 ...args: unknown[]
1091 ) => Promise<void>,
1092 this,
1093 chargingStation,
1094 ).catch(Constants.EMPTY_FUNCTION);
1095 } else {
1096 setTimeout(
1097 () => {
1098 this.runInAsyncScope(
1099 this.updateFirmwareSimulation.bind(this) as (
1100 this: OCPP16IncomingRequestService,
1101 ...args: unknown[]
1102 ) => Promise<void>,
1103 this,
1104 chargingStation,
1105 ).catch(Constants.EMPTY_FUNCTION);
1106 },
1107 retrieveDate?.getTime() - now,
1108 );
1109 }
1110 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1111 }
1112
1113 private async updateFirmwareSimulation(
1114 chargingStation: ChargingStation,
1115 maxDelay = 30,
1116 minDelay = 15,
1117 ): Promise<void> {
1118 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1119 return;
1120 }
1121 if (chargingStation.hasEvses) {
1122 for (const [evseId, evseStatus] of chargingStation.evses) {
1123 if (evseId > 0) {
1124 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1125 if (connectorStatus?.transactionStarted === false) {
1126 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1127 chargingStation,
1128 connectorId,
1129 OCPP16ChargePointStatus.Unavailable,
1130 );
1131 }
1132 }
1133 }
1134 }
1135 } else {
1136 for (const connectorId of chargingStation.connectors.keys()) {
1137 if (
1138 connectorId > 0 &&
1139 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1140 ) {
1141 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1142 chargingStation,
1143 connectorId,
1144 OCPP16ChargePointStatus.Unavailable,
1145 );
1146 }
1147 }
1148 }
1149 await chargingStation.ocppRequestService.requestHandler<
1150 OCPP16FirmwareStatusNotificationRequest,
1151 OCPP16FirmwareStatusNotificationResponse
1152 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1153 status: OCPP16FirmwareStatus.Downloading,
1154 });
1155 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1156 if (
1157 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1158 OCPP16FirmwareStatus.DownloadFailed
1159 ) {
1160 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1161 await chargingStation.ocppRequestService.requestHandler<
1162 OCPP16FirmwareStatusNotificationRequest,
1163 OCPP16FirmwareStatusNotificationResponse
1164 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1165 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1166 });
1167 chargingStation.stationInfo.firmwareStatus =
1168 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1169 return;
1170 }
1171 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1172 await chargingStation.ocppRequestService.requestHandler<
1173 OCPP16FirmwareStatusNotificationRequest,
1174 OCPP16FirmwareStatusNotificationResponse
1175 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1176 status: OCPP16FirmwareStatus.Downloaded,
1177 });
1178 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1179 let wasTransactionsStarted = false;
1180 let transactionsStarted: boolean;
1181 do {
1182 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1183 if (runningTransactions > 0) {
1184 const waitTime = secondsToMilliseconds(15);
1185 logger.debug(
1186 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1187 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1188 waitTime,
1189 )} before continuing firmware update simulation`,
1190 );
1191 await sleep(waitTime);
1192 transactionsStarted = true;
1193 wasTransactionsStarted = true;
1194 } else {
1195 if (chargingStation.hasEvses) {
1196 for (const [evseId, evseStatus] of chargingStation.evses) {
1197 if (evseId > 0) {
1198 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1199 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1200 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1201 chargingStation,
1202 connectorId,
1203 OCPP16ChargePointStatus.Unavailable,
1204 );
1205 }
1206 }
1207 }
1208 }
1209 } else {
1210 for (const connectorId of chargingStation.connectors.keys()) {
1211 if (
1212 connectorId > 0 &&
1213 chargingStation.getConnectorStatus(connectorId)?.status !==
1214 OCPP16ChargePointStatus.Unavailable
1215 ) {
1216 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1217 chargingStation,
1218 connectorId,
1219 OCPP16ChargePointStatus.Unavailable,
1220 );
1221 }
1222 }
1223 }
1224 transactionsStarted = false;
1225 }
1226 } while (transactionsStarted);
1227 !wasTransactionsStarted &&
1228 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
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(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
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(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
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 | undefined;
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 | undefined;
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<GetDiagnosticsResponse>(
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<OCPP16TriggerMessageResponse>(
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<OCPP16DataTransferResponse>(
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<OCPP16ReserveNowResponse>(
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 reservation = chargingStation.getReservationBy(
1619 ReservationFilterKey.RESERVATION_ID,
1620 reservationId,
1621 );
1622 if (isUndefined(reservation)) {
1623 logger.error(
1624 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1625 does not exist on charging station`,
1626 );
1627 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1628 }
1629 await chargingStation.removeReservation(
1630 reservation!,
1631 ReservationTerminationReason.RESERVATION_CANCELED,
1632 );
1633 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1634 } catch (error) {
1635 return this.handleIncomingRequestError<GenericResponse>(
1636 chargingStation,
1637 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1638 error as Error,
1639 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1640 )!;
1641 }
1642 }
1643 }