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