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