fix: fix valid reservation detection 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 { isWithinInterval, 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 removeExpiredReservations,
19 setConfigurationKeyValue,
20 } from '../../../charging-station';
21 import { OCPPError } from '../../../exception';
22 import {
23 type ChangeConfigurationRequest,
24 type ChangeConfigurationResponse,
25 type ClearChargingProfileRequest,
26 type ClearChargingProfileResponse,
27 ErrorType,
28 type GenericResponse,
29 GenericStatus,
30 type GetConfigurationRequest,
31 type GetConfigurationResponse,
32 type GetDiagnosticsRequest,
33 type GetDiagnosticsResponse,
34 type IncomingRequestHandler,
35 type JsonObject,
36 type JsonType,
37 OCPP16AuthorizationStatus,
38 OCPP16AvailabilityType,
39 type OCPP16BootNotificationRequest,
40 type OCPP16BootNotificationResponse,
41 type OCPP16CancelReservationRequest,
42 type OCPP16ChangeAvailabilityRequest,
43 type OCPP16ChangeAvailabilityResponse,
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<OCPP16ChangeAvailabilityRequest>(
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 isWithinInterval(chargingProfile.chargingSchedule.startSchedule!, {
682 start: startDate,
683 end: endDate,
684 })
685 ) {
686 compositeSchedule = chargingProfile.chargingSchedule;
687 break;
688 }
689 }
690 return {
691 status: GenericStatus.Accepted,
692 scheduleStart: compositeSchedule?.startSchedule,
693 connectorId: commandPayload.connectorId,
694 chargingSchedule: compositeSchedule,
695 };
696 }
697
698 private handleRequestClearChargingProfile(
699 chargingStation: ChargingStation,
700 commandPayload: ClearChargingProfileRequest,
701 ): ClearChargingProfileResponse {
702 if (
703 OCPP16ServiceUtils.checkFeatureProfile(
704 chargingStation,
705 OCPP16SupportedFeatureProfiles.SmartCharging,
706 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
707 ) === false
708 ) {
709 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
710 }
711 if (chargingStation.hasConnector(commandPayload.connectorId!) === false) {
712 logger.error(
713 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
714 a non existing connector id ${commandPayload.connectorId}`,
715 );
716 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
717 }
718 if (
719 !isNullOrUndefined(commandPayload.connectorId) &&
720 isNotEmptyArray(
721 chargingStation.getConnectorStatus(commandPayload.connectorId!)?.chargingProfiles,
722 )
723 ) {
724 chargingStation.getConnectorStatus(commandPayload.connectorId!)!.chargingProfiles = [];
725 logger.debug(
726 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
727 commandPayload.connectorId
728 }`,
729 );
730 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
731 }
732 if (isNullOrUndefined(commandPayload.connectorId)) {
733 let clearedCP = false;
734 if (chargingStation.hasEvses) {
735 for (const evseStatus of chargingStation.evses.values()) {
736 for (const connectorStatus of evseStatus.connectors.values()) {
737 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
738 chargingStation,
739 commandPayload,
740 connectorStatus.chargingProfiles,
741 );
742 }
743 }
744 } else {
745 for (const connectorId of chargingStation.connectors.keys()) {
746 clearedCP = OCPP16ServiceUtils.clearChargingProfiles(
747 chargingStation,
748 commandPayload,
749 chargingStation.getConnectorStatus(connectorId)?.chargingProfiles,
750 );
751 }
752 }
753 if (clearedCP) {
754 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
755 }
756 }
757 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
758 }
759
760 private async handleRequestChangeAvailability(
761 chargingStation: ChargingStation,
762 commandPayload: OCPP16ChangeAvailabilityRequest,
763 ): Promise<OCPP16ChangeAvailabilityResponse> {
764 const connectorId: number = commandPayload.connectorId;
765 if (chargingStation.hasConnector(connectorId) === false) {
766 logger.error(
767 `${chargingStation.logPrefix()} Trying to change the availability of a
768 non existing connector id ${connectorId.toString()}`,
769 );
770 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
771 }
772 const chargePointStatus: OCPP16ChargePointStatus =
773 commandPayload.type === OCPP16AvailabilityType.Operative
774 ? OCPP16ChargePointStatus.Available
775 : OCPP16ChargePointStatus.Unavailable;
776 if (connectorId === 0) {
777 let response: OCPP16ChangeAvailabilityResponse;
778 if (chargingStation.hasEvses) {
779 for (const evseStatus of chargingStation.evses.values()) {
780 response = await OCPP16ServiceUtils.changeAvailability(
781 chargingStation,
782 [...evseStatus.connectors.keys()],
783 chargePointStatus,
784 commandPayload.type,
785 );
786 }
787 } else {
788 response = await OCPP16ServiceUtils.changeAvailability(
789 chargingStation,
790 [...chargingStation.connectors.keys()],
791 chargePointStatus,
792 commandPayload.type,
793 );
794 }
795 return response!;
796 } else if (
797 connectorId > 0 &&
798 (chargingStation.isChargingStationAvailable() === true ||
799 (chargingStation.isChargingStationAvailable() === false &&
800 commandPayload.type === OCPP16AvailabilityType.Inoperative))
801 ) {
802 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
803 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
804 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
805 }
806 chargingStation.getConnectorStatus(connectorId)!.availability = commandPayload.type;
807 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
808 chargingStation,
809 connectorId,
810 chargePointStatus,
811 );
812 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
813 }
814 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
815 }
816
817 private async handleRequestRemoteStartTransaction(
818 chargingStation: ChargingStation,
819 commandPayload: RemoteStartTransactionRequest,
820 ): Promise<GenericResponse> {
821 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
822 if (chargingStation.hasConnector(transactionConnectorId) === false) {
823 return this.notifyRemoteStartTransactionRejected(
824 chargingStation,
825 transactionConnectorId,
826 idTag,
827 );
828 }
829 if (
830 !chargingStation.isChargingStationAvailable() ||
831 !chargingStation.isConnectorAvailable(transactionConnectorId)
832 ) {
833 return this.notifyRemoteStartTransactionRejected(
834 chargingStation,
835 transactionConnectorId,
836 idTag,
837 );
838 }
839 const remoteStartTransactionLogMsg = `
840 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
841 chargingStation.stationInfo.chargingStationId
842 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
843 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
844 chargingStation,
845 transactionConnectorId,
846 OCPP16ChargePointStatus.Preparing,
847 );
848 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId)!;
849 // Authorization check required
850 if (
851 chargingStation.getAuthorizeRemoteTxRequests() === true &&
852 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
853 ) {
854 // Authorization successful, start transaction
855 if (
856 this.setRemoteStartTransactionChargingProfile(
857 chargingStation,
858 transactionConnectorId,
859 chargingProfile!,
860 ) === true
861 ) {
862 connectorStatus.transactionRemoteStarted = true;
863 if (
864 (
865 await chargingStation.ocppRequestService.requestHandler<
866 OCPP16StartTransactionRequest,
867 OCPP16StartTransactionResponse
868 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
869 connectorId: transactionConnectorId,
870 idTag,
871 })
872 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
873 ) {
874 logger.debug(remoteStartTransactionLogMsg);
875 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
876 }
877 return this.notifyRemoteStartTransactionRejected(
878 chargingStation,
879 transactionConnectorId,
880 idTag,
881 );
882 }
883 return this.notifyRemoteStartTransactionRejected(
884 chargingStation,
885 transactionConnectorId,
886 idTag,
887 );
888 }
889 // No authorization check required, start transaction
890 if (
891 this.setRemoteStartTransactionChargingProfile(
892 chargingStation,
893 transactionConnectorId,
894 chargingProfile!,
895 ) === true
896 ) {
897 connectorStatus.transactionRemoteStarted = true;
898 if (
899 (
900 await chargingStation.ocppRequestService.requestHandler<
901 OCPP16StartTransactionRequest,
902 OCPP16StartTransactionResponse
903 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
904 connectorId: transactionConnectorId,
905 idTag,
906 })
907 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
908 ) {
909 logger.debug(remoteStartTransactionLogMsg);
910 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
911 }
912 return this.notifyRemoteStartTransactionRejected(
913 chargingStation,
914 transactionConnectorId,
915 idTag,
916 );
917 }
918 return this.notifyRemoteStartTransactionRejected(
919 chargingStation,
920 transactionConnectorId,
921 idTag,
922 );
923 }
924
925 private async notifyRemoteStartTransactionRejected(
926 chargingStation: ChargingStation,
927 connectorId: number,
928 idTag: string,
929 ): Promise<GenericResponse> {
930 if (
931 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
932 ) {
933 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
934 chargingStation,
935 connectorId,
936 OCPP16ChargePointStatus.Available,
937 );
938 }
939 logger.warn(
940 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
941 ${connectorId.toString()}, idTag '${idTag}', availability '${chargingStation.getConnectorStatus(
942 connectorId,
943 )?.availability}', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`,
944 );
945 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
946 }
947
948 private setRemoteStartTransactionChargingProfile(
949 chargingStation: ChargingStation,
950 connectorId: number,
951 chargingProfile: OCPP16ChargingProfile,
952 ): boolean {
953 if (
954 chargingProfile &&
955 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE
956 ) {
957 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile);
958 logger.debug(
959 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
960 on connector id ${connectorId}: %j`,
961 chargingProfile,
962 );
963 return true;
964 } else if (
965 chargingProfile &&
966 chargingProfile.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE
967 ) {
968 logger.warn(
969 `${chargingStation.logPrefix()} Not allowed to set ${
970 chargingProfile.chargingProfilePurpose
971 } charging profile(s) at remote start transaction`,
972 );
973 return false;
974 }
975 return true;
976 }
977
978 private async handleRequestRemoteStopTransaction(
979 chargingStation: ChargingStation,
980 commandPayload: RemoteStopTransactionRequest,
981 ): Promise<GenericResponse> {
982 const transactionId = commandPayload.transactionId;
983 if (chargingStation.hasEvses) {
984 for (const [evseId, evseStatus] of chargingStation.evses) {
985 if (evseId > 0) {
986 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
987 if (connectorStatus.transactionId === transactionId) {
988 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
989 }
990 }
991 }
992 }
993 } else {
994 for (const connectorId of chargingStation.connectors.keys()) {
995 if (
996 connectorId > 0 &&
997 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
998 ) {
999 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1000 }
1001 }
1002 }
1003 logger.warn(
1004 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id
1005 ${transactionId.toString()}`,
1006 );
1007 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1008 }
1009
1010 private handleRequestUpdateFirmware(
1011 chargingStation: ChargingStation,
1012 commandPayload: OCPP16UpdateFirmwareRequest,
1013 ): OCPP16UpdateFirmwareResponse {
1014 if (
1015 OCPP16ServiceUtils.checkFeatureProfile(
1016 chargingStation,
1017 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1018 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1019 ) === false
1020 ) {
1021 logger.warn(
1022 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1023 Cannot simulate firmware update: feature profile not supported`,
1024 );
1025 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1026 }
1027 if (
1028 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1029 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1030 ) {
1031 logger.warn(
1032 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1033 Cannot simulate firmware update: firmware update is already in progress`,
1034 );
1035 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1036 }
1037 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1038 const now = Date.now();
1039 if (retrieveDate?.getTime() <= now) {
1040 this.runInAsyncScope(
1041 this.updateFirmwareSimulation.bind(this) as (
1042 this: OCPP16IncomingRequestService,
1043 ...args: unknown[]
1044 ) => Promise<void>,
1045 this,
1046 chargingStation,
1047 ).catch(Constants.EMPTY_FUNCTION);
1048 } else {
1049 setTimeout(
1050 () => {
1051 this.runInAsyncScope(
1052 this.updateFirmwareSimulation.bind(this) as (
1053 this: OCPP16IncomingRequestService,
1054 ...args: unknown[]
1055 ) => Promise<void>,
1056 this,
1057 chargingStation,
1058 ).catch(Constants.EMPTY_FUNCTION);
1059 },
1060 retrieveDate?.getTime() - now,
1061 );
1062 }
1063 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1064 }
1065
1066 private async updateFirmwareSimulation(
1067 chargingStation: ChargingStation,
1068 maxDelay = 30,
1069 minDelay = 15,
1070 ): Promise<void> {
1071 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1072 return;
1073 }
1074 if (chargingStation.hasEvses) {
1075 for (const [evseId, evseStatus] of chargingStation.evses) {
1076 if (evseId > 0) {
1077 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1078 if (connectorStatus?.transactionStarted === false) {
1079 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1080 chargingStation,
1081 connectorId,
1082 OCPP16ChargePointStatus.Unavailable,
1083 );
1084 }
1085 }
1086 }
1087 }
1088 } else {
1089 for (const connectorId of chargingStation.connectors.keys()) {
1090 if (
1091 connectorId > 0 &&
1092 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1093 ) {
1094 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1095 chargingStation,
1096 connectorId,
1097 OCPP16ChargePointStatus.Unavailable,
1098 );
1099 }
1100 }
1101 }
1102 await chargingStation.ocppRequestService.requestHandler<
1103 OCPP16FirmwareStatusNotificationRequest,
1104 OCPP16FirmwareStatusNotificationResponse
1105 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1106 status: OCPP16FirmwareStatus.Downloading,
1107 });
1108 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1109 if (
1110 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1111 OCPP16FirmwareStatus.DownloadFailed
1112 ) {
1113 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1114 await chargingStation.ocppRequestService.requestHandler<
1115 OCPP16FirmwareStatusNotificationRequest,
1116 OCPP16FirmwareStatusNotificationResponse
1117 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1118 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1119 });
1120 chargingStation.stationInfo.firmwareStatus =
1121 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1122 return;
1123 }
1124 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1125 await chargingStation.ocppRequestService.requestHandler<
1126 OCPP16FirmwareStatusNotificationRequest,
1127 OCPP16FirmwareStatusNotificationResponse
1128 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1129 status: OCPP16FirmwareStatus.Downloaded,
1130 });
1131 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1132 let wasTransactionsStarted = false;
1133 let transactionsStarted: boolean;
1134 do {
1135 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1136 if (runningTransactions > 0) {
1137 const waitTime = secondsToMilliseconds(15);
1138 logger.debug(
1139 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1140 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1141 waitTime,
1142 )} before continuing firmware update simulation`,
1143 );
1144 await sleep(waitTime);
1145 transactionsStarted = true;
1146 wasTransactionsStarted = true;
1147 } else {
1148 if (chargingStation.hasEvses) {
1149 for (const [evseId, evseStatus] of chargingStation.evses) {
1150 if (evseId > 0) {
1151 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1152 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1153 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1154 chargingStation,
1155 connectorId,
1156 OCPP16ChargePointStatus.Unavailable,
1157 );
1158 }
1159 }
1160 }
1161 }
1162 } else {
1163 for (const connectorId of chargingStation.connectors.keys()) {
1164 if (
1165 connectorId > 0 &&
1166 chargingStation.getConnectorStatus(connectorId)?.status !==
1167 OCPP16ChargePointStatus.Unavailable
1168 ) {
1169 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1170 chargingStation,
1171 connectorId,
1172 OCPP16ChargePointStatus.Unavailable,
1173 );
1174 }
1175 }
1176 }
1177 transactionsStarted = false;
1178 }
1179 } while (transactionsStarted);
1180 !wasTransactionsStarted &&
1181 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1182 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1183 return;
1184 }
1185 await chargingStation.ocppRequestService.requestHandler<
1186 OCPP16FirmwareStatusNotificationRequest,
1187 OCPP16FirmwareStatusNotificationResponse
1188 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1189 status: OCPP16FirmwareStatus.Installing,
1190 });
1191 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1192 if (
1193 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1194 OCPP16FirmwareStatus.InstallationFailed
1195 ) {
1196 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1197 await chargingStation.ocppRequestService.requestHandler<
1198 OCPP16FirmwareStatusNotificationRequest,
1199 OCPP16FirmwareStatusNotificationResponse
1200 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1201 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1202 });
1203 chargingStation.stationInfo.firmwareStatus =
1204 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1205 return;
1206 }
1207 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1208 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1209 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1210 }
1211 }
1212
1213 private async handleRequestGetDiagnostics(
1214 chargingStation: ChargingStation,
1215 commandPayload: GetDiagnosticsRequest,
1216 ): Promise<GetDiagnosticsResponse> {
1217 if (
1218 OCPP16ServiceUtils.checkFeatureProfile(
1219 chargingStation,
1220 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1221 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1222 ) === false
1223 ) {
1224 logger.warn(
1225 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1226 Cannot get diagnostics: feature profile not supported`,
1227 );
1228 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1229 }
1230 const uri = new URL(commandPayload.location);
1231 if (uri.protocol.startsWith('ftp:')) {
1232 let ftpClient: Client | undefined;
1233 try {
1234 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1235 .filter((file) => file.endsWith('.log'))
1236 .map((file) => join('./', file));
1237 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1238 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1239 ftpClient = new Client();
1240 const accessResponse = await ftpClient.access({
1241 host: uri.host,
1242 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1243 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1244 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1245 });
1246 let uploadResponse: FTPResponse | undefined;
1247 if (accessResponse.code === 220) {
1248 ftpClient.trackProgress((info) => {
1249 logger.info(
1250 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1251 info.bytes / 1024
1252 } bytes transferred from diagnostics archive ${info.name}`,
1253 );
1254 chargingStation.ocppRequestService
1255 .requestHandler<
1256 OCPP16DiagnosticsStatusNotificationRequest,
1257 OCPP16DiagnosticsStatusNotificationResponse
1258 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1259 status: OCPP16DiagnosticsStatus.Uploading,
1260 })
1261 .catch((error) => {
1262 logger.error(
1263 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1264 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1265 error,
1266 );
1267 });
1268 });
1269 uploadResponse = await ftpClient.uploadFrom(
1270 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1271 `${uri.pathname}${diagnosticsArchive}`,
1272 );
1273 if (uploadResponse.code === 226) {
1274 await chargingStation.ocppRequestService.requestHandler<
1275 OCPP16DiagnosticsStatusNotificationRequest,
1276 OCPP16DiagnosticsStatusNotificationResponse
1277 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1278 status: OCPP16DiagnosticsStatus.Uploaded,
1279 });
1280 if (ftpClient) {
1281 ftpClient.close();
1282 }
1283 return { fileName: diagnosticsArchive };
1284 }
1285 throw new OCPPError(
1286 ErrorType.GENERIC_ERROR,
1287 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1288 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1289 }`,
1290 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1291 );
1292 }
1293 throw new OCPPError(
1294 ErrorType.GENERIC_ERROR,
1295 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1296 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1297 }`,
1298 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1299 );
1300 } catch (error) {
1301 await chargingStation.ocppRequestService.requestHandler<
1302 OCPP16DiagnosticsStatusNotificationRequest,
1303 OCPP16DiagnosticsStatusNotificationResponse
1304 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1305 status: OCPP16DiagnosticsStatus.UploadFailed,
1306 });
1307 if (ftpClient) {
1308 ftpClient.close();
1309 }
1310 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1311 chargingStation,
1312 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1313 error as Error,
1314 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1315 )!;
1316 }
1317 } else {
1318 logger.error(
1319 `${chargingStation.logPrefix()} Unsupported protocol ${
1320 uri.protocol
1321 } to transfer the diagnostic logs archive`,
1322 );
1323 await chargingStation.ocppRequestService.requestHandler<
1324 OCPP16DiagnosticsStatusNotificationRequest,
1325 OCPP16DiagnosticsStatusNotificationResponse
1326 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1327 status: OCPP16DiagnosticsStatus.UploadFailed,
1328 });
1329 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1330 }
1331 }
1332
1333 private handleRequestTriggerMessage(
1334 chargingStation: ChargingStation,
1335 commandPayload: OCPP16TriggerMessageRequest,
1336 ): OCPP16TriggerMessageResponse {
1337 if (
1338 !OCPP16ServiceUtils.checkFeatureProfile(
1339 chargingStation,
1340 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1341 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1342 ) ||
1343 !OCPP16ServiceUtils.isMessageTriggerSupported(
1344 chargingStation,
1345 commandPayload.requestedMessage,
1346 )
1347 ) {
1348 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1349 }
1350 if (
1351 !OCPP16ServiceUtils.isConnectorIdValid(
1352 chargingStation,
1353 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1354 commandPayload.connectorId!,
1355 )
1356 ) {
1357 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1358 }
1359 try {
1360 switch (commandPayload.requestedMessage) {
1361 case OCPP16MessageTrigger.BootNotification:
1362 setTimeout(() => {
1363 chargingStation.ocppRequestService
1364 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1365 chargingStation,
1366 OCPP16RequestCommand.BOOT_NOTIFICATION,
1367 chargingStation.bootNotificationRequest,
1368 { skipBufferingOnError: true, triggerMessage: true },
1369 )
1370 .then((response) => {
1371 chargingStation.bootNotificationResponse = response;
1372 })
1373 .catch(Constants.EMPTY_FUNCTION);
1374 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1375 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1376 case OCPP16MessageTrigger.Heartbeat:
1377 setTimeout(() => {
1378 chargingStation.ocppRequestService
1379 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1380 chargingStation,
1381 OCPP16RequestCommand.HEARTBEAT,
1382 null,
1383 {
1384 triggerMessage: true,
1385 },
1386 )
1387 .catch(Constants.EMPTY_FUNCTION);
1388 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1389 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1390 case OCPP16MessageTrigger.StatusNotification:
1391 setTimeout(() => {
1392 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1393 chargingStation.ocppRequestService
1394 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1395 chargingStation,
1396 OCPP16RequestCommand.STATUS_NOTIFICATION,
1397 {
1398 connectorId: commandPayload.connectorId,
1399 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1400 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1401 },
1402 {
1403 triggerMessage: true,
1404 },
1405 )
1406 .catch(Constants.EMPTY_FUNCTION);
1407 } else {
1408 // eslint-disable-next-line no-lonely-if
1409 if (chargingStation.hasEvses) {
1410 for (const evseStatus of chargingStation.evses.values()) {
1411 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1412 chargingStation.ocppRequestService
1413 .requestHandler<
1414 OCPP16StatusNotificationRequest,
1415 OCPP16StatusNotificationResponse
1416 >(
1417 chargingStation,
1418 OCPP16RequestCommand.STATUS_NOTIFICATION,
1419 {
1420 connectorId,
1421 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1422 status: connectorStatus.status,
1423 },
1424 {
1425 triggerMessage: true,
1426 },
1427 )
1428 .catch(Constants.EMPTY_FUNCTION);
1429 }
1430 }
1431 } else {
1432 for (const connectorId of chargingStation.connectors.keys()) {
1433 chargingStation.ocppRequestService
1434 .requestHandler<
1435 OCPP16StatusNotificationRequest,
1436 OCPP16StatusNotificationResponse
1437 >(
1438 chargingStation,
1439 OCPP16RequestCommand.STATUS_NOTIFICATION,
1440 {
1441 connectorId,
1442 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1443 status: chargingStation.getConnectorStatus(connectorId)?.status,
1444 },
1445 {
1446 triggerMessage: true,
1447 },
1448 )
1449 .catch(Constants.EMPTY_FUNCTION);
1450 }
1451 }
1452 }
1453 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1454 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1455 default:
1456 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1457 }
1458 } catch (error) {
1459 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1460 chargingStation,
1461 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1462 error as Error,
1463 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1464 )!;
1465 }
1466 }
1467
1468 private handleRequestDataTransfer(
1469 chargingStation: ChargingStation,
1470 commandPayload: OCPP16DataTransferRequest,
1471 ): OCPP16DataTransferResponse {
1472 try {
1473 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1474 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1475 }
1476 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1477 } catch (error) {
1478 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1479 chargingStation,
1480 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1481 error as Error,
1482 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1483 )!;
1484 }
1485 }
1486
1487 private async handleRequestReserveNow(
1488 chargingStation: ChargingStation,
1489 commandPayload: OCPP16ReserveNowRequest,
1490 ): Promise<OCPP16ReserveNowResponse> {
1491 if (
1492 !OCPP16ServiceUtils.checkFeatureProfile(
1493 chargingStation,
1494 OCPP16SupportedFeatureProfiles.Reservation,
1495 OCPP16IncomingRequestCommand.RESERVE_NOW,
1496 )
1497 ) {
1498 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1499 }
1500 const { reservationId, idTag, connectorId } = commandPayload;
1501 let response: OCPP16ReserveNowResponse;
1502 try {
1503 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1504 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1505 }
1506 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1507 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1508 }
1509 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1510 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1511 }
1512 await removeExpiredReservations(chargingStation);
1513 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1514 case OCPP16ChargePointStatus.Faulted:
1515 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1516 break;
1517 case OCPP16ChargePointStatus.Preparing:
1518 case OCPP16ChargePointStatus.Charging:
1519 case OCPP16ChargePointStatus.SuspendedEV:
1520 case OCPP16ChargePointStatus.SuspendedEVSE:
1521 case OCPP16ChargePointStatus.Finishing:
1522 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1523 break;
1524 case OCPP16ChargePointStatus.Unavailable:
1525 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1526 break;
1527 case OCPP16ChargePointStatus.Reserved:
1528 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1529 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1530 break;
1531 }
1532 // eslint-disable-next-line no-fallthrough
1533 default:
1534 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1535 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1536 break;
1537 }
1538 await chargingStation.addReservation({
1539 id: commandPayload.reservationId,
1540 ...commandPayload,
1541 });
1542 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1543 break;
1544 }
1545 return response;
1546 } catch (error) {
1547 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1548 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1549 chargingStation,
1550 OCPP16IncomingRequestCommand.RESERVE_NOW,
1551 error as Error,
1552 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1553 )!;
1554 }
1555 }
1556
1557 private async handleRequestCancelReservation(
1558 chargingStation: ChargingStation,
1559 commandPayload: OCPP16CancelReservationRequest,
1560 ): Promise<GenericResponse> {
1561 if (
1562 !OCPP16ServiceUtils.checkFeatureProfile(
1563 chargingStation,
1564 OCPP16SupportedFeatureProfiles.Reservation,
1565 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1566 )
1567 ) {
1568 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1569 }
1570 try {
1571 const { reservationId } = commandPayload;
1572 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1573 if (isUndefined(reservation)) {
1574 logger.debug(
1575 `${chargingStation.logPrefix()} Reservation with id ${reservationId}
1576 does not exist on charging station`,
1577 );
1578 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1579 }
1580 await chargingStation.removeReservation(
1581 reservation!,
1582 ReservationTerminationReason.RESERVATION_CANCELED,
1583 );
1584 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1585 } catch (error) {
1586 return this.handleIncomingRequestError<GenericResponse>(
1587 chargingStation,
1588 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1589 error as Error,
1590 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1591 )!;
1592 }
1593 }
1594 }