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