f5779ab2952bbf3eb3c660d79a7882539aa5a687
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
3 import fs from 'fs';
4 import path from 'path';
5 import { URL, fileURLToPath } from 'url';
6
7 import type { JSONSchemaType } from 'ajv';
8 import { Client, FTPResponse } from 'basic-ftp';
9 import tar from 'tar';
10
11 import OCPPError from '../../../exception/OCPPError';
12 import type { JsonObject, JsonType } from '../../../types/JsonType';
13 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
14 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
15 import {
16 ChargingProfilePurposeType,
17 OCPP16ChargingProfile,
18 } from '../../../types/ocpp/1.6/ChargingProfile';
19 import {
20 OCPP16StandardParametersKey,
21 OCPP16SupportedFeatureProfiles,
22 } from '../../../types/ocpp/1.6/Configuration';
23 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
24 import {
25 ChangeAvailabilityRequest,
26 ChangeConfigurationRequest,
27 ClearChargingProfileRequest,
28 DiagnosticsStatusNotificationRequest,
29 GetConfigurationRequest,
30 GetDiagnosticsRequest,
31 MessageTrigger,
32 OCPP16AvailabilityType,
33 OCPP16BootNotificationRequest,
34 OCPP16ClearCacheRequest,
35 OCPP16HeartbeatRequest,
36 OCPP16IncomingRequestCommand,
37 OCPP16RequestCommand,
38 OCPP16StatusNotificationRequest,
39 OCPP16TriggerMessageRequest,
40 RemoteStartTransactionRequest,
41 RemoteStopTransactionRequest,
42 ResetRequest,
43 SetChargingProfileRequest,
44 UnlockConnectorRequest,
45 } from '../../../types/ocpp/1.6/Requests';
46 import type {
47 ChangeAvailabilityResponse,
48 ChangeConfigurationResponse,
49 ClearChargingProfileResponse,
50 DiagnosticsStatusNotificationResponse,
51 GetConfigurationResponse,
52 GetDiagnosticsResponse,
53 OCPP16BootNotificationResponse,
54 OCPP16HeartbeatResponse,
55 OCPP16StatusNotificationResponse,
56 OCPP16TriggerMessageResponse,
57 SetChargingProfileResponse,
58 UnlockConnectorResponse,
59 } from '../../../types/ocpp/1.6/Responses';
60 import {
61 OCPP16AuthorizationStatus,
62 OCPP16AuthorizeRequest,
63 OCPP16AuthorizeResponse,
64 OCPP16StartTransactionRequest,
65 OCPP16StartTransactionResponse,
66 OCPP16StopTransactionReason,
67 } from '../../../types/ocpp/1.6/Transaction';
68 import type { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
69 import { ErrorType } from '../../../types/ocpp/ErrorType';
70 import type { IncomingRequestHandler } from '../../../types/ocpp/Requests';
71 import type { DefaultResponse } from '../../../types/ocpp/Responses';
72 import Constants from '../../../utils/Constants';
73 import logger from '../../../utils/Logger';
74 import Utils from '../../../utils/Utils';
75 import type ChargingStation from '../../ChargingStation';
76 import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
77 import { ChargingStationUtils } from '../../ChargingStationUtils';
78 import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
79 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
80
81 const moduleName = 'OCPP16IncomingRequestService';
82
83 export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
84 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
85 private jsonSchemas: Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>;
86
87 public constructor() {
88 if (new.target?.name === moduleName) {
89 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
90 }
91 super();
92 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
93 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
94 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
95 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
96 [
97 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
98 this.handleRequestGetConfiguration.bind(this),
99 ],
100 [
101 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
102 this.handleRequestChangeConfiguration.bind(this),
103 ],
104 [
105 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
106 this.handleRequestSetChargingProfile.bind(this),
107 ],
108 [
109 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
110 this.handleRequestClearChargingProfile.bind(this),
111 ],
112 [
113 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
114 this.handleRequestChangeAvailability.bind(this),
115 ],
116 [
117 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
118 this.handleRequestRemoteStartTransaction.bind(this),
119 ],
120 [
121 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
122 this.handleRequestRemoteStopTransaction.bind(this),
123 ],
124 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
125 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
126 ]);
127 this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
128 [
129 OCPP16IncomingRequestCommand.RESET,
130 JSON.parse(
131 fs.readFileSync(
132 path.resolve(
133 path.dirname(fileURLToPath(import.meta.url)),
134 '../../../assets/json-schemas/ocpp/1.6/Reset.json'
135 ),
136 'utf8'
137 )
138 ) as JSONSchemaType<ResetRequest>,
139 ],
140 [
141 OCPP16IncomingRequestCommand.CLEAR_CACHE,
142 JSON.parse(
143 fs.readFileSync(
144 path.resolve(
145 path.dirname(fileURLToPath(import.meta.url)),
146 '../../../assets/json-schemas/ocpp/1.6/ClearCache.json'
147 ),
148 'utf8'
149 )
150 ) as JSONSchemaType<OCPP16ClearCacheRequest>,
151 ],
152 [
153 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
154 JSON.parse(
155 fs.readFileSync(
156 path.resolve(
157 path.dirname(fileURLToPath(import.meta.url)),
158 '../../../assets/json-schemas/ocpp/1.6/UnlockConnector.json'
159 ),
160 'utf8'
161 )
162 ) as JSONSchemaType<UnlockConnectorRequest>,
163 ],
164 [
165 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
166 JSON.parse(
167 fs.readFileSync(
168 path.resolve(
169 path.dirname(fileURLToPath(import.meta.url)),
170 '../../../assets/json-schemas/ocpp/1.6/GetConfiguration.json'
171 ),
172 'utf8'
173 )
174 ) as JSONSchemaType<GetConfigurationRequest>,
175 ],
176 [
177 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
178 JSON.parse(
179 fs.readFileSync(
180 path.resolve(
181 path.dirname(fileURLToPath(import.meta.url)),
182 '../../../assets/json-schemas/ocpp/1.6/ChangeConfiguration.json'
183 ),
184 'utf8'
185 )
186 ) as JSONSchemaType<ChangeConfigurationRequest>,
187 ],
188 [
189 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
190 JSON.parse(
191 fs.readFileSync(
192 path.resolve(
193 path.dirname(fileURLToPath(import.meta.url)),
194 '../../../assets/json-schemas/ocpp/1.6/GetDiagnostics.json'
195 ),
196 'utf8'
197 )
198 ) as JSONSchemaType<GetDiagnosticsRequest>,
199 ],
200 [
201 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
202 JSON.parse(
203 fs.readFileSync(
204 path.resolve(
205 path.dirname(fileURLToPath(import.meta.url)),
206 '../../../assets/json-schemas/ocpp/1.6/SetChargingProfile.json'
207 ),
208 'utf8'
209 )
210 ) as JSONSchemaType<SetChargingProfileRequest>,
211 ],
212 [
213 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
214 JSON.parse(
215 fs.readFileSync(
216 path.resolve(
217 path.dirname(fileURLToPath(import.meta.url)),
218 '../../../assets/json-schemas/ocpp/1.6/ClearChargingProfile.json'
219 ),
220 'utf8'
221 )
222 ) as JSONSchemaType<ClearChargingProfileRequest>,
223 ],
224 [
225 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
226 JSON.parse(
227 fs.readFileSync(
228 path.resolve(
229 path.dirname(fileURLToPath(import.meta.url)),
230 '../../../assets/json-schemas/ocpp/1.6/ChangeAvailability.json'
231 ),
232 'utf8'
233 )
234 ) as JSONSchemaType<ChangeAvailabilityRequest>,
235 ],
236 [
237 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
238 JSON.parse(
239 fs.readFileSync(
240 path.resolve(
241 path.dirname(fileURLToPath(import.meta.url)),
242 '../../../assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json'
243 ),
244 'utf8'
245 )
246 ) as JSONSchemaType<RemoteStartTransactionRequest>,
247 ],
248 [
249 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
250 JSON.parse(
251 fs.readFileSync(
252 path.resolve(
253 path.dirname(fileURLToPath(import.meta.url)),
254 '../../../assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json'
255 ),
256 'utf8'
257 )
258 ) as JSONSchemaType<RemoteStopTransactionRequest>,
259 ],
260 [
261 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
262 JSON.parse(
263 fs.readFileSync(
264 path.resolve(
265 path.dirname(fileURLToPath(import.meta.url)),
266 '../../../assets/json-schemas/ocpp/1.6/TriggerMessage.json'
267 ),
268 'utf8'
269 )
270 ) as JSONSchemaType<OCPP16TriggerMessageRequest>,
271 ],
272 ]);
273 this.validatePayload.bind(this);
274 }
275
276 public async incomingRequestHandler(
277 chargingStation: ChargingStation,
278 messageId: string,
279 commandName: OCPP16IncomingRequestCommand,
280 commandPayload: JsonType
281 ): Promise<void> {
282 let response: JsonType;
283 if (
284 chargingStation.getOcppStrictCompliance() &&
285 chargingStation.isInPendingState() &&
286 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
287 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
288 ) {
289 throw new OCPPError(
290 ErrorType.SECURITY_ERROR,
291 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
292 commandPayload,
293 null,
294 2
295 )} while the charging station is in pending state on the central server`,
296 commandName,
297 commandPayload
298 );
299 }
300 if (
301 chargingStation.isRegistered() ||
302 (!chargingStation.getOcppStrictCompliance() && chargingStation.isInUnknownState())
303 ) {
304 if (
305 this.incomingRequestHandlers.has(commandName) &&
306 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
307 ) {
308 try {
309 this.validatePayload(chargingStation, commandName, commandPayload);
310 // Call the method to build the response
311 response = await this.incomingRequestHandlers.get(commandName)(
312 chargingStation,
313 commandPayload
314 );
315 } catch (error) {
316 // Log
317 logger.error(
318 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
319 error
320 );
321 throw error;
322 }
323 } else {
324 // Throw exception
325 throw new OCPPError(
326 ErrorType.NOT_IMPLEMENTED,
327 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
328 commandPayload,
329 null,
330 2
331 )}`,
332 commandName,
333 commandPayload
334 );
335 }
336 } else {
337 throw new OCPPError(
338 ErrorType.SECURITY_ERROR,
339 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
340 commandPayload,
341 null,
342 2
343 )} while the charging station is not registered on the central server.`,
344 commandName,
345 commandPayload
346 );
347 }
348 // Send the built response
349 await chargingStation.ocppRequestService.sendResponse(
350 chargingStation,
351 messageId,
352 response,
353 commandName
354 );
355 }
356
357 private validatePayload(
358 chargingStation: ChargingStation,
359 commandName: OCPP16IncomingRequestCommand,
360 commandPayload: JsonType
361 ): boolean {
362 if (this.jsonSchemas.has(commandName)) {
363 return this.validateIncomingRequestPayload(
364 chargingStation,
365 commandName,
366 this.jsonSchemas.get(commandName),
367 commandPayload
368 );
369 }
370 logger.warn(
371 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation`
372 );
373 return false;
374 }
375
376 // Simulate charging station restart
377 private handleRequestReset(
378 chargingStation: ChargingStation,
379 commandPayload: ResetRequest
380 ): DefaultResponse {
381 // eslint-disable-next-line @typescript-eslint/no-misused-promises
382 setImmediate(async (): Promise<void> => {
383 await chargingStation.reset((commandPayload.type + 'Reset') as OCPP16StopTransactionReason);
384 });
385 logger.info(
386 `${chargingStation.logPrefix()} ${
387 commandPayload.type
388 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
389 chargingStation.stationInfo.resetTime
390 )}`
391 );
392 return Constants.OCPP_RESPONSE_ACCEPTED;
393 }
394
395 private handleRequestClearCache(chargingStation: ChargingStation): DefaultResponse {
396 chargingStation.authorizedTagsCache.deleteAuthorizedTags(
397 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
398 );
399 return Constants.OCPP_RESPONSE_ACCEPTED;
400 }
401
402 private async handleRequestUnlockConnector(
403 chargingStation: ChargingStation,
404 commandPayload: UnlockConnectorRequest
405 ): Promise<UnlockConnectorResponse> {
406 const connectorId = commandPayload.connectorId;
407 if (connectorId === 0) {
408 logger.error(
409 chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
410 );
411 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
412 }
413 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
414 const stopResponse = await chargingStation.stopTransactionOnConnector(
415 connectorId,
416 OCPP16StopTransactionReason.UNLOCK_COMMAND
417 );
418 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
419 return Constants.OCPP_RESPONSE_UNLOCKED;
420 }
421 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
422 }
423 await chargingStation.ocppRequestService.requestHandler<
424 OCPP16StatusNotificationRequest,
425 OCPP16StatusNotificationResponse
426 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
427 connectorId,
428 status: OCPP16ChargePointStatus.AVAILABLE,
429 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
430 });
431 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
432 return Constants.OCPP_RESPONSE_UNLOCKED;
433 }
434
435 private handleRequestGetConfiguration(
436 chargingStation: ChargingStation,
437 commandPayload: GetConfigurationRequest
438 ): GetConfigurationResponse {
439 const configurationKey: OCPPConfigurationKey[] = [];
440 const unknownKey: string[] = [];
441 if (Utils.isEmptyArray(commandPayload.key) === true) {
442 for (const configuration of chargingStation.ocppConfiguration.configurationKey) {
443 if (Utils.isUndefined(configuration.visible) === true) {
444 configuration.visible = true;
445 }
446 if (configuration.visible === false) {
447 continue;
448 }
449 configurationKey.push({
450 key: configuration.key,
451 readonly: configuration.readonly,
452 value: configuration.value,
453 });
454 }
455 } else {
456 for (const key of commandPayload.key) {
457 const keyFound = ChargingStationConfigurationUtils.getConfigurationKey(
458 chargingStation,
459 key
460 );
461 if (keyFound) {
462 if (Utils.isUndefined(keyFound.visible) === true) {
463 keyFound.visible = true;
464 }
465 if (keyFound.visible === false) {
466 continue;
467 }
468 configurationKey.push({
469 key: keyFound.key,
470 readonly: keyFound.readonly,
471 value: keyFound.value,
472 });
473 } else {
474 unknownKey.push(key);
475 }
476 }
477 }
478 return {
479 configurationKey,
480 unknownKey,
481 };
482 }
483
484 private handleRequestChangeConfiguration(
485 chargingStation: ChargingStation,
486 commandPayload: ChangeConfigurationRequest
487 ): ChangeConfigurationResponse {
488 const keyToChange = ChargingStationConfigurationUtils.getConfigurationKey(
489 chargingStation,
490 commandPayload.key,
491 true
492 );
493 if (!keyToChange) {
494 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
495 } else if (keyToChange && keyToChange.readonly) {
496 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
497 } else if (keyToChange && !keyToChange.readonly) {
498 let valueChanged = false;
499 if (keyToChange.value !== commandPayload.value) {
500 ChargingStationConfigurationUtils.setConfigurationKeyValue(
501 chargingStation,
502 commandPayload.key,
503 commandPayload.value,
504 true
505 );
506 valueChanged = true;
507 }
508 let triggerHeartbeatRestart = false;
509 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
510 ChargingStationConfigurationUtils.setConfigurationKeyValue(
511 chargingStation,
512 OCPP16StandardParametersKey.HeartbeatInterval,
513 commandPayload.value
514 );
515 triggerHeartbeatRestart = true;
516 }
517 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
518 ChargingStationConfigurationUtils.setConfigurationKeyValue(
519 chargingStation,
520 OCPP16StandardParametersKey.HeartBeatInterval,
521 commandPayload.value
522 );
523 triggerHeartbeatRestart = true;
524 }
525 if (triggerHeartbeatRestart) {
526 chargingStation.restartHeartbeat();
527 }
528 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
529 chargingStation.restartWebSocketPing();
530 }
531 if (keyToChange.reboot) {
532 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
533 }
534 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
535 }
536 }
537
538 private handleRequestSetChargingProfile(
539 chargingStation: ChargingStation,
540 commandPayload: SetChargingProfileRequest
541 ): SetChargingProfileResponse {
542 if (
543 OCPP16ServiceUtils.checkFeatureProfile(
544 chargingStation,
545 OCPP16SupportedFeatureProfiles.SmartCharging,
546 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
547 ) === false
548 ) {
549 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED;
550 }
551 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
552 logger.error(
553 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
554 commandPayload.connectorId
555 }`
556 );
557 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
558 }
559 if (
560 commandPayload.csChargingProfiles.chargingProfilePurpose ===
561 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
562 commandPayload.connectorId !== 0
563 ) {
564 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
565 }
566 if (
567 commandPayload.csChargingProfiles.chargingProfilePurpose ===
568 ChargingProfilePurposeType.TX_PROFILE &&
569 (commandPayload.connectorId === 0 ||
570 chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted ===
571 false)
572 ) {
573 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
574 }
575 OCPP16ServiceUtils.setChargingProfile(
576 chargingStation,
577 commandPayload.connectorId,
578 commandPayload.csChargingProfiles
579 );
580 logger.debug(
581 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${
582 commandPayload.connectorId
583 }, dump their stack: %j`,
584 chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
585 );
586 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
587 }
588
589 private handleRequestClearChargingProfile(
590 chargingStation: ChargingStation,
591 commandPayload: ClearChargingProfileRequest
592 ): ClearChargingProfileResponse {
593 if (
594 OCPP16ServiceUtils.checkFeatureProfile(
595 chargingStation,
596 OCPP16SupportedFeatureProfiles.SmartCharging,
597 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
598 ) === false
599 ) {
600 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
601 }
602 if (chargingStation.connectors.has(commandPayload.connectorId) === false) {
603 logger.error(
604 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
605 commandPayload.connectorId
606 }`
607 );
608 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
609 }
610 const connectorStatus = chargingStation.getConnectorStatus(commandPayload.connectorId);
611 if (commandPayload.connectorId && !Utils.isEmptyArray(connectorStatus.chargingProfiles)) {
612 connectorStatus.chargingProfiles = [];
613 logger.debug(
614 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${
615 commandPayload.connectorId
616 }, dump their stack: %j`,
617 connectorStatus.chargingProfiles
618 );
619 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
620 }
621 if (!commandPayload.connectorId) {
622 let clearedCP = false;
623 for (const connectorId of chargingStation.connectors.keys()) {
624 if (!Utils.isEmptyArray(chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
625 chargingStation
626 .getConnectorStatus(connectorId)
627 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
628 let clearCurrentCP = false;
629 if (chargingProfile.chargingProfileId === commandPayload.id) {
630 clearCurrentCP = true;
631 }
632 if (
633 !commandPayload.chargingProfilePurpose &&
634 chargingProfile.stackLevel === commandPayload.stackLevel
635 ) {
636 clearCurrentCP = true;
637 }
638 if (
639 !chargingProfile.stackLevel &&
640 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
641 ) {
642 clearCurrentCP = true;
643 }
644 if (
645 chargingProfile.stackLevel === commandPayload.stackLevel &&
646 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
647 ) {
648 clearCurrentCP = true;
649 }
650 if (clearCurrentCP) {
651 connectorStatus.chargingProfiles.splice(index, 1);
652 logger.debug(
653 `${chargingStation.logPrefix()} Matching charging profile(s) cleared on connector id ${
654 commandPayload.connectorId
655 }, dump their stack: %j`,
656 connectorStatus.chargingProfiles
657 );
658 clearedCP = true;
659 }
660 });
661 }
662 }
663 if (clearedCP) {
664 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
665 }
666 }
667 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
668 }
669
670 private async handleRequestChangeAvailability(
671 chargingStation: ChargingStation,
672 commandPayload: ChangeAvailabilityRequest
673 ): Promise<ChangeAvailabilityResponse> {
674 const connectorId: number = commandPayload.connectorId;
675 if (!chargingStation.getConnectorStatus(connectorId)) {
676 logger.error(
677 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
678 );
679 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
680 }
681 const chargePointStatus: OCPP16ChargePointStatus =
682 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
683 ? OCPP16ChargePointStatus.AVAILABLE
684 : OCPP16ChargePointStatus.UNAVAILABLE;
685 if (connectorId === 0) {
686 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
687 for (const id of chargingStation.connectors.keys()) {
688 if (chargingStation.getConnectorStatus(id)?.transactionStarted === true) {
689 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
690 }
691 chargingStation.getConnectorStatus(id).availability = commandPayload.type;
692 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
693 await chargingStation.ocppRequestService.requestHandler<
694 OCPP16StatusNotificationRequest,
695 OCPP16StatusNotificationResponse
696 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
697 connectorId: id,
698 status: chargePointStatus,
699 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
700 });
701 chargingStation.getConnectorStatus(id).status = chargePointStatus;
702 }
703 }
704 return response;
705 } else if (
706 connectorId > 0 &&
707 (chargingStation.isChargingStationAvailable() === true ||
708 (chargingStation.isChargingStationAvailable() === false &&
709 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
710 ) {
711 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
712 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
713 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
714 }
715 chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
716 await chargingStation.ocppRequestService.requestHandler<
717 OCPP16StatusNotificationRequest,
718 OCPP16StatusNotificationResponse
719 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
720 connectorId,
721 status: chargePointStatus,
722 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
723 });
724 chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
725 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
726 }
727 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
728 }
729
730 private async handleRequestRemoteStartTransaction(
731 chargingStation: ChargingStation,
732 commandPayload: RemoteStartTransactionRequest
733 ): Promise<DefaultResponse> {
734 const transactionConnectorId = commandPayload.connectorId;
735 if (chargingStation.connectors.has(transactionConnectorId) === true) {
736 const remoteStartTransactionLogMsg =
737 chargingStation.logPrefix() +
738 ' Transaction remotely STARTED on ' +
739 chargingStation.stationInfo.chargingStationId +
740 '#' +
741 transactionConnectorId.toString() +
742 " for idTag '" +
743 commandPayload.idTag +
744 "'";
745 await chargingStation.ocppRequestService.requestHandler<
746 OCPP16StatusNotificationRequest,
747 OCPP16StatusNotificationResponse
748 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
749 connectorId: transactionConnectorId,
750 status: OCPP16ChargePointStatus.PREPARING,
751 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
752 });
753 const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
754 connectorStatus.status = OCPP16ChargePointStatus.PREPARING;
755 if (chargingStation.isChargingStationAvailable() === true) {
756 // Check if authorized
757 if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
758 let authorized = false;
759 if (
760 chargingStation.getLocalAuthListEnabled() === true &&
761 chargingStation.hasAuthorizedTags() === true &&
762 chargingStation.authorizedTagsCache
763 .getAuthorizedTags(
764 ChargingStationUtils.getAuthorizationFile(chargingStation.stationInfo)
765 )
766 .find((idTag) => idTag === commandPayload.idTag)
767 ) {
768 connectorStatus.localAuthorizeIdTag = commandPayload.idTag;
769 connectorStatus.idTagLocalAuthorized = true;
770 authorized = true;
771 } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
772 connectorStatus.authorizeIdTag = commandPayload.idTag;
773 const authorizeResponse: OCPP16AuthorizeResponse =
774 await chargingStation.ocppRequestService.requestHandler<
775 OCPP16AuthorizeRequest,
776 OCPP16AuthorizeResponse
777 >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
778 idTag: commandPayload.idTag,
779 });
780 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
781 authorized = true;
782 }
783 } else {
784 logger.warn(
785 `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
786 );
787 }
788 if (authorized === true) {
789 // Authorization successful, start transaction
790 if (
791 this.setRemoteStartTransactionChargingProfile(
792 chargingStation,
793 transactionConnectorId,
794 commandPayload.chargingProfile
795 ) === true
796 ) {
797 connectorStatus.transactionRemoteStarted = true;
798 if (
799 (
800 await chargingStation.ocppRequestService.requestHandler<
801 OCPP16StartTransactionRequest,
802 OCPP16StartTransactionResponse
803 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
804 connectorId: transactionConnectorId,
805 idTag: commandPayload.idTag,
806 })
807 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
808 ) {
809 logger.debug(remoteStartTransactionLogMsg);
810 return Constants.OCPP_RESPONSE_ACCEPTED;
811 }
812 return this.notifyRemoteStartTransactionRejected(
813 chargingStation,
814 transactionConnectorId,
815 commandPayload.idTag
816 );
817 }
818 return this.notifyRemoteStartTransactionRejected(
819 chargingStation,
820 transactionConnectorId,
821 commandPayload.idTag
822 );
823 }
824 return this.notifyRemoteStartTransactionRejected(
825 chargingStation,
826 transactionConnectorId,
827 commandPayload.idTag
828 );
829 }
830 // No authorization check required, start transaction
831 if (
832 this.setRemoteStartTransactionChargingProfile(
833 chargingStation,
834 transactionConnectorId,
835 commandPayload.chargingProfile
836 ) === true
837 ) {
838 connectorStatus.transactionRemoteStarted = true;
839 if (
840 (
841 await chargingStation.ocppRequestService.requestHandler<
842 OCPP16StartTransactionRequest,
843 OCPP16StartTransactionResponse
844 >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
845 connectorId: transactionConnectorId,
846 idTag: commandPayload.idTag,
847 })
848 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
849 ) {
850 logger.debug(remoteStartTransactionLogMsg);
851 return Constants.OCPP_RESPONSE_ACCEPTED;
852 }
853 return this.notifyRemoteStartTransactionRejected(
854 chargingStation,
855 transactionConnectorId,
856 commandPayload.idTag
857 );
858 }
859 return this.notifyRemoteStartTransactionRejected(
860 chargingStation,
861 transactionConnectorId,
862 commandPayload.idTag
863 );
864 }
865 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
878 private async notifyRemoteStartTransactionRejected(
879 chargingStation: ChargingStation,
880 connectorId: number,
881 idTag: string
882 ): Promise<DefaultResponse> {
883 if (
884 chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE
885 ) {
886 await chargingStation.ocppRequestService.requestHandler<
887 OCPP16StatusNotificationRequest,
888 OCPP16StatusNotificationResponse
889 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
890 connectorId,
891 status: OCPP16ChargePointStatus.AVAILABLE,
892 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
893 });
894 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
895 }
896 logger.warn(
897 chargingStation.logPrefix() +
898 ' Remote starting transaction REJECTED on connector Id ' +
899 connectorId.toString() +
900 ", idTag '" +
901 idTag +
902 "', availability '" +
903 chargingStation.getConnectorStatus(connectorId).availability +
904 "', status '" +
905 chargingStation.getConnectorStatus(connectorId).status +
906 "'"
907 );
908 return Constants.OCPP_RESPONSE_REJECTED;
909 }
910
911 private setRemoteStartTransactionChargingProfile(
912 chargingStation: ChargingStation,
913 connectorId: number,
914 cp: OCPP16ChargingProfile
915 ): boolean {
916 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
917 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, cp);
918 logger.debug(
919 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on connector id ${connectorId}, dump their stack: %j`,
920 chargingStation.getConnectorStatus(connectorId).chargingProfiles
921 );
922 return true;
923 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
924 logger.warn(
925 `${chargingStation.logPrefix()} Not allowed to set ${
926 cp.chargingProfilePurpose
927 } charging profile(s) at remote start transaction`
928 );
929 return false;
930 } else if (!cp) {
931 return true;
932 }
933 }
934
935 private async handleRequestRemoteStopTransaction(
936 chargingStation: ChargingStation,
937 commandPayload: RemoteStopTransactionRequest
938 ): Promise<DefaultResponse> {
939 const transactionId = commandPayload.transactionId;
940 for (const connectorId of chargingStation.connectors.keys()) {
941 if (
942 connectorId > 0 &&
943 chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
944 ) {
945 await chargingStation.ocppRequestService.requestHandler<
946 OCPP16StatusNotificationRequest,
947 OCPP16StatusNotificationResponse
948 >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, {
949 connectorId,
950 status: OCPP16ChargePointStatus.FINISHING,
951 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
952 });
953 chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
954 const stopResponse = await chargingStation.stopTransactionOnConnector(
955 connectorId,
956 OCPP16StopTransactionReason.REMOTE
957 );
958 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
959 return Constants.OCPP_RESPONSE_ACCEPTED;
960 }
961 return Constants.OCPP_RESPONSE_REJECTED;
962 }
963 }
964 logger.warn(
965 chargingStation.logPrefix() +
966 ' Trying to remote stop a non existing transaction ' +
967 transactionId.toString()
968 );
969 return Constants.OCPP_RESPONSE_REJECTED;
970 }
971
972 private async handleRequestGetDiagnostics(
973 chargingStation: ChargingStation,
974 commandPayload: GetDiagnosticsRequest
975 ): Promise<GetDiagnosticsResponse> {
976 if (
977 OCPP16ServiceUtils.checkFeatureProfile(
978 chargingStation,
979 OCPP16SupportedFeatureProfiles.FirmwareManagement,
980 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
981 ) === false
982 ) {
983 return Constants.OCPP_RESPONSE_EMPTY;
984 }
985 logger.debug(
986 chargingStation.logPrefix() +
987 ' ' +
988 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
989 ' request received: %j',
990 commandPayload
991 );
992 const uri = new URL(commandPayload.location);
993 if (uri.protocol.startsWith('ftp:')) {
994 let ftpClient: Client;
995 try {
996 const logFiles = fs
997 .readdirSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'))
998 .filter((file) => file.endsWith('.log'))
999 .map((file) => path.join('./', file));
1000 const diagnosticsArchive = chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
1001 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
1002 ftpClient = new Client();
1003 const accessResponse = await ftpClient.access({
1004 host: uri.host,
1005 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
1006 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
1007 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
1008 });
1009 let uploadResponse: FTPResponse;
1010 if (accessResponse.code === 220) {
1011 // eslint-disable-next-line @typescript-eslint/no-misused-promises
1012 ftpClient.trackProgress(async (info) => {
1013 logger.info(
1014 `${chargingStation.logPrefix()} ${
1015 info.bytes / 1024
1016 } bytes transferred from diagnostics archive ${info.name}`
1017 );
1018 await chargingStation.ocppRequestService.requestHandler<
1019 DiagnosticsStatusNotificationRequest,
1020 DiagnosticsStatusNotificationResponse
1021 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1022 status: OCPP16DiagnosticsStatus.Uploading,
1023 });
1024 });
1025 uploadResponse = await ftpClient.uploadFrom(
1026 path.join(
1027 path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../'),
1028 diagnosticsArchive
1029 ),
1030 uri.pathname + diagnosticsArchive
1031 );
1032 if (uploadResponse.code === 226) {
1033 await chargingStation.ocppRequestService.requestHandler<
1034 DiagnosticsStatusNotificationRequest,
1035 DiagnosticsStatusNotificationResponse
1036 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1037 status: OCPP16DiagnosticsStatus.Uploaded,
1038 });
1039 if (ftpClient) {
1040 ftpClient.close();
1041 }
1042 return { fileName: diagnosticsArchive };
1043 }
1044 throw new OCPPError(
1045 ErrorType.GENERIC_ERROR,
1046 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1047 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1048 }`,
1049 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1050 );
1051 }
1052 throw new OCPPError(
1053 ErrorType.GENERIC_ERROR,
1054 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
1055 uploadResponse?.code && '|' + uploadResponse?.code.toString()
1056 }`,
1057 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1058 );
1059 } catch (error) {
1060 await chargingStation.ocppRequestService.requestHandler<
1061 DiagnosticsStatusNotificationRequest,
1062 DiagnosticsStatusNotificationResponse
1063 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1064 status: OCPP16DiagnosticsStatus.UploadFailed,
1065 });
1066 if (ftpClient) {
1067 ftpClient.close();
1068 }
1069 return this.handleIncomingRequestError(
1070 chargingStation,
1071 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1072 error as Error,
1073 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
1074 );
1075 }
1076 } else {
1077 logger.error(
1078 `${chargingStation.logPrefix()} Unsupported protocol ${
1079 uri.protocol
1080 } to transfer the diagnostic logs archive`
1081 );
1082 await chargingStation.ocppRequestService.requestHandler<
1083 DiagnosticsStatusNotificationRequest,
1084 DiagnosticsStatusNotificationResponse
1085 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1086 status: OCPP16DiagnosticsStatus.UploadFailed,
1087 });
1088 return Constants.OCPP_RESPONSE_EMPTY;
1089 }
1090 }
1091
1092 private handleRequestTriggerMessage(
1093 chargingStation: ChargingStation,
1094 commandPayload: OCPP16TriggerMessageRequest
1095 ): OCPP16TriggerMessageResponse {
1096 if (
1097 !OCPP16ServiceUtils.checkFeatureProfile(
1098 chargingStation,
1099 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1100 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1101 )
1102 ) {
1103 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1104 }
1105 // TODO: factor out the check on connector id
1106 if (commandPayload?.connectorId < 0) {
1107 logger.warn(
1108 `${chargingStation.logPrefix()} ${
1109 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1110 } incoming request received with invalid connectorId ${commandPayload.connectorId}`
1111 );
1112 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED;
1113 }
1114 try {
1115 switch (commandPayload.requestedMessage) {
1116 case MessageTrigger.BootNotification:
1117 setTimeout(() => {
1118 chargingStation.ocppRequestService
1119 .requestHandler<OCPP16BootNotificationRequest, OCPP16BootNotificationResponse>(
1120 chargingStation,
1121 OCPP16RequestCommand.BOOT_NOTIFICATION,
1122 chargingStation.bootNotificationRequest,
1123 { skipBufferingOnError: true, triggerMessage: true }
1124 )
1125 .then((response) => {
1126 chargingStation.bootNotificationResponse = response;
1127 })
1128 .catch(() => {
1129 /* This is intentional */
1130 });
1131 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1132 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1133 case MessageTrigger.Heartbeat:
1134 setTimeout(() => {
1135 chargingStation.ocppRequestService
1136 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
1137 chargingStation,
1138 OCPP16RequestCommand.HEARTBEAT,
1139 null,
1140 {
1141 triggerMessage: true,
1142 }
1143 )
1144 .catch(() => {
1145 /* This is intentional */
1146 });
1147 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1148 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1149 case MessageTrigger.StatusNotification:
1150 setTimeout(() => {
1151 if (commandPayload?.connectorId) {
1152 chargingStation.ocppRequestService
1153 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
1154 chargingStation,
1155 OCPP16RequestCommand.STATUS_NOTIFICATION,
1156 {
1157 connectorId: commandPayload.connectorId,
1158 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1159 status: chargingStation.getConnectorStatus(commandPayload.connectorId).status,
1160 },
1161 {
1162 triggerMessage: true,
1163 }
1164 )
1165 .catch(() => {
1166 /* This is intentional */
1167 });
1168 } else {
1169 for (const connectorId of chargingStation.connectors.keys()) {
1170 chargingStation.ocppRequestService
1171 .requestHandler<
1172 OCPP16StatusNotificationRequest,
1173 OCPP16StatusNotificationResponse
1174 >(
1175 chargingStation,
1176 OCPP16RequestCommand.STATUS_NOTIFICATION,
1177 {
1178 connectorId,
1179 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
1180 status: chargingStation.getConnectorStatus(connectorId).status,
1181 },
1182 {
1183 triggerMessage: true,
1184 }
1185 )
1186 .catch(() => {
1187 /* This is intentional */
1188 });
1189 }
1190 }
1191 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
1192 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
1193 default:
1194 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
1195 }
1196 } catch (error) {
1197 return this.handleIncomingRequestError(
1198 chargingStation,
1199 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1200 error as Error,
1201 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
1202 );
1203 }
1204 }
1205 }