fix: fix reservation removal at transaction start
[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 (
829 (chargingStation.getConnectorStatus(transactionConnectorId)!.status ===
830 OCPP16ChargePointStatus.Reserved &&
831 chargingStation.getReservationBy('connectorId', transactionConnectorId)?.idTag !== idTag) ||
832 (chargingStation.getConnectorStatus(0)!.status === OCPP16ChargePointStatus.Reserved &&
833 chargingStation.getReservationBy('connectorId', 0)?.idTag !== idTag)
834 ) {
835 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
836 }
837 if (chargingStation.hasConnector(transactionConnectorId) === false) {
838 return this.notifyRemoteStartTransactionRejected(
839 chargingStation,
840 transactionConnectorId,
841 idTag,
842 );
843 }
844 if (
845 !chargingStation.isChargingStationAvailable() ||
846 !chargingStation.isConnectorAvailable(transactionConnectorId)
847 ) {
848 return this.notifyRemoteStartTransactionRejected(
849 chargingStation,
850 transactionConnectorId,
851 idTag,
852 );
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 if (
865 chargingStation.getAuthorizeRemoteTxRequests() &&
866 !chargingStation.getLocalAuthListEnabled() &&
867 !chargingStation.getMustAuthorizeAtRemoteStart()
868 ) {
869 logger.warn(
870 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction
871 but local authorization or must authorize at remote start isn't enabled`,
872 );
873 }
874 // Authorization check required
875 if (
876 chargingStation.getAuthorizeRemoteTxRequests() === true &&
877 chargingStation.getMustAuthorizeAtRemoteStart() === true &&
878 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
879 ) {
880 // Authorization successful, start transaction
881 if (
882 this.setRemoteStartTransactionChargingProfile(
883 chargingStation,
884 transactionConnectorId,
885 chargingProfile!,
886 ) === true
887 ) {
888 connectorStatus.transactionRemoteStarted = true;
889 if (
890 (
891 await chargingStation.ocppRequestService.requestHandler<
892 OCPP16StartTransactionRequest,
893 OCPP16StartTransactionResponse
894 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
895 connectorId: transactionConnectorId,
896 idTag,
897 })
898 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
899 ) {
900 logger.debug(remoteStartTransactionLogMsg);
901 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
902 }
903 return this.notifyRemoteStartTransactionRejected(
904 chargingStation,
905 transactionConnectorId,
906 idTag,
907 );
908 }
909 return this.notifyRemoteStartTransactionRejected(
910 chargingStation,
911 transactionConnectorId,
912 idTag,
913 );
914 }
915 // No authorization check required, start transaction
916 if (
917 this.setRemoteStartTransactionChargingProfile(
918 chargingStation,
919 transactionConnectorId,
920 chargingProfile!,
921 ) === true
922 ) {
923 connectorStatus.transactionRemoteStarted = true;
924 if (
925 (
926 await chargingStation.ocppRequestService.requestHandler<
927 OCPP16StartTransactionRequest,
928 OCPP16StartTransactionResponse
929 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
930 connectorId: transactionConnectorId,
931 idTag,
932 })
933 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
934 ) {
935 logger.debug(remoteStartTransactionLogMsg);
936 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
937 }
938 return this.notifyRemoteStartTransactionRejected(
939 chargingStation,
940 transactionConnectorId,
941 idTag,
942 );
943 }
944 return this.notifyRemoteStartTransactionRejected(
945 chargingStation,
946 transactionConnectorId,
947 idTag,
948 );
949 }
950
951 private async notifyRemoteStartTransactionRejected(
952 chargingStation: ChargingStation,
953 connectorId: number,
954 idTag: string,
955 ): Promise<GenericResponse> {
956 if (
957 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
958 ) {
959 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
960 chargingStation,
961 connectorId,
962 OCPP16ChargePointStatus.Available,
963 );
964 }
965 logger.warn(
966 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
967 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
968 connectorId,
969 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
970 );
971 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
972 }
973
974 private setRemoteStartTransactionChargingProfile(
975 chargingStation: ChargingStation,
976 connectorId: number,
977 chargingProfile: OCPP16ChargingProfile,
978 ): boolean {
979 if (
980 chargingProfile &&
981 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
982 ) {
983 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
984 logger.debug(
985 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
986 on connector id ${connectorId}: %j`,
987 chargingProfile,
988 );
989 return true;
990 } else if (
991 chargingProfile &&
992 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
993 ) {
994 logger.warn(
995 `${chargingStation.logPrefix()} Not allowed to set ${
996 chargingProfile.chargingProfilePurpose
997 } charging profile(s) at remote start transaction`,
998 );
999 return false;
1000 }
1001 return true;
1002 }
1003
1004 private async handleRequestRemoteStopTransaction(
1005 chargingStation: ChargingStation,
1006 commandPayload: RemoteStopTransactionRequest,
1007 ): Promise<GenericResponse> {
1008 const transactionId = commandPayload.transactionId;
1009 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1010 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1011 chargingStation,
1012 connectorId,
1013 OCPP16ChargePointStatus.Finishing,
1014 );
1015 const stopResponse = await chargingStation.stopTransactionOnConnector(
1016 connectorId,
1017 OCPP16StopTransactionReason.REMOTE,
1018 );
1019 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1020 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1021 }
1022 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1023 };
1024 if (chargingStation.hasEvses) {
1025 for (const [evseId, evseStatus] of chargingStation.evses) {
1026 if (evseId > 0) {
1027 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1028 if (connectorStatus.transactionId === transactionId) {
1029 return remoteStopTransaction(connectorId);
1030 }
1031 }
1032 }
1033 }
1034 } else {
1035 for (const connectorId of chargingStation.connectors.keys()) {
1036 if (
1037 connectorId > 0 &&
1038 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1039 ) {
1040 return remoteStopTransaction(connectorId);
1041 }
1042 }
1043 }
1044 logger.warn(
1045 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1046 ${transactionId.toString()}`,
1047 );
1048 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1049 }
1050
1051 private handleRequestUpdateFirmware(
1052 chargingStation: ChargingStation,
1053 commandPayload: OCPP16UpdateFirmwareRequest,
1054 ): OCPP16UpdateFirmwareResponse {
1055 if (
1056 OCPP16ServiceUtils.checkFeatureProfile(
1057 chargingStation,
1058 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1059 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1060 ) === false
1061 ) {
1062 logger.warn(
1063 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1064 Cannot simulate firmware update: feature profile not supported`,
1065 );
1066 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1067 }
1068 if (
1069 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1070 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1071 ) {
1072 logger.warn(
1073 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1074 Cannot simulate firmware update: firmware update is already in progress`,
1075 );
1076 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1077 }
1078 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1079 const now = Date.now();
1080 if (retrieveDate?.getTime() <= now) {
1081 this.runInAsyncScope(
1082 this.updateFirmwareSimulation.bind(this) as (
1083 this: OCPP16IncomingRequestService,
1084 ...args: unknown[]
1085 ) => Promise<void>,
1086 this,
1087 chargingStation,
1088 ).catch(Constants.EMPTY_FUNCTION);
1089 } else {
1090 setTimeout(
1091 () => {
1092 this.runInAsyncScope(
1093 this.updateFirmwareSimulation.bind(this) as (
1094 this: OCPP16IncomingRequestService,
1095 ...args: unknown[]
1096 ) => Promise<void>,
1097 this,
1098 chargingStation,
1099 ).catch(Constants.EMPTY_FUNCTION);
1100 },
1101 retrieveDate?.getTime() - now,
1102 );
1103 }
1104 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1105 }
1106
1107 private async updateFirmwareSimulation(
1108 chargingStation: ChargingStation,
1109 maxDelay = 30,
1110 minDelay = 15,
1111 ): Promise<void> {
1112 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1113 return;
1114 }
1115 if (chargingStation.hasEvses) {
1116 for (const [evseId, evseStatus] of chargingStation.evses) {
1117 if (evseId > 0) {
1118 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1119 if (connectorStatus?.transactionStarted === false) {
1120 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1121 chargingStation,
1122 connectorId,
1123 OCPP16ChargePointStatus.Unavailable,
1124 );
1125 }
1126 }
1127 }
1128 }
1129 } else {
1130 for (const connectorId of chargingStation.connectors.keys()) {
1131 if (
1132 connectorId > 0 &&
1133 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1134 ) {
1135 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1136 chargingStation,
1137 connectorId,
1138 OCPP16ChargePointStatus.Unavailable,
1139 );
1140 }
1141 }
1142 }
1143 await chargingStation.ocppRequestService.requestHandler<
1144 OCPP16FirmwareStatusNotificationRequest,
1145 OCPP16FirmwareStatusNotificationResponse
1146 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1147 status: OCPP16FirmwareStatus.Downloading,
1148 });
1149 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1150 if (
1151 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1152 OCPP16FirmwareStatus.DownloadFailed
1153 ) {
1154 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1155 await chargingStation.ocppRequestService.requestHandler<
1156 OCPP16FirmwareStatusNotificationRequest,
1157 OCPP16FirmwareStatusNotificationResponse
1158 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1159 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1160 });
1161 chargingStation.stationInfo.firmwareStatus =
1162 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1163 return;
1164 }
1165 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1166 await chargingStation.ocppRequestService.requestHandler<
1167 OCPP16FirmwareStatusNotificationRequest,
1168 OCPP16FirmwareStatusNotificationResponse
1169 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1170 status: OCPP16FirmwareStatus.Downloaded,
1171 });
1172 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1173 let wasTransactionsStarted = false;
1174 let transactionsStarted: boolean;
1175 do {
1176 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1177 if (runningTransactions > 0) {
1178 const waitTime = secondsToMilliseconds(15);
1179 logger.debug(
1180 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1181 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1182 waitTime,
1183 )} before continuing firmware update simulation`,
1184 );
1185 await sleep(waitTime);
1186 transactionsStarted = true;
1187 wasTransactionsStarted = true;
1188 } else {
1189 if (chargingStation.hasEvses) {
1190 for (const [evseId, evseStatus] of chargingStation.evses) {
1191 if (evseId > 0) {
1192 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1193 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1194 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1195 chargingStation,
1196 connectorId,
1197 OCPP16ChargePointStatus.Unavailable,
1198 );
1199 }
1200 }
1201 }
1202 }
1203 } else {
1204 for (const connectorId of chargingStation.connectors.keys()) {
1205 if (
1206 connectorId > 0 &&
1207 chargingStation.getConnectorStatus(connectorId)?.status !==
1208 OCPP16ChargePointStatus.Unavailable
1209 ) {
1210 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1211 chargingStation,
1212 connectorId,
1213 OCPP16ChargePointStatus.Unavailable,
1214 );
1215 }
1216 }
1217 }
1218 transactionsStarted = false;
1219 }
1220 } while (transactionsStarted);
1221 !wasTransactionsStarted &&
1222 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1223 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1224 return;
1225 }
1226 await chargingStation.ocppRequestService.requestHandler<
1227 OCPP16FirmwareStatusNotificationRequest,
1228 OCPP16FirmwareStatusNotificationResponse
1229 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1230 status: OCPP16FirmwareStatus.Installing,
1231 });
1232 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1233 if (
1234 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1235 OCPP16FirmwareStatus.InstallationFailed
1236 ) {
1237 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1238 await chargingStation.ocppRequestService.requestHandler<
1239 OCPP16FirmwareStatusNotificationRequest,
1240 OCPP16FirmwareStatusNotificationResponse
1241 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1242 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1243 });
1244 chargingStation.stationInfo.firmwareStatus =
1245 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1246 return;
1247 }
1248 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1249 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1250 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1251 }
1252 }
1253
1254 private async handleRequestGetDiagnostics(
1255 chargingStation: ChargingStation,
1256 commandPayload: GetDiagnosticsRequest,
1257 ): Promise<GetDiagnosticsResponse> {
1258 if (
1259 OCPP16ServiceUtils.checkFeatureProfile(
1260 chargingStation,
1261 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1262 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1263 ) === false
1264 ) {
1265 logger.warn(
1266 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1267 Cannot get diagnostics: feature profile not supported`,
1268 );
1269 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1270 }
1271 const uri = new URL(commandPayload.location);
1272 if (uri.protocol.startsWith('ftp:')) {
1273 let ftpClient: Client | undefined;
1274 try {
1275 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1276 .filter((file) => file.endsWith('.log'))
1277 .map((file) => join('./', file));
1278 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1279 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1280 ftpClient = new Client();
1281 const accessResponse = await ftpClient.access({
1282 host: uri.host,
1283 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1284 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1285 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1286 });
1287 let uploadResponse: FTPResponse | undefined;
1288 if (accessResponse.code === 220) {
1289 ftpClient.trackProgress((info) => {
1290 logger.info(
1291 `${chargingStation.logPrefix()} ${
1292 info.bytes / 1024
1293 } bytes transferred from diagnostics archive ${info.name}`,
1294 );
1295 chargingStation.ocppRequestService
1296 .requestHandler<
1297 OCPP16DiagnosticsStatusNotificationRequest,
1298 OCPP16DiagnosticsStatusNotificationResponse
1299 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1300 status: OCPP16DiagnosticsStatus.Uploading,
1301 })
1302 .catch((error) => {
1303 logger.error(
1304 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1305 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1306 error,
1307 );
1308 });
1309 });
1310 uploadResponse = await ftpClient.uploadFrom(
1311 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1312 `${uri.pathname}${diagnosticsArchive}`,
1313 );
1314 if (uploadResponse.code === 226) {
1315 await chargingStation.ocppRequestService.requestHandler<
1316 OCPP16DiagnosticsStatusNotificationRequest,
1317 OCPP16DiagnosticsStatusNotificationResponse
1318 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1319 status: OCPP16DiagnosticsStatus.Uploaded,
1320 });
1321 if (ftpClient) {
1322 ftpClient.close();
1323 }
1324 return { fileName: diagnosticsArchive };
1325 }
1326 throw new OCPPError(
1327 ErrorType.GENERIC_ERROR,
1328 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1329 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1330 }`,
1331 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1332 );
1333 }
1334 throw new OCPPError(
1335 ErrorType.GENERIC_ERROR,
1336 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1337 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1338 }`,
1339 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1340 );
1341 } catch (error) {
1342 await chargingStation.ocppRequestService.requestHandler<
1343 OCPP16DiagnosticsStatusNotificationRequest,
1344 OCPP16DiagnosticsStatusNotificationResponse
1345 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1346 status: OCPP16DiagnosticsStatus.UploadFailed,
1347 });
1348 if (ftpClient) {
1349 ftpClient.close();
1350 }
1351 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1352 chargingStation,
1353 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1354 error as Error,
1355 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1356 )!;
1357 }
1358 } else {
1359 logger.error(
1360 `${chargingStation.logPrefix()} Unsupported protocol ${
1361 uri.protocol
1362 } to transfer the diagnostic logs archive`,
1363 );
1364 await chargingStation.ocppRequestService.requestHandler<
1365 OCPP16DiagnosticsStatusNotificationRequest,
1366 OCPP16DiagnosticsStatusNotificationResponse
1367 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1368 status: OCPP16DiagnosticsStatus.UploadFailed,
1369 });
1370 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1371 }
1372 }
1373
1374 private handleRequestTriggerMessage(
1375 chargingStation: ChargingStation,
1376 commandPayload: OCPP16TriggerMessageRequest,
1377 ): OCPP16TriggerMessageResponse {
1378 if (
1379 !OCPP16ServiceUtils.checkFeatureProfile(
1380 chargingStation,
1381 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1382 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1383 ) ||
1384 !OCPP16ServiceUtils.isMessageTriggerSupported(
1385 chargingStation,
1386 commandPayload.requestedMessage,
1387 )
1388 ) {
1389 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1390 }
1391 if (
1392 !OCPP16ServiceUtils.isConnectorIdValid(
1393 chargingStation,
1394 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1395 commandPayload.connectorId!,
1396 )
1397 ) {
1398 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1399 }
1400 try {
1401 switch (commandPayload.requestedMessage) {
1402 case OCPP16MessageTrigger.BootNotification:
1403 setTimeout(() => {
1404 chargingStation.ocppRequestService
1405 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1406 chargingStation,
1407 OCPP16RequestCommand.BOOT_NOTIFICATION,
1408 chargingStation.bootNotificationRequest,
1409 { skipBufferingOnError: true, triggerMessage: true },
1410 )
1411 .then((response) => {
1412 chargingStation.bootNotificationResponse = response;
1413 })
1414 .catch(Constants.EMPTY_FUNCTION);
1415 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1416 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1417 case OCPP16MessageTrigger.Heartbeat:
1418 setTimeout(() => {
1419 chargingStation.ocppRequestService
1420 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1421 chargingStation,
1422 OCPP16RequestCommand.HEARTBEAT,
1423 null,
1424 {
1425 triggerMessage: true,
1426 },
1427 )
1428 .catch(Constants.EMPTY_FUNCTION);
1429 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1430 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1431 case OCPP16MessageTrigger.StatusNotification:
1432 setTimeout(() => {
1433 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1434 chargingStation.ocppRequestService
1435 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1436 chargingStation,
1437 OCPP16RequestCommand.STATUS_NOTIFICATION,
1438 {
1439 connectorId: commandPayload.connectorId,
1440 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1441 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1442 },
1443 {
1444 triggerMessage: true,
1445 },
1446 )
1447 .catch(Constants.EMPTY_FUNCTION);
1448 } else {
1449 // eslint-disable-next-line no-lonely-if
1450 if (chargingStation.hasEvses) {
1451 for (const evseStatus of chargingStation.evses.values()) {
1452 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1453 chargingStation.ocppRequestService
1454 .requestHandler<
1455 OCPP16StatusNotificationRequest,
1456 OCPP16StatusNotificationResponse
1457 >(
1458 chargingStation,
1459 OCPP16RequestCommand.STATUS_NOTIFICATION,
1460 {
1461 connectorId,
1462 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1463 status: connectorStatus.status,
1464 },
1465 {
1466 triggerMessage: true,
1467 },
1468 )
1469 .catch(Constants.EMPTY_FUNCTION);
1470 }
1471 }
1472 } else {
1473 for (const connectorId of chargingStation.connectors.keys()) {
1474 chargingStation.ocppRequestService
1475 .requestHandler<
1476 OCPP16StatusNotificationRequest,
1477 OCPP16StatusNotificationResponse
1478 >(
1479 chargingStation,
1480 OCPP16RequestCommand.STATUS_NOTIFICATION,
1481 {
1482 connectorId,
1483 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1484 status: chargingStation.getConnectorStatus(connectorId)?.status,
1485 },
1486 {
1487 triggerMessage: true,
1488 },
1489 )
1490 .catch(Constants.EMPTY_FUNCTION);
1491 }
1492 }
1493 }
1494 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1495 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1496 default:
1497 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1498 }
1499 } catch (error) {
1500 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1501 chargingStation,
1502 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1503 error as Error,
1504 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1505 )!;
1506 }
1507 }
1508
1509 private handleRequestDataTransfer(
1510 chargingStation: ChargingStation,
1511 commandPayload: OCPP16DataTransferRequest,
1512 ): OCPP16DataTransferResponse {
1513 try {
1514 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1515 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1516 }
1517 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1518 } catch (error) {
1519 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1520 chargingStation,
1521 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1522 error as Error,
1523 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1524 )!;
1525 }
1526 }
1527
1528 private async handleRequestReserveNow(
1529 chargingStation: ChargingStation,
1530 commandPayload: OCPP16ReserveNowRequest,
1531 ): Promise<OCPP16ReserveNowResponse> {
1532 if (
1533 !OCPP16ServiceUtils.checkFeatureProfile(
1534 chargingStation,
1535 OCPP16SupportedFeatureProfiles.Reservation,
1536 OCPP16IncomingRequestCommand.RESERVE_NOW,
1537 )
1538 ) {
1539 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1540 }
1541 const { reservationId, idTag, connectorId } = commandPayload;
1542 let response: OCPP16ReserveNowResponse;
1543 try {
1544 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1545 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1546 }
1547 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1548 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1549 }
1550 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1551 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1552 }
1553 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1554 case OCPP16ChargePointStatus.Faulted:
1555 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1556 break;
1557 case OCPP16ChargePointStatus.Preparing:
1558 case OCPP16ChargePointStatus.Charging:
1559 case OCPP16ChargePointStatus.SuspendedEV:
1560 case OCPP16ChargePointStatus.SuspendedEVSE:
1561 case OCPP16ChargePointStatus.Finishing:
1562 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1563 break;
1564 case OCPP16ChargePointStatus.Unavailable:
1565 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1566 break;
1567 case OCPP16ChargePointStatus.Reserved:
1568 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1569 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1570 break;
1571 }
1572 // eslint-disable-next-line no-fallthrough
1573 default:
1574 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1575 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1576 break;
1577 }
1578 await chargingStation.addReservation({
1579 id: commandPayload.reservationId,
1580 ...commandPayload,
1581 });
1582 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1583 break;
1584 }
1585 return response;
1586 } catch (error) {
1587 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1588 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1589 chargingStation,
1590 OCPP16IncomingRequestCommand.RESERVE_NOW,
1591 error as Error,
1592 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1593 )!;
1594 }
1595 }
1596
1597 private async handleRequestCancelReservation(
1598 chargingStation: ChargingStation,
1599 commandPayload: OCPP16CancelReservationRequest,
1600 ): Promise<GenericResponse> {
1601 if (
1602 !OCPP16ServiceUtils.checkFeatureProfile(
1603 chargingStation,
1604 OCPP16SupportedFeatureProfiles.Reservation,
1605 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1606 )
1607 ) {
1608 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1609 }
1610 try {
1611 const { reservationId } = commandPayload;
1612 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1613 if (isUndefined(reservation)) {
1614 logger.error(
1615 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1616 does not exist on charging station`,
1617 );
1618 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1619 }
1620 await chargingStation.removeReservation(
1621 reservation!,
1622 ReservationTerminationReason.RESERVATION_CANCELED,
1623 );
1624 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1625 } catch (error) {
1626 return this.handleIncomingRequestError<GenericResponse>(
1627 chargingStation,
1628 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1629 error as Error,
1630 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1631 )!;
1632 }
1633 }
1634 }