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