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