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