Merge branch 'main' into reservation-feature
[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 fs from 'node:fs';
4 import path 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 tar 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 ConnectorStatusEnum,
28 ErrorType,
29 type GenericResponse,
30 GenericStatus,
31 type GetConfigurationRequest,
32 type GetConfigurationResponse,
33 type GetDiagnosticsRequest,
34 type GetDiagnosticsResponse,
35 type IncomingRequestHandler,
36 type JsonObject,
37 type JsonType,
38 OCPP16AuthorizationStatus,
39 OCPP16AvailabilityType,
40 type OCPP16BootNotificationRequest,
41 type OCPP16BootNotificationResponse,
42 type OCPP16CancelReservationRequest,
43 type OCPP16CancelReservationResponse,
44 OCPP16ChargePointErrorCode,
45 OCPP16ChargePointStatus,
46 type OCPP16ChargingProfile,
47 OCPP16ChargingProfilePurposeType,
48 type OCPP16ChargingSchedule,
49 type OCPP16ClearCacheRequest,
50 type OCPP16DataTransferRequest,
51 type OCPP16DataTransferResponse,
52 OCPP16DataTransferVendorId,
53 OCPP16DiagnosticsStatus,
54 type OCPP16DiagnosticsStatusNotificationRequest,
55 type OCPP16DiagnosticsStatusNotificationResponse,
56 OCPP16FirmwareStatus,
57 type OCPP16FirmwareStatusNotificationRequest,
58 type OCPP16FirmwareStatusNotificationResponse,
59 type OCPP16GetCompositeScheduleRequest,
60 type OCPP16GetCompositeScheduleResponse,
61 type OCPP16HeartbeatRequest,
62 type OCPP16HeartbeatResponse,
63 OCPP16IncomingRequestCommand,
64 OCPP16MessageTrigger,
65 OCPP16RequestCommand,
66 type OCPP16ReserveNowRequest,
67 type OCPP16ReserveNowResponse,
68 OCPP16StandardParametersKey,
69 type OCPP16StartTransactionRequest,
70 type OCPP16StartTransactionResponse,
71 type OCPP16StatusNotificationRequest,
72 type OCPP16StatusNotificationResponse,
73 OCPP16StopTransactionReason,
74 OCPP16SupportedFeatureProfiles,
75 type OCPP16TriggerMessageRequest,
76 type OCPP16TriggerMessageResponse,
77 type OCPP16UpdateFirmwareRequest,
78 type OCPP16UpdateFirmwareResponse,
79 type OCPPConfigurationKey,
80 OCPPVersion,
81 type RemoteStartTransactionRequest,
82 type RemoteStopTransactionRequest,
83 ReservationFilterKey,
84 ReservationTerminationReason,
85 type ResetRequest,
86 type SetChargingProfileRequest,
87 type SetChargingProfileResponse,
88 type StartTransactionRequest,
89 type UnlockConnectorRequest,
90 type UnlockConnectorResponse,
91 } from '../../../types';
92 import { Constants, Utils, logger } from '../../../utils';
93 import { OCPPConstants } from '../OCPPConstants';
94 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService';
95
96 const moduleName = 'OCPP16IncomingRequestService';
97
98 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
99 protected jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
100 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
101
102 public constructor() {
103 // if (new.target?.name === moduleName) {
104 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
105 // }
106 super(OCPPVersion.VERSION_16);
107 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
108 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
109 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
110 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
111 [
112 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
113 this.handleRequestGetConfiguration.bind(this),
114 ],
115 [
116 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
117 this.handleRequestChangeConfiguration.bind(this),
118 ],
119 [
120 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
121 this.handleRequestGetCompositeSchedule.bind(this),
122 ],
123 [
124 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
125 this.handleRequestSetChargingProfile.bind(this),
126 ],
127 [
128 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
129 this.handleRequestClearChargingProfile.bind(this),
130 ],
131 [
132 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
133 this.handleRequestChangeAvailability.bind(this),
134 ],
135 [
136 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
137 this.handleRequestRemoteStartTransaction.bind(this),
138 ],
139 [
140 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
141 this.handleRequestRemoteStopTransaction.bind(this),
142 ],
143 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
144 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
145 [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
146 [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
147 [OCPP16IncomingRequestCommand.RESERVE_NOW, this.handleRequestReserveNow.bind(this)],
148 [
149 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
150 this.handleRequestCancelReservation.bind(this),
151 ],
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 OCPP16IncomingRequestCommand.RESERVE_NOW,
276 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
277 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
278 moduleName,
279 'constructor'
280 ),
281 ],
282 [
283 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
284 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
285 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
286 moduleName,
287 'constructor'
288 ),
289 ],
290 ]);
291 this.validatePayload = this.validatePayload.bind(this) as (
292 chargingStation: ChargingStation,
293 commandName: OCPP16IncomingRequestCommand,
294 commandPayload: JsonType
295 ) => boolean;
296 }
297
298 public async incomingRequestHandler(
299 chargingStation: ChargingStation,
300 messageId: string,
301 commandName: OCPP16IncomingRequestCommand,
302 commandPayload: JsonType
303 ): Promise<void> {
304 let response: JsonType;
305 if (
306 chargingStation.getOcppStrictCompliance() === true &&
307 chargingStation.inPendingState() === true &&
308 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
309 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
310 ) {
311 throw new OCPPError(
312 ErrorType.SECURITY_ERROR,
313 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
314 commandPayload,
315 null,
316 2
317 )} while the charging station is in pending state on the central server`,
318 commandName,
319 commandPayload
320 );
321 }
322 if (
323 chargingStation.isRegistered() === true ||
324 (chargingStation.getOcppStrictCompliance() === false &&
325 chargingStation.inUnknownState() === true)
326 ) {
327 if (
328 this.incomingRequestHandlers.has(commandName) === true &&
329 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName) === true
330 ) {
331 try {
332 this.validatePayload(chargingStation, commandName, commandPayload);
333 // Call the method to build the response
334 response = await this.incomingRequestHandlers.get(commandName)(
335 chargingStation,
336 commandPayload
337 );
338 } catch (error) {
339 // Log
340 logger.error(
341 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler:
342 Handle incoming request error:`,
343 error
344 );
345 throw error;
346 }
347 } else {
348 // Throw exception
349 throw new OCPPError(
350 ErrorType.NOT_IMPLEMENTED,
351 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
352 commandPayload,
353 null,
354 2
355 )}`,
356 commandName,
357 commandPayload
358 );
359 }
360 } else {
361 throw new OCPPError(
362 ErrorType.SECURITY_ERROR,
363 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
364 commandPayload,
365 null,
366 2
367 )} while the charging station is not registered on the central server.`,
368 commandName,
369 commandPayload
370 );
371 }
372 // Send the built response
373 await chargingStation.ocppRequestService.sendResponse(
374 chargingStation,
375 messageId,
376 response,
377 commandName
378 );
379 }
380
381 private validatePayload(
382 chargingStation: ChargingStation,
383 commandName: OCPP16IncomingRequestCommand,
384 commandPayload: JsonType
385 ): boolean {
386 if (this.jsonSchemas.has(commandName) === true) {
387 return this.validateIncomingRequestPayload(
388 chargingStation,
389 commandName,
390 this.jsonSchemas.get(commandName),
391 commandPayload
392 );
393 }
394 logger.warn(
395 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found
396 for command '${commandName}' PDU validation`
397 );
398 return false;
399 }
400
401 // Simulate charging station restart
402 private handleRequestReset(
403 chargingStation: ChargingStation,
404 commandPayload: ResetRequest
405 ): GenericResponse {
406 this.runInAsyncScope(
407 chargingStation.reset.bind(chargingStation) as (
408 this: ChargingStation,
409 ...args: any[]
410 ) => Promise<void>,
411 chargingStation,
412 `${commandPayload.type}Reset` as OCPP16StopTransactionReason
413 ).catch(Constants.EMPTY_FUNCTION);
414 logger.info(
415 `${chargingStation.logPrefix()} ${
416 commandPayload.type
417 } reset command received, simulating it. The station will be
418 back online in ${Utils.formatDurationMilliSeconds(chargingStation.stationInfo.resetTime)}`
419 );
420 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
421 }
422
423 private async handleRequestUnlockConnector(
424 chargingStation: ChargingStation,
425 commandPayload: UnlockConnectorRequest
426 ): Promise<UnlockConnectorResponse> {
427 const connectorId = commandPayload.connectorId;
428 if (chargingStation.hasConnector(connectorId) === false) {
429 logger.error(
430 `${chargingStation.logPrefix()} Trying to unlock a non existing
431 connector id ${connectorId.toString()}`
432 );
433 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
434 }
435 if (connectorId === 0) {
436 logger.error(
437 `${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId.toString()}`
438 );
439 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
440 }
441 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
442 const stopResponse = await chargingStation.stopTransactionOnConnector(
443 connectorId,
444 OCPP16StopTransactionReason.UNLOCK_COMMAND
445 );
446 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
447 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
448 }
449 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED;
450 }
451 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
452 chargingStation,
453 connectorId,
454 OCPP16ChargePointStatus.Available
455 );
456 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED;
457 }
458
459 private handleRequestGetConfiguration(
460 chargingStation: ChargingStation,
461 commandPayload: GetConfigurationRequest
462 ): GetConfigurationResponse {
463 const configurationKey: OCPPConfigurationKey[] = [];
464 const unknownKey: string[] = [];
465 if (Utils.isUndefined(commandPayload.key) === true) {
466 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
467 if (Utils.isUndefined(configuration.visible) === true) {
468 configuration.visible = true;
469 }
470 if (configuration.visible === false) {
471 continue;
472 }
473 configurationKey.push({
474 key: configuration.key,
475 readonly: configuration.readonly,
476 value: configuration.value,
477 });
478 }
479 } else if (Utils.isNotEmptyArray(commandPayload.key) === true) {
480 for (const key of commandPayload.key) {
481 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
482 chargingStation,
483 key,
484 true
485 );
486 if (keyFound) {
487 if (Utils.isUndefined(keyFound.visible) === true) {
488 keyFound.visible = true;
489 }
490 if (keyFound.visible === false) {
491 continue;
492 }
493 configurationKey.push({
494 key: keyFound.key,
495 readonly: keyFound.readonly,
496 value: keyFound.value,
497 });
498 } else {
499 unknownKey.push(key);
500 }
501 }
502 }
503 return {
504 configurationKey,
505 unknownKey,
506 };
507 }
508
509 private handleRequestChangeConfiguration(
510 chargingStation: ChargingStation,
511 commandPayload: ChangeConfigurationRequest
512 ): ChangeConfigurationResponse {
513 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
514 chargingStation,
515 commandPayload.key,
516 true
517 );
518 if (!keyToChange) {
519 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
520 } else if (keyToChange?.readonly === true) {
521 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
522 } else if (keyToChange?.readonly === false) {
523 let valueChanged = false;
524 if (keyToChange.value !== commandPayload.value) {
525 ChargingStationConfigurationUtils.setConfigurationKeyValue(
526 chargingStation,
527 commandPayload.key,
528 commandPayload.value,
529 true
530 );
531 valueChanged = true;
532 }
533 let triggerHeartbeatRestart = false;
534 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
535 ChargingStationConfigurationUtils.setConfigurationKeyValue(
536 chargingStation,
537 OCPP16StandardParametersKey.HeartbeatInterval,
538 commandPayload.value
539 );
540 triggerHeartbeatRestart = true;
541 }
542 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
543 ChargingStationConfigurationUtils.setConfigurationKeyValue(
544 chargingStation,
545 OCPP16StandardParametersKey.HeartBeatInterval,
546 commandPayload.value
547 );
548 triggerHeartbeatRestart = true;
549 }
550 if (triggerHeartbeatRestart) {
551 chargingStation.restartHeartbeat();
552 }
553 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
554 chargingStation.restartWebSocketPing();
555 }
556 if (keyToChange.reboot) {
557 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
558 }
559 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
560 }
561 }
562
563 private handleRequestSetChargingProfile(
564 chargingStation: ChargingStation,
565 commandPayload: SetChargingProfileRequest
566 ): SetChargingProfileResponse {
567 if (
568 OCPP16ServiceUtils.checkFeatureProfile(
569 chargingStation,
570 OCPP16SupportedFeatureProfiles.SmartCharging,
571 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
572 ) === false
573 ) {
574 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
575 }
576 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
577 logger.error(
578 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a
579 non existing connector id ${commandPayload.connectorId}`
580 );
581 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
582 }
583 if (
584 commandPayload.csChargingProfiles.chargingProfilePurpose ===
585 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
586 commandPayload.connectorId !== 0
587 ) {
588 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
589 }
590 if (
591 commandPayload.csChargingProfiles.chargingProfilePurpose ===
592 OCPP16ChargingProfilePurposeType.TX_PROFILE &&
593 (commandPayload.connectorId === 0 ||
594 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
595 false)
596 ) {
597 logger.error(
598 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s)
599 on connector ${commandPayload.connectorId} without a started transaction`
600 );
601 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
602 }
603 OCPP16ServiceUtils.setChargingProfile(
604 chargingStation,
605 commandPayload.connectorId,
606 commandPayload.csChargingProfiles
607 );
608 logger.debug(
609 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
610 commandPayload.connectorId
611 }: %j`,
612 commandPayload.csChargingProfiles
613 );
614 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
615 }
616
617 private handleRequestGetCompositeSchedule(
618 chargingStation: ChargingStation,
619 commandPayload: OCPP16GetCompositeScheduleRequest
620 ): OCPP16GetCompositeScheduleResponse {
621 if (
622 OCPP16ServiceUtils.checkFeatureProfile(
623 chargingStation,
624 OCPP16SupportedFeatureProfiles.SmartCharging,
625 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
626 ) === false
627 ) {
628 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
629 }
630 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
631 logger.error(
632 `${chargingStation.logPrefix()} Trying to get composite schedule to a
633 non existing connector id ${commandPayload.connectorId}`
634 );
635 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
636 }
637 if (
638 Utils.isEmptyArray(
639 chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
640 )
641 ) {
642 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
643 }
644 const startDate = new Date();
645 const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000);
646 let compositeSchedule: OCPP16ChargingSchedule;
647 for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)
648 .chargingProfiles) {
649 // FIXME: build the composite schedule including the local power limit, the stack level, the charging rate unit, etc.
650 if (
651 chargingProfile.chargingSchedule?.startSchedule >= startDate &&
652 chargingProfile.chargingSchedule?.startSchedule <= endDate
653 ) {
654 compositeSchedule = chargingProfile.chargingSchedule;
655 break;
656 }
657 }
658 return {
659 status: GenericStatus.Accepted,
660 scheduleStart: compositeSchedule?.startSchedule,
661 connectorId: commandPayload.connectorId,
662 chargingSchedule: compositeSchedule,
663 };
664 }
665
666 private handleRequestClearChargingProfile(
667 chargingStation: ChargingStation,
668 commandPayload: ClearChargingProfileRequest
669 ): ClearChargingProfileResponse {
670 if (
671 OCPP16ServiceUtils.checkFeatureProfile(
672 chargingStation,
673 OCPP16SupportedFeatureProfiles.SmartCharging,
674 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
675 ) === false
676 ) {
677 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
678 }
679 if (chargingStation.hasConnector(commandPayload.connectorId) === false) {
680 logger.error(
681 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to
682 a non existing connector id ${commandPayload.connectorId}`
683 );
684 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
685 }
686 if (
687 !Utils.isNullOrUndefined(commandPayload.connectorId) &&
688 Utils.isNotEmptyArray(
689 chargingStation.getConnectorStatus(commandPayload.connectorId)?.chargingProfiles
690 )
691 ) {
692 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
693 logger.debug(
694 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
695 commandPayload.connectorId
696 }`
697 );
698 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
699 }
700 if (Utils.isNullOrUndefined(commandPayload.connectorId)) {
701 let clearedCP = false;
702 const clearChargingProfiles = (connectorStatus: ConnectorStatus) => {
703 if (Utils.isNotEmptyArray(connectorStatus?.chargingProfiles)) {
704 connectorStatus?.chargingProfiles?.forEach(
705 (chargingProfile: OCPP16ChargingProfile, index: number) => {
706 let clearCurrentCP = false;
707 if (chargingProfile.chargingProfileId === commandPayload.id) {
708 clearCurrentCP = true;
709 }
710 if (
711 !commandPayload.chargingProfilePurpose &&
712 chargingProfile.stackLevel === commandPayload.stackLevel
713 ) {
714 clearCurrentCP = true;
715 }
716 if (
717 !chargingProfile.stackLevel &&
718 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
719 ) {
720 clearCurrentCP = true;
721 }
722 if (
723 chargingProfile.stackLevel === commandPayload.stackLevel &&
724 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
725 ) {
726 clearCurrentCP = true;
727 }
728 if (clearCurrentCP) {
729 connectorStatus?.chargingProfiles?.splice(index, 1);
730 logger.debug(
731 `${chargingStation.logPrefix()} Matching charging profile(s) cleared: %j`,
732 chargingProfile
733 );
734 clearedCP = true;
735 }
736 }
737 );
738 }
739 };
740 if (chargingStation.hasEvses) {
741 for (const evseStatus of chargingStation.evses.values()) {
742 for (const connectorStatus of evseStatus.connectors.values()) {
743 clearChargingProfiles(connectorStatus);
744 }
745 }
746 } else {
747 for (const connectorId of chargingStation.connectors.keys()) {
748 clearChargingProfiles(chargingStation.getConnectorStatus(connectorId));
749 }
750 }
751 if (clearedCP) {
752 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
753 }
754 }
755 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
756 }
757
758 private async handleRequestChangeAvailability(
759 chargingStation: ChargingStation,
760 commandPayload: ChangeAvailabilityRequest
761 ): Promise<ChangeAvailabilityResponse> {
762 const connectorId: number = commandPayload.connectorId;
763 if (chargingStation.hasConnector(connectorId) === false) {
764 logger.error(
765 `${chargingStation.logPrefix()} Trying to change the availability of a
766 non existing connector id ${connectorId.toString()}`
767 );
768 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
769 }
770 const chargePointStatus: OCPP16ChargePointStatus =
771 commandPayload.type === OCPP16AvailabilityType.Operative
772 ? OCPP16ChargePointStatus.Available
773 : OCPP16ChargePointStatus.Unavailable;
774 if (connectorId === 0) {
775 let response: ChangeAvailabilityResponse =
776 OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
777 const changeAvailability = async (id: number, connectorStatus: ConnectorStatus) => {
778 if (connectorStatus?.transactionStarted === true) {
779 response = OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
780 }
781 connectorStatus.availability = commandPayload.type;
782 if (response === OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
783 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
784 chargingStation,
785 id,
786 chargePointStatus
787 );
788 }
789 };
790 if (chargingStation.hasEvses) {
791 for (const evseStatus of chargingStation.evses.values()) {
792 for (const [id, connectorStatus] of evseStatus.connectors) {
793 await changeAvailability(id, connectorStatus);
794 }
795 }
796 } else {
797 for (const id of chargingStation.connectors.keys()) {
798 await changeAvailability(id, chargingStation.getConnectorStatus(id));
799 }
800 }
801 return response;
802 } else if (
803 connectorId > 0 &&
804 (chargingStation.isChargingStationAvailable() === true ||
805 (chargingStation.isChargingStationAvailable() === false &&
806 commandPayload.type === OCPP16AvailabilityType.Inoperative))
807 ) {
808 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
809 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
810 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
811 }
812 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
813 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
814 chargingStation,
815 connectorId,
816 chargePointStatus
817 );
818 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
819 }
820 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
821 }
822
823 private async handleRequestRemoteStartTransaction(
824 chargingStation: ChargingStation,
825 commandPayload: RemoteStartTransactionRequest
826 ): Promise<GenericResponse> {
827 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload;
828 const reserved =
829 chargingStation.getConnectorStatus(transactionConnectorId).status ===
830 OCPP16ChargePointStatus.Reserved;
831 const reservedOnConnectorZero =
832 chargingStation.getConnectorStatus(0).status === OCPP16ChargePointStatus.Reserved;
833 if (
834 (reserved &&
835 !chargingStation.validateIncomingRequestWithReservation(transactionConnectorId, idTag)) ||
836 (reservedOnConnectorZero && !chargingStation.validateIncomingRequestWithReservation(0, idTag))
837 ) {
838 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
839 }
840 if (chargingStation.hasConnector(transactionConnectorId) === false) {
841 return this.notifyRemoteStartTransactionRejected(
842 chargingStation,
843 transactionConnectorId,
844 idTag
845 );
846 }
847 if (
848 !chargingStation.isChargingStationAvailable() ||
849 !chargingStation.isConnectorAvailable(transactionConnectorId)
850 ) {
851 return this.notifyRemoteStartTransactionRejected(
852 chargingStation,
853 transactionConnectorId,
854 idTag
855 );
856 }
857 const remoteStartTransactionLogMsg = `
858 ${chargingStation.logPrefix()} Transaction remotely STARTED on ${
859 chargingStation.stationInfo.chargingStationId
860 }#${transactionConnectorId.toString()} for idTag '${idTag}'`;
861 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
862 chargingStation,
863 transactionConnectorId,
864 OCPP16ChargePointStatus.Preparing
865 );
866 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
867 // Check if authorized
868 if (
869 chargingStation.getAuthorizeRemoteTxRequests() &&
870 (await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
871 ) {
872 // Authorization successful, start transaction
873 if (
874 this.setRemoteStartTransactionChargingProfile(
875 chargingStation,
876 transactionConnectorId,
877 chargingProfile
878 ) === true
879 ) {
880 connectorStatus.transactionRemoteStarted = true;
881 const startTransactionPayload: Partial<StartTransactionRequest> = {
882 connectorId: transactionConnectorId,
883 idTag: idTag,
884 };
885 if (reserved || reservedOnConnectorZero) {
886 const reservation = chargingStation.getReservationBy(
887 ReservationFilterKey.CONNECTOR_ID,
888 reservedOnConnectorZero ? 0 : transactionConnectorId
889 );
890 startTransactionPayload.reservationId = reservation.id;
891 await chargingStation.removeReservation(
892 reservation,
893 ReservationTerminationReason.TRANSACTION_STARTED
894 );
895 }
896 if (
897 (
898 await chargingStation.ocppRequestService.requestHandler<
899 OCPP16StartTransactionRequest,
900 OCPP16StartTransactionResponse
901 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, startTransactionPayload)
902 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
903 ) {
904 logger.debug(remoteStartTransactionLogMsg);
905 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
906 }
907 return this.notifyRemoteStartTransactionRejected(
908 chargingStation,
909 transactionConnectorId,
910 idTag
911 );
912 }
913 return this.notifyRemoteStartTransactionRejected(
914 chargingStation,
915 transactionConnectorId,
916 idTag
917 );
918 }
919 // No authorization check required, start transaction
920 if (
921 this.setRemoteStartTransactionChargingProfile(
922 chargingStation,
923 transactionConnectorId,
924 chargingProfile
925 ) === true
926 ) {
927 connectorStatus.transactionRemoteStarted = true;
928 if (
929 (
930 await chargingStation.ocppRequestService.requestHandler<
931 OCPP16StartTransactionRequest,
932 OCPP16StartTransactionResponse
933 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
934 connectorId: transactionConnectorId,
935 idTag,
936 })
937 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
938 ) {
939 logger.debug(remoteStartTransactionLogMsg);
940 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
941 }
942 return this.notifyRemoteStartTransactionRejected(
943 chargingStation,
944 transactionConnectorId,
945 idTag
946 );
947 }
948 return this.notifyRemoteStartTransactionRejected(
949 chargingStation,
950 transactionConnectorId,
951 idTag
952 );
953 }
954
955 private async notifyRemoteStartTransactionRejected(
956 chargingStation: ChargingStation,
957 connectorId: number,
958 idTag: string
959 ): Promise<GenericResponse> {
960 if (
961 chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available
962 ) {
963 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
964 chargingStation,
965 connectorId,
966 OCPP16ChargePointStatus.Available
967 );
968 }
969 logger.warn(
970 `${chargingStation.logPrefix()} Remote starting transaction REJECTED on connector id
971 ${connectorId.toString()}, idTag '${idTag}', availability '${
972 chargingStation.getConnectorStatus(connectorId)?.availability
973 }', status '${chargingStation.getConnectorStatus(connectorId)?.status}'`
974 );
975 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
976 }
977
978 private setRemoteStartTransactionChargingProfile(
979 chargingStation: ChargingStation,
980 connectorId: number,
981 cp: OCPP16ChargingProfile
982 ): boolean {
983 if (cp && cp.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
984 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
985 logger.debug(
986 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction
987 on connector id ${connectorId}: %j`,
988 cp
989 );
990 return true;
991 } else if (cp && cp.chargingProfilePurpose !== OCPP16ChargingProfilePurposeType.TX_PROFILE) {
992 logger.warn(
993 `${chargingStation.logPrefix()} Not allowed to set ${
994 cp.chargingProfilePurpose
995 } charging profile(s) at remote start transaction`
996 );
997 return false;
998 } else if (!cp) {
999 return true;
1000 }
1001 }
1002
1003 private async handleRequestRemoteStopTransaction(
1004 chargingStation: ChargingStation,
1005 commandPayload: RemoteStopTransactionRequest
1006 ): Promise<GenericResponse> {
1007 const transactionId = commandPayload.transactionId;
1008 const remoteStopTransaction = async (connectorId: number): Promise<GenericResponse> => {
1009 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1010 chargingStation,
1011 connectorId,
1012 OCPP16ChargePointStatus.Finishing
1013 );
1014 const stopResponse = await chargingStation.stopTransactionOnConnector(
1015 connectorId,
1016 OCPP16StopTransactionReason.REMOTE
1017 );
1018 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
1019 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
1020 }
1021 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1022 };
1023 if (chargingStation.hasEvses) {
1024 for (const [evseId, evseStatus] of chargingStation.evses) {
1025 if (evseId > 0) {
1026 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1027 if (connectorStatus.transactionId === transactionId) {
1028 return remoteStopTransaction(connectorId);
1029 }
1030 }
1031 }
1032 }
1033 } else {
1034 for (const connectorId of chargingStation.connectors.keys()) {
1035 if (
1036 connectorId > 0 &&
1037 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1038 ) {
1039 return remoteStopTransaction(connectorId);
1040 }
1041 }
1042 }
1043 logger.warn(
1044 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1045 ${transactionId.toString()}`
1046 );
1047 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1048 }
1049
1050 private handleRequestUpdateFirmware(
1051 chargingStation: ChargingStation,
1052 commandPayload: OCPP16UpdateFirmwareRequest
1053 ): OCPP16UpdateFirmwareResponse {
1054 if (
1055 OCPP16ServiceUtils.checkFeatureProfile(
1056 chargingStation,
1057 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1058 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1059 ) === false
1060 ) {
1061 logger.warn(
1062 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1063 Cannot simulate firmware update: feature profile not supported`
1064 );
1065 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1066 }
1067 if (
1068 !Utils.isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1069 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1070 ) {
1071 logger.warn(
1072 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1073 Cannot simulate firmware update: firmware update is already in progress`
1074 );
1075 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1076 }
1077 const retrieveDate = Utils.convertToDate(commandPayload.retrieveDate);
1078 const now = Date.now();
1079 if (retrieveDate?.getTime() <= now) {
1080 this.runInAsyncScope(
1081 this.updateFirmwareSimulation.bind(this) as (
1082 this: OCPP16IncomingRequestService,
1083 ...args: any[]
1084 ) => Promise<void>,
1085 this,
1086 chargingStation
1087 ).catch(Constants.EMPTY_FUNCTION);
1088 } else {
1089 setTimeout(() => {
1090 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION);
1091 }, retrieveDate?.getTime() - now);
1092 }
1093 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1094 }
1095
1096 private async updateFirmwareSimulation(
1097 chargingStation: ChargingStation,
1098 maxDelay = 30,
1099 minDelay = 15
1100 ): Promise<void> {
1101 if (
1102 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1103 false
1104 ) {
1105 return;
1106 }
1107 if (chargingStation.hasEvses) {
1108 for (const [evseId, evseStatus] of chargingStation.evses) {
1109 if (evseId > 0) {
1110 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1111 if (connectorStatus?.transactionStarted === false) {
1112 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1113 chargingStation,
1114 connectorId,
1115 OCPP16ChargePointStatus.Unavailable
1116 );
1117 }
1118 }
1119 }
1120 }
1121 } else {
1122 for (const connectorId of chargingStation.connectors.keys()) {
1123 if (
1124 connectorId > 0 &&
1125 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1126 ) {
1127 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1128 chargingStation,
1129 connectorId,
1130 OCPP16ChargePointStatus.Unavailable
1131 );
1132 }
1133 }
1134 }
1135 await chargingStation.ocppRequestService.requestHandler<
1136 OCPP16FirmwareStatusNotificationRequest,
1137 OCPP16FirmwareStatusNotificationResponse
1138 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1139 status: OCPP16FirmwareStatus.Downloading,
1140 });
1141 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1142 if (
1143 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1144 OCPP16FirmwareStatus.DownloadFailed
1145 ) {
1146 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
1147 await chargingStation.ocppRequestService.requestHandler<
1148 OCPP16FirmwareStatusNotificationRequest,
1149 OCPP16FirmwareStatusNotificationResponse
1150 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1151 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1152 });
1153 chargingStation.stationInfo.firmwareStatus =
1154 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1155 return;
1156 }
1157 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
1158 await chargingStation.ocppRequestService.requestHandler<
1159 OCPP16FirmwareStatusNotificationRequest,
1160 OCPP16FirmwareStatusNotificationResponse
1161 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1162 status: OCPP16FirmwareStatus.Downloaded,
1163 });
1164 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1165 let wasTransactionsStarted = false;
1166 let transactionsStarted: boolean;
1167 do {
1168 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1169 if (runningTransactions > 0) {
1170 const waitTime = 15 * 1000;
1171 logger.debug(
1172 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1173 ${runningTransactions} transaction(s) in progress, waiting ${
1174 waitTime / 1000
1175 } seconds before continuing firmware update simulation`
1176 );
1177 await Utils.sleep(waitTime);
1178 transactionsStarted = true;
1179 wasTransactionsStarted = true;
1180 } else {
1181 if (chargingStation.hasEvses) {
1182 for (const [evseId, evseStatus] of chargingStation.evses) {
1183 if (evseId > 0) {
1184 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1185 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1186 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1187 chargingStation,
1188 connectorId,
1189 OCPP16ChargePointStatus.Unavailable
1190 );
1191 }
1192 }
1193 }
1194 }
1195 } else {
1196 for (const connectorId of chargingStation.connectors.keys()) {
1197 if (
1198 connectorId > 0 &&
1199 chargingStation.getConnectorStatus(connectorId)?.status !==
1200 OCPP16ChargePointStatus.Unavailable
1201 ) {
1202 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1203 chargingStation,
1204 connectorId,
1205 OCPP16ChargePointStatus.Unavailable
1206 );
1207 }
1208 }
1209 }
1210 transactionsStarted = false;
1211 }
1212 } while (transactionsStarted);
1213 !wasTransactionsStarted &&
1214 (await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000));
1215 if (
1216 ChargingStationUtils.checkChargingStation(chargingStation, chargingStation.logPrefix()) ===
1217 false
1218 ) {
1219 return;
1220 }
1221 await chargingStation.ocppRequestService.requestHandler<
1222 OCPP16FirmwareStatusNotificationRequest,
1223 OCPP16FirmwareStatusNotificationResponse
1224 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1225 status: OCPP16FirmwareStatus.Installing,
1226 });
1227 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1228 if (
1229 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1230 OCPP16FirmwareStatus.InstallationFailed
1231 ) {
1232 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
1233 await chargingStation.ocppRequestService.requestHandler<
1234 OCPP16FirmwareStatusNotificationRequest,
1235 OCPP16FirmwareStatusNotificationResponse
1236 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1237 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1238 });
1239 chargingStation.stationInfo.firmwareStatus =
1240 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1241 return;
1242 }
1243 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1244 await Utils.sleep(Utils.getRandomInteger(maxDelay, minDelay) * 1000);
1245 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1246 }
1247 }
1248
1249 private async handleRequestGetDiagnostics(
1250 chargingStation: ChargingStation,
1251 commandPayload: GetDiagnosticsRequest
1252 ): Promise<GetDiagnosticsResponse> {
1253 if (
1254 OCPP16ServiceUtils.checkFeatureProfile(
1255 chargingStation,
1256 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1257 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1258 ) === false
1259 ) {
1260 logger.warn(
1261 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1262 Cannot get diagnostics: feature profile not supported`
1263 );
1264 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1265 }
1266 const uri = new URL(commandPayload.location);
1267 if (uri.protocol.startsWith('ftp:')) {
1268 let ftpClient: Client;
1269 try {
1270 const logFiles = fs
1271 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'))
1272 .filter((file) => file.endsWith('.log'))
1273 .map((file) => path.join('./', file));
1274 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1275 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1276 ftpClient = new Client();
1277 const accessResponse = await ftpClient.access({
1278 host: uri.host,
1279 ...(Utils.isNotEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1280 ...(Utils.isNotEmptyString(uri.username) && { user: uri.username }),
1281 ...(Utils.isNotEmptyString(uri.password) && { password: uri.password }),
1282 });
1283 let uploadResponse: FTPResponse;
1284 if (accessResponse.code === 220) {
1285 ftpClient.trackProgress((info) => {
1286 logger.info(
1287 `${chargingStation.logPrefix()} ${
1288 info.bytes / 1024
1289 } bytes transferred from diagnostics archive ${info.name}`
1290 );
1291 chargingStation.ocppRequestService
1292 .requestHandler<
1293 OCPP16DiagnosticsStatusNotificationRequest,
1294 OCPP16DiagnosticsStatusNotificationResponse
1295 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1296 status: OCPP16DiagnosticsStatus.Uploading,
1297 })
1298 .catch((error) => {
1299 logger.error(
1300 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1301 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1302 error
1303 );
1304 });
1305 });
1306 uploadResponse = await ftpClient.uploadFrom(
1307 path.join(
1308 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../'),
1309 diagnosticsArchive
1310 ),
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 OCPPConstants.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 OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED;
1545 }
1546 if (connectorId === 0 && !chargingStation.getReservationOnConnectorId0Enabled()) {
1547 return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED;
1548 }
1549 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1550 return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED;
1551 }
1552 switch (chargingStation.getConnectorStatus(connectorId).status) {
1553 case ConnectorStatusEnum.Faulted:
1554 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_FAULTED;
1555 break;
1556 case ConnectorStatusEnum.Occupied:
1557 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1558 break;
1559 case ConnectorStatusEnum.Unavailable:
1560 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1561 break;
1562 case ConnectorStatusEnum.Reserved:
1563 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1564 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1565 break;
1566 }
1567 // eslint-disable-next-line no-fallthrough
1568 default:
1569 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1570 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1571 break;
1572 }
1573 await chargingStation.addReservation({
1574 id: commandPayload.reservationId,
1575 ...commandPayload,
1576 });
1577 response = OCPPConstants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1578 break;
1579 }
1580 return response;
1581 } catch (error) {
1582 chargingStation.getConnectorStatus(connectorId).status = ConnectorStatusEnum.Available;
1583 return this.handleIncomingRequestError(
1584 chargingStation,
1585 OCPP16IncomingRequestCommand.RESERVE_NOW,
1586 error as Error,
1587 { errorResponse: OCPPConstants.OCPP_RESERVATION_RESPONSE_FAULTED }
1588 );
1589 }
1590 }
1591
1592 private async handleRequestCancelReservation(
1593 chargingStation: ChargingStation,
1594 commandPayload: OCPP16CancelReservationRequest
1595 ): Promise<OCPP16CancelReservationResponse> {
1596 if (
1597 !OCPP16ServiceUtils.checkFeatureProfile(
1598 chargingStation,
1599 OCPP16SupportedFeatureProfiles.Reservation,
1600 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1601 )
1602 ) {
1603 return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1604 }
1605 try {
1606 const { reservationId } = commandPayload;
1607 const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
1608 if (!exists) {
1609 logger.error(
1610 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1611 does not exist on charging station`
1612 );
1613 return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1614 }
1615 await chargingStation.removeReservation(reservation);
1616 return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1617 } catch (error) {
1618 return this.handleIncomingRequestError(
1619 chargingStation,
1620 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1621 error as Error,
1622 { errorResponse: OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
1623 );
1624 }
1625 }
1626 }