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