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