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