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