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