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