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