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