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