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