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