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