refactor: factor out remote stop transaction 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 if (chargingStation.hasEvses) {
1006 for (const [evseId, evseStatus] of chargingStation.evses) {
1007 if (evseId > 0) {
1008 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1009 if (connectorStatus.transactionId === transactionId) {
1010 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1011 }
1012 }
1013 }
1014 }
1015 } else {
1016 for (const connectorId of chargingStation.connectors.keys()) {
1017 if (
1018 connectorId > 0 &&
1019 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
1020 ) {
1021 return OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId);
1022 }
1023 }
1024 }
1025 logger.warn(
1026 `${chargingStation.logPrefix()} Trying to remote stop a non existing transaction with id:
1027 ${transactionId.toString()}`,
1028 );
1029 return OCPP16Constants.OCPP_RESPONSE_REJECTED;
1030 }
1031
1032 private handleRequestUpdateFirmware(
1033 chargingStation: ChargingStation,
1034 commandPayload: OCPP16UpdateFirmwareRequest,
1035 ): OCPP16UpdateFirmwareResponse {
1036 if (
1037 OCPP16ServiceUtils.checkFeatureProfile(
1038 chargingStation,
1039 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1040 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
1041 ) === false
1042 ) {
1043 logger.warn(
1044 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1045 Cannot simulate firmware update: feature profile not supported`,
1046 );
1047 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1048 }
1049 if (
1050 !isNullOrUndefined(chargingStation.stationInfo.firmwareStatus) &&
1051 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1052 ) {
1053 logger.warn(
1054 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware:
1055 Cannot simulate firmware update: firmware update is already in progress`,
1056 );
1057 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1058 }
1059 const retrieveDate = convertToDate(commandPayload.retrieveDate)!;
1060 const now = Date.now();
1061 if (retrieveDate?.getTime() <= now) {
1062 this.runInAsyncScope(
1063 this.updateFirmwareSimulation.bind(this) as (
1064 this: OCPP16IncomingRequestService,
1065 ...args: unknown[]
1066 ) => Promise<void>,
1067 this,
1068 chargingStation,
1069 ).catch(Constants.EMPTY_FUNCTION);
1070 } else {
1071 setTimeout(
1072 () => {
1073 this.runInAsyncScope(
1074 this.updateFirmwareSimulation.bind(this) as (
1075 this: OCPP16IncomingRequestService,
1076 ...args: unknown[]
1077 ) => Promise<void>,
1078 this,
1079 chargingStation,
1080 ).catch(Constants.EMPTY_FUNCTION);
1081 },
1082 retrieveDate?.getTime() - now,
1083 );
1084 }
1085 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1086 }
1087
1088 private async updateFirmwareSimulation(
1089 chargingStation: ChargingStation,
1090 maxDelay = 30,
1091 minDelay = 15,
1092 ): Promise<void> {
1093 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1094 return;
1095 }
1096 if (chargingStation.hasEvses) {
1097 for (const [evseId, evseStatus] of chargingStation.evses) {
1098 if (evseId > 0) {
1099 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1100 if (connectorStatus?.transactionStarted === false) {
1101 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1102 chargingStation,
1103 connectorId,
1104 OCPP16ChargePointStatus.Unavailable,
1105 );
1106 }
1107 }
1108 }
1109 }
1110 } else {
1111 for (const connectorId of chargingStation.connectors.keys()) {
1112 if (
1113 connectorId > 0 &&
1114 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1115 ) {
1116 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1117 chargingStation,
1118 connectorId,
1119 OCPP16ChargePointStatus.Unavailable,
1120 );
1121 }
1122 }
1123 }
1124 await chargingStation.ocppRequestService.requestHandler<
1125 OCPP16FirmwareStatusNotificationRequest,
1126 OCPP16FirmwareStatusNotificationResponse
1127 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1128 status: OCPP16FirmwareStatus.Downloading,
1129 });
1130 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading;
1131 if (
1132 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1133 OCPP16FirmwareStatus.DownloadFailed
1134 ) {
1135 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1136 await chargingStation.ocppRequestService.requestHandler<
1137 OCPP16FirmwareStatusNotificationRequest,
1138 OCPP16FirmwareStatusNotificationResponse
1139 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1140 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1141 });
1142 chargingStation.stationInfo.firmwareStatus =
1143 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1144 return;
1145 }
1146 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1147 await chargingStation.ocppRequestService.requestHandler<
1148 OCPP16FirmwareStatusNotificationRequest,
1149 OCPP16FirmwareStatusNotificationResponse
1150 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1151 status: OCPP16FirmwareStatus.Downloaded,
1152 });
1153 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloaded;
1154 let wasTransactionsStarted = false;
1155 let transactionsStarted: boolean;
1156 do {
1157 const runningTransactions = chargingStation.getNumberOfRunningTransactions();
1158 if (runningTransactions > 0) {
1159 const waitTime = secondsToMilliseconds(15);
1160 logger.debug(
1161 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
1162 ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1163 waitTime,
1164 )} before continuing firmware update simulation`,
1165 );
1166 await sleep(waitTime);
1167 transactionsStarted = true;
1168 wasTransactionsStarted = true;
1169 } else {
1170 if (chargingStation.hasEvses) {
1171 for (const [evseId, evseStatus] of chargingStation.evses) {
1172 if (evseId > 0) {
1173 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1174 if (connectorStatus?.status !== OCPP16ChargePointStatus.Unavailable) {
1175 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1176 chargingStation,
1177 connectorId,
1178 OCPP16ChargePointStatus.Unavailable,
1179 );
1180 }
1181 }
1182 }
1183 }
1184 } else {
1185 for (const connectorId of chargingStation.connectors.keys()) {
1186 if (
1187 connectorId > 0 &&
1188 chargingStation.getConnectorStatus(connectorId)?.status !==
1189 OCPP16ChargePointStatus.Unavailable
1190 ) {
1191 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1192 chargingStation,
1193 connectorId,
1194 OCPP16ChargePointStatus.Unavailable,
1195 );
1196 }
1197 }
1198 }
1199 transactionsStarted = false;
1200 }
1201 } while (transactionsStarted);
1202 !wasTransactionsStarted &&
1203 (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
1204 if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
1205 return;
1206 }
1207 await chargingStation.ocppRequestService.requestHandler<
1208 OCPP16FirmwareStatusNotificationRequest,
1209 OCPP16FirmwareStatusNotificationResponse
1210 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1211 status: OCPP16FirmwareStatus.Installing,
1212 });
1213 chargingStation.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Installing;
1214 if (
1215 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1216 OCPP16FirmwareStatus.InstallationFailed
1217 ) {
1218 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1219 await chargingStation.ocppRequestService.requestHandler<
1220 OCPP16FirmwareStatusNotificationRequest,
1221 OCPP16FirmwareStatusNotificationResponse
1222 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1223 status: chargingStation.stationInfo?.firmwareUpgrade?.failureStatus,
1224 });
1225 chargingStation.stationInfo.firmwareStatus =
1226 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
1227 return;
1228 }
1229 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1230 await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
1231 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
1232 }
1233 }
1234
1235 private async handleRequestGetDiagnostics(
1236 chargingStation: ChargingStation,
1237 commandPayload: GetDiagnosticsRequest,
1238 ): Promise<GetDiagnosticsResponse> {
1239 if (
1240 OCPP16ServiceUtils.checkFeatureProfile(
1241 chargingStation,
1242 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1243 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1244 ) === false
1245 ) {
1246 logger.warn(
1247 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1248 Cannot get diagnostics: feature profile not supported`,
1249 );
1250 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1251 }
1252 const uri = new URL(commandPayload.location);
1253 if (uri.protocol.startsWith('ftp:')) {
1254 let ftpClient: Client | undefined;
1255 try {
1256 const logFiles = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), '../'))
1257 .filter((file) => file.endsWith('.log'))
1258 .map((file) => join('./', file));
1259 const diagnosticsArchive = `${chargingStation.stationInfo.chargingStationId}_logs.tar.gz`;
1260 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive));
1261 ftpClient = new Client();
1262 const accessResponse = await ftpClient.access({
1263 host: uri.host,
1264 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1265 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1266 ...(isNotEmptyString(uri.password) && { password: uri.password }),
1267 });
1268 let uploadResponse: FTPResponse | undefined;
1269 if (accessResponse.code === 220) {
1270 ftpClient.trackProgress((info) => {
1271 logger.info(
1272 `${chargingStation.logPrefix()} ${
1273 info.bytes / 1024
1274 } bytes transferred from diagnostics archive ${info.name}`,
1275 );
1276 chargingStation.ocppRequestService
1277 .requestHandler<
1278 OCPP16DiagnosticsStatusNotificationRequest,
1279 OCPP16DiagnosticsStatusNotificationResponse
1280 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1281 status: OCPP16DiagnosticsStatus.Uploading,
1282 })
1283 .catch((error) => {
1284 logger.error(
1285 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics:
1286 Error while sending '${OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION}'`,
1287 error,
1288 );
1289 });
1290 });
1291 uploadResponse = await ftpClient.uploadFrom(
1292 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1293 `${uri.pathname}${diagnosticsArchive}`,
1294 );
1295 if (uploadResponse.code === 226) {
1296 await chargingStation.ocppRequestService.requestHandler<
1297 OCPP16DiagnosticsStatusNotificationRequest,
1298 OCPP16DiagnosticsStatusNotificationResponse
1299 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1300 status: OCPP16DiagnosticsStatus.Uploaded,
1301 });
1302 if (ftpClient) {
1303 ftpClient.close();
1304 }
1305 return { fileName: diagnosticsArchive };
1306 }
1307 throw new OCPPError(
1308 ErrorType.GENERIC_ERROR,
1309 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1310 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1311 }`,
1312 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1313 );
1314 }
1315 throw new OCPPError(
1316 ErrorType.GENERIC_ERROR,
1317 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1318 uploadResponse?.code && `|${uploadResponse?.code.toString()}`
1319 }`,
1320 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1321 );
1322 } catch (error) {
1323 await chargingStation.ocppRequestService.requestHandler<
1324 OCPP16DiagnosticsStatusNotificationRequest,
1325 OCPP16DiagnosticsStatusNotificationResponse
1326 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1327 status: OCPP16DiagnosticsStatus.UploadFailed,
1328 });
1329 if (ftpClient) {
1330 ftpClient.close();
1331 }
1332 return this.handleIncomingRequestError<GetDiagnosticsResponse>(
1333 chargingStation,
1334 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1335 error as Error,
1336 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY },
1337 )!;
1338 }
1339 } else {
1340 logger.error(
1341 `${chargingStation.logPrefix()} Unsupported protocol ${
1342 uri.protocol
1343 } to transfer the diagnostic logs archive`,
1344 );
1345 await chargingStation.ocppRequestService.requestHandler<
1346 OCPP16DiagnosticsStatusNotificationRequest,
1347 OCPP16DiagnosticsStatusNotificationResponse
1348 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1349 status: OCPP16DiagnosticsStatus.UploadFailed,
1350 });
1351 return OCPP16Constants.OCPP_RESPONSE_EMPTY;
1352 }
1353 }
1354
1355 private handleRequestTriggerMessage(
1356 chargingStation: ChargingStation,
1357 commandPayload: OCPP16TriggerMessageRequest,
1358 ): OCPP16TriggerMessageResponse {
1359 if (
1360 !OCPP16ServiceUtils.checkFeatureProfile(
1361 chargingStation,
1362 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1363 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1364 ) ||
1365 !OCPP16ServiceUtils.isMessageTriggerSupported(
1366 chargingStation,
1367 commandPayload.requestedMessage,
1368 )
1369 ) {
1370 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1371 }
1372 if (
1373 !OCPP16ServiceUtils.isConnectorIdValid(
1374 chargingStation,
1375 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1376 commandPayload.connectorId!,
1377 )
1378 ) {
1379 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1380 }
1381 try {
1382 switch (commandPayload.requestedMessage) {
1383 case OCPP16MessageTrigger.BootNotification:
1384 setTimeout(() => {
1385 chargingStation.ocppRequestService
1386 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1387 chargingStation,
1388 OCPP16RequestCommand.BOOT_NOTIFICATION,
1389 chargingStation.bootNotificationRequest,
1390 { skipBufferingOnError: true, triggerMessage: true },
1391 )
1392 .then((response) => {
1393 chargingStation.bootNotificationResponse = response;
1394 })
1395 .catch(Constants.EMPTY_FUNCTION);
1396 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1397 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1398 case OCPP16MessageTrigger.Heartbeat:
1399 setTimeout(() => {
1400 chargingStation.ocppRequestService
1401 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1402 chargingStation,
1403 OCPP16RequestCommand.HEARTBEAT,
1404 null,
1405 {
1406 triggerMessage: true,
1407 },
1408 )
1409 .catch(Constants.EMPTY_FUNCTION);
1410 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1411 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1412 case OCPP16MessageTrigger.StatusNotification:
1413 setTimeout(() => {
1414 if (!isNullOrUndefined(commandPayload?.connectorId)) {
1415 chargingStation.ocppRequestService
1416 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1417 chargingStation,
1418 OCPP16RequestCommand.STATUS_NOTIFICATION,
1419 {
1420 connectorId: commandPayload.connectorId,
1421 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1422 status: chargingStation.getConnectorStatus(commandPayload.connectorId!)?.status,
1423 },
1424 {
1425 triggerMessage: true,
1426 },
1427 )
1428 .catch(Constants.EMPTY_FUNCTION);
1429 } else {
1430 // eslint-disable-next-line no-lonely-if
1431 if (chargingStation.hasEvses) {
1432 for (const evseStatus of chargingStation.evses.values()) {
1433 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1434 chargingStation.ocppRequestService
1435 .requestHandler<
1436 OCPP16StatusNotificationRequest,
1437 OCPP16StatusNotificationResponse
1438 >(
1439 chargingStation,
1440 OCPP16RequestCommand.STATUS_NOTIFICATION,
1441 {
1442 connectorId,
1443 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1444 status: connectorStatus.status,
1445 },
1446 {
1447 triggerMessage: true,
1448 },
1449 )
1450 .catch(Constants.EMPTY_FUNCTION);
1451 }
1452 }
1453 } else {
1454 for (const connectorId of chargingStation.connectors.keys()) {
1455 chargingStation.ocppRequestService
1456 .requestHandler<
1457 OCPP16StatusNotificationRequest,
1458 OCPP16StatusNotificationResponse
1459 >(
1460 chargingStation,
1461 OCPP16RequestCommand.STATUS_NOTIFICATION,
1462 {
1463 connectorId,
1464 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1465 status: chargingStation.getConnectorStatus(connectorId)?.status,
1466 },
1467 {
1468 triggerMessage: true,
1469 },
1470 )
1471 .catch(Constants.EMPTY_FUNCTION);
1472 }
1473 }
1474 }
1475 }, OCPP16Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1476 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1477 default:
1478 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1479 }
1480 } catch (error) {
1481 return this.handleIncomingRequestError<OCPP16TriggerMessageResponse>(
1482 chargingStation,
1483 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1484 error as Error,
1485 { errorResponse: OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED },
1486 )!;
1487 }
1488 }
1489
1490 private handleRequestDataTransfer(
1491 chargingStation: ChargingStation,
1492 commandPayload: OCPP16DataTransferRequest,
1493 ): OCPP16DataTransferResponse {
1494 try {
1495 if (Object.values(OCPP16DataTransferVendorId).includes(commandPayload.vendorId)) {
1496 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED;
1497 }
1498 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID;
1499 } catch (error) {
1500 return this.handleIncomingRequestError<OCPP16DataTransferResponse>(
1501 chargingStation,
1502 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1503 error as Error,
1504 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED },
1505 )!;
1506 }
1507 }
1508
1509 private async handleRequestReserveNow(
1510 chargingStation: ChargingStation,
1511 commandPayload: OCPP16ReserveNowRequest,
1512 ): Promise<OCPP16ReserveNowResponse> {
1513 if (
1514 !OCPP16ServiceUtils.checkFeatureProfile(
1515 chargingStation,
1516 OCPP16SupportedFeatureProfiles.Reservation,
1517 OCPP16IncomingRequestCommand.RESERVE_NOW,
1518 )
1519 ) {
1520 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1521 }
1522 const { reservationId, idTag, connectorId } = commandPayload;
1523 let response: OCPP16ReserveNowResponse;
1524 try {
1525 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1526 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1527 }
1528 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1529 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1530 }
1531 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1532 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED;
1533 }
1534 switch (chargingStation.getConnectorStatus(connectorId)!.status) {
1535 case OCPP16ChargePointStatus.Faulted:
1536 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED;
1537 break;
1538 case OCPP16ChargePointStatus.Preparing:
1539 case OCPP16ChargePointStatus.Charging:
1540 case OCPP16ChargePointStatus.SuspendedEV:
1541 case OCPP16ChargePointStatus.SuspendedEVSE:
1542 case OCPP16ChargePointStatus.Finishing:
1543 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1544 break;
1545 case OCPP16ChargePointStatus.Unavailable:
1546 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
1547 break;
1548 case OCPP16ChargePointStatus.Reserved:
1549 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1550 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1551 break;
1552 }
1553 // eslint-disable-next-line no-fallthrough
1554 default:
1555 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1556 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
1557 break;
1558 }
1559 await chargingStation.addReservation({
1560 id: commandPayload.reservationId,
1561 ...commandPayload,
1562 });
1563 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
1564 break;
1565 }
1566 return response;
1567 } catch (error) {
1568 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available;
1569 return this.handleIncomingRequestError<OCPP16ReserveNowResponse>(
1570 chargingStation,
1571 OCPP16IncomingRequestCommand.RESERVE_NOW,
1572 error as Error,
1573 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED },
1574 )!;
1575 }
1576 }
1577
1578 private async handleRequestCancelReservation(
1579 chargingStation: ChargingStation,
1580 commandPayload: OCPP16CancelReservationRequest,
1581 ): Promise<GenericResponse> {
1582 if (
1583 !OCPP16ServiceUtils.checkFeatureProfile(
1584 chargingStation,
1585 OCPP16SupportedFeatureProfiles.Reservation,
1586 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1587 )
1588 ) {
1589 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1590 }
1591 try {
1592 const { reservationId } = commandPayload;
1593 const reservation = chargingStation.getReservationBy('reservationId', reservationId);
1594 if (isUndefined(reservation)) {
1595 logger.error(
1596 `${chargingStation.logPrefix()} Reservation with ID ${reservationId}
1597 does not exist on charging station`,
1598 );
1599 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
1600 }
1601 await chargingStation.removeReservation(
1602 reservation!,
1603 ReservationTerminationReason.RESERVATION_CANCELED,
1604 );
1605 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
1606 } catch (error) {
1607 return this.handleIncomingRequestError<GenericResponse>(
1608 chargingStation,
1609 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1610 error as Error,
1611 { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED },
1612 )!;
1613 }
1614 }
1615 }