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