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