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