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