Convert sendBootNotification to OCPP message sending handler
[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 {
4 ChangeAvailabilityRequest,
5 ChangeConfigurationRequest,
6 ClearChargingProfileRequest,
7 GetConfigurationRequest,
8 GetDiagnosticsRequest,
9 MessageTrigger,
10 OCPP16AvailabilityType,
11 OCPP16IncomingRequestCommand,
12 OCPP16RequestCommand,
13 OCPP16TriggerMessageRequest,
14 RemoteStartTransactionRequest,
15 RemoteStopTransactionRequest,
16 ResetRequest,
17 SetChargingProfileRequest,
18 UnlockConnectorRequest,
19 } from '../../../types/ocpp/1.6/Requests';
20 import {
21 ChangeAvailabilityResponse,
22 ChangeConfigurationResponse,
23 ClearChargingProfileResponse,
24 GetConfigurationResponse,
25 GetDiagnosticsResponse,
26 OCPP16TriggerMessageResponse,
27 SetChargingProfileResponse,
28 UnlockConnectorResponse,
29 } from '../../../types/ocpp/1.6/Responses';
30 import {
31 ChargingProfilePurposeType,
32 OCPP16ChargingProfile,
33 } from '../../../types/ocpp/1.6/ChargingProfile';
34 import { Client, FTPResponse } from 'basic-ftp';
35 import {
36 OCPP16AuthorizationStatus,
37 OCPP16StopTransactionReason,
38 } from '../../../types/ocpp/1.6/Transaction';
39
40 import type ChargingStation from '../../ChargingStation';
41 import Constants from '../../../utils/Constants';
42 import { DefaultResponse } from '../../../types/ocpp/Responses';
43 import { ErrorType } from '../../../types/ocpp/ErrorType';
44 import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
45 import { JsonType } from '../../../types/JsonType';
46 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
47 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
48 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
49 import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
50 import OCPPError from '../../../exception/OCPPError';
51 import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
52 import { URL } from 'url';
53 import Utils from '../../../utils/Utils';
54 import fs from 'fs';
55 import logger from '../../../utils/Logger';
56 import path from 'path';
57 import tar from 'tar';
58
59 const moduleName = 'OCPP16IncomingRequestService';
60
61 export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
62 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
63
64 public constructor(chargingStation: ChargingStation) {
65 if (new.target?.name === moduleName) {
66 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
67 }
68 super(chargingStation);
69 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
70 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
71 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
72 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
73 [
74 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
75 this.handleRequestGetConfiguration.bind(this),
76 ],
77 [
78 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
79 this.handleRequestChangeConfiguration.bind(this),
80 ],
81 [
82 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
83 this.handleRequestSetChargingProfile.bind(this),
84 ],
85 [
86 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
87 this.handleRequestClearChargingProfile.bind(this),
88 ],
89 [
90 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
91 this.handleRequestChangeAvailability.bind(this),
92 ],
93 [
94 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
95 this.handleRequestRemoteStartTransaction.bind(this),
96 ],
97 [
98 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
99 this.handleRequestRemoteStopTransaction.bind(this),
100 ],
101 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
102 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
103 ]);
104 }
105
106 public async handleRequest(
107 messageId: string,
108 commandName: OCPP16IncomingRequestCommand,
109 commandPayload: JsonType
110 ): Promise<void> {
111 let result: JsonType;
112 if (
113 this.chargingStation.getOcppStrictCompliance() &&
114 this.chargingStation.isInPendingState() &&
115 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
116 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
117 ) {
118 throw new OCPPError(
119 ErrorType.SECURITY_ERROR,
120 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
121 commandPayload,
122 null,
123 2
124 )} while the charging station is in pending state on the central server`,
125 commandName
126 );
127 }
128 if (
129 this.chargingStation.isRegistered() ||
130 (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())
131 ) {
132 if (this.incomingRequestHandlers.has(commandName)) {
133 try {
134 // Call the method to build the result
135 result = await this.incomingRequestHandlers.get(commandName)(commandPayload);
136 } catch (error) {
137 // Log
138 logger.error(this.chargingStation.logPrefix() + ' Handle request error: %j', error);
139 throw error;
140 }
141 } else {
142 // Throw exception
143 throw new OCPPError(
144 ErrorType.NOT_IMPLEMENTED,
145 `${commandName} is not implemented to handle request payload ${JSON.stringify(
146 commandPayload,
147 null,
148 2
149 )}`,
150 commandName
151 );
152 }
153 } else {
154 throw new OCPPError(
155 ErrorType.SECURITY_ERROR,
156 `${commandName} cannot be issued to handle request payload ${JSON.stringify(
157 commandPayload,
158 null,
159 2
160 )} while the charging station is not registered on the central server.`,
161 commandName
162 );
163 }
164 // Send the built result
165 await this.chargingStation.ocppRequestService.sendResult(messageId, result, commandName);
166 }
167
168 // Simulate charging station restart
169 private handleRequestReset(commandPayload: ResetRequest): DefaultResponse {
170 // eslint-disable-next-line @typescript-eslint/no-misused-promises
171 setImmediate(async (): Promise<void> => {
172 await this.chargingStation.stop(
173 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
174 );
175 await Utils.sleep(this.chargingStation.stationInfo.resetTime);
176 this.chargingStation.start();
177 });
178 logger.info(
179 `${this.chargingStation.logPrefix()} ${
180 commandPayload.type
181 } reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(
182 this.chargingStation.stationInfo.resetTime
183 )}`
184 );
185 return Constants.OCPP_RESPONSE_ACCEPTED;
186 }
187
188 private handleRequestClearCache(): DefaultResponse {
189 return Constants.OCPP_RESPONSE_ACCEPTED;
190 }
191
192 private async handleRequestUnlockConnector(
193 commandPayload: UnlockConnectorRequest
194 ): Promise<UnlockConnectorResponse> {
195 const connectorId = commandPayload.connectorId;
196 if (connectorId === 0) {
197 logger.error(
198 this.chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
199 );
200 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
201 }
202 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
203 const transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
204 const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(
205 transactionId,
206 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
207 this.chargingStation.getTransactionIdTag(transactionId),
208 OCPP16StopTransactionReason.UNLOCK_COMMAND
209 );
210 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
211 return Constants.OCPP_RESPONSE_UNLOCKED;
212 }
213 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
214 }
215 await this.chargingStation.ocppRequestService.sendStatusNotification(
216 connectorId,
217 OCPP16ChargePointStatus.AVAILABLE
218 );
219 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
220 return Constants.OCPP_RESPONSE_UNLOCKED;
221 }
222
223 private handleRequestGetConfiguration(
224 commandPayload: GetConfigurationRequest
225 ): GetConfigurationResponse {
226 const configurationKey: OCPPConfigurationKey[] = [];
227 const unknownKey: string[] = [];
228 if (Utils.isEmptyArray(commandPayload.key)) {
229 for (const configuration of this.chargingStation.configuration.configurationKey) {
230 if (Utils.isUndefined(configuration.visible)) {
231 configuration.visible = true;
232 }
233 if (!configuration.visible) {
234 continue;
235 }
236 configurationKey.push({
237 key: configuration.key,
238 readonly: configuration.readonly,
239 value: configuration.value,
240 });
241 }
242 } else {
243 for (const key of commandPayload.key) {
244 const keyFound = this.chargingStation.getConfigurationKey(key);
245 if (keyFound) {
246 if (Utils.isUndefined(keyFound.visible)) {
247 keyFound.visible = true;
248 }
249 if (!keyFound.visible) {
250 continue;
251 }
252 configurationKey.push({
253 key: keyFound.key,
254 readonly: keyFound.readonly,
255 value: keyFound.value,
256 });
257 } else {
258 unknownKey.push(key);
259 }
260 }
261 }
262 return {
263 configurationKey,
264 unknownKey,
265 };
266 }
267
268 private handleRequestChangeConfiguration(
269 commandPayload: ChangeConfigurationRequest
270 ): ChangeConfigurationResponse {
271 // JSON request fields type sanity check
272 if (!Utils.isString(commandPayload.key)) {
273 logger.error(
274 `${this.chargingStation.logPrefix()} ${
275 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
276 } request key field is not a string:`,
277 commandPayload
278 );
279 }
280 if (!Utils.isString(commandPayload.value)) {
281 logger.error(
282 `${this.chargingStation.logPrefix()} ${
283 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
284 } request value field is not a string:`,
285 commandPayload
286 );
287 }
288 const keyToChange = this.chargingStation.getConfigurationKey(commandPayload.key, true);
289 if (!keyToChange) {
290 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
291 } else if (keyToChange && keyToChange.readonly) {
292 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
293 } else if (keyToChange && !keyToChange.readonly) {
294 const keyIndex = this.chargingStation.configuration.configurationKey.indexOf(keyToChange);
295 let valueChanged = false;
296 if (
297 this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value
298 ) {
299 this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
300 valueChanged = true;
301 }
302 let triggerHeartbeatRestart = false;
303 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
304 this.chargingStation.setConfigurationKeyValue(
305 OCPP16StandardParametersKey.HeartbeatInterval,
306 commandPayload.value
307 );
308 triggerHeartbeatRestart = true;
309 }
310 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
311 this.chargingStation.setConfigurationKeyValue(
312 OCPP16StandardParametersKey.HeartBeatInterval,
313 commandPayload.value
314 );
315 triggerHeartbeatRestart = true;
316 }
317 if (triggerHeartbeatRestart) {
318 this.chargingStation.restartHeartbeat();
319 }
320 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
321 this.chargingStation.restartWebSocketPing();
322 }
323 if (keyToChange.reboot) {
324 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
325 }
326 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
327 }
328 }
329
330 private handleRequestSetChargingProfile(
331 commandPayload: SetChargingProfileRequest
332 ): SetChargingProfileResponse {
333 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
334 logger.error(
335 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
336 commandPayload.connectorId
337 }`
338 );
339 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
340 }
341 if (
342 commandPayload.csChargingProfiles.chargingProfilePurpose ===
343 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
344 commandPayload.connectorId !== 0
345 ) {
346 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
347 }
348 if (
349 commandPayload.csChargingProfiles.chargingProfilePurpose ===
350 ChargingProfilePurposeType.TX_PROFILE &&
351 (commandPayload.connectorId === 0 ||
352 !this.chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)
353 ) {
354 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
355 }
356 this.chargingStation.setChargingProfile(
357 commandPayload.connectorId,
358 commandPayload.csChargingProfiles
359 );
360 logger.debug(
361 `${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`,
362 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
363 );
364 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
365 }
366
367 private handleRequestClearChargingProfile(
368 commandPayload: ClearChargingProfileRequest
369 ): ClearChargingProfileResponse {
370 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
371 logger.error(
372 `${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${
373 commandPayload.connectorId
374 }`
375 );
376 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
377 }
378 if (
379 commandPayload.connectorId &&
380 !Utils.isEmptyArray(
381 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
382 )
383 ) {
384 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
385 logger.debug(
386 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
387 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
388 );
389 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
390 }
391 if (!commandPayload.connectorId) {
392 let clearedCP = false;
393 for (const connectorId of this.chargingStation.connectors.keys()) {
394 if (
395 !Utils.isEmptyArray(this.chargingStation.getConnectorStatus(connectorId).chargingProfiles)
396 ) {
397 this.chargingStation
398 .getConnectorStatus(connectorId)
399 .chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
400 let clearCurrentCP = false;
401 if (chargingProfile.chargingProfileId === commandPayload.id) {
402 clearCurrentCP = true;
403 }
404 if (
405 !commandPayload.chargingProfilePurpose &&
406 chargingProfile.stackLevel === commandPayload.stackLevel
407 ) {
408 clearCurrentCP = true;
409 }
410 if (
411 !chargingProfile.stackLevel &&
412 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
413 ) {
414 clearCurrentCP = true;
415 }
416 if (
417 chargingProfile.stackLevel === commandPayload.stackLevel &&
418 chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose
419 ) {
420 clearCurrentCP = true;
421 }
422 if (clearCurrentCP) {
423 this.chargingStation.getConnectorStatus(
424 commandPayload.connectorId
425 ).chargingProfiles[index] = {} as OCPP16ChargingProfile;
426 logger.debug(
427 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
428 this.chargingStation.getConnectorStatus(commandPayload.connectorId)
429 .chargingProfiles
430 );
431 clearedCP = true;
432 }
433 });
434 }
435 }
436 if (clearedCP) {
437 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
438 }
439 }
440 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
441 }
442
443 private async handleRequestChangeAvailability(
444 commandPayload: ChangeAvailabilityRequest
445 ): Promise<ChangeAvailabilityResponse> {
446 const connectorId: number = commandPayload.connectorId;
447 if (!this.chargingStation.getConnectorStatus(connectorId)) {
448 logger.error(
449 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
450 );
451 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
452 }
453 const chargePointStatus: OCPP16ChargePointStatus =
454 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
455 ? OCPP16ChargePointStatus.AVAILABLE
456 : OCPP16ChargePointStatus.UNAVAILABLE;
457 if (connectorId === 0) {
458 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
459 for (const id of this.chargingStation.connectors.keys()) {
460 if (this.chargingStation.getConnectorStatus(id)?.transactionStarted) {
461 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
462 }
463 this.chargingStation.getConnectorStatus(id).availability = commandPayload.type;
464 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
465 await this.chargingStation.ocppRequestService.sendStatusNotification(
466 id,
467 chargePointStatus
468 );
469 this.chargingStation.getConnectorStatus(id).status = chargePointStatus;
470 }
471 }
472 return response;
473 } else if (
474 connectorId > 0 &&
475 (this.chargingStation.getConnectorStatus(0).availability ===
476 OCPP16AvailabilityType.OPERATIVE ||
477 (this.chargingStation.getConnectorStatus(0).availability ===
478 OCPP16AvailabilityType.INOPERATIVE &&
479 commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))
480 ) {
481 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
482 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
483 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
484 }
485 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
486 await this.chargingStation.ocppRequestService.sendStatusNotification(
487 connectorId,
488 chargePointStatus
489 );
490 this.chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
491 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
492 }
493 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
494 }
495
496 private async handleRequestRemoteStartTransaction(
497 commandPayload: RemoteStartTransactionRequest
498 ): Promise<DefaultResponse> {
499 const transactionConnectorId: number = commandPayload.connectorId;
500 if (transactionConnectorId) {
501 await this.chargingStation.ocppRequestService.sendStatusNotification(
502 transactionConnectorId,
503 OCPP16ChargePointStatus.PREPARING
504 );
505 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
506 OCPP16ChargePointStatus.PREPARING;
507 if (
508 this.chargingStation.isChargingStationAvailable() &&
509 this.chargingStation.isConnectorAvailable(transactionConnectorId)
510 ) {
511 // Check if authorized
512 if (this.chargingStation.getAuthorizeRemoteTxRequests()) {
513 let authorized = false;
514 if (
515 this.chargingStation.getLocalAuthListEnabled() &&
516 this.chargingStation.hasAuthorizedTags() &&
517 this.chargingStation.authorizedTags.find((value) => value === commandPayload.idTag)
518 ) {
519 this.chargingStation.getConnectorStatus(transactionConnectorId).localAuthorizeIdTag =
520 commandPayload.idTag;
521 this.chargingStation.getConnectorStatus(transactionConnectorId).idTagLocalAuthorized =
522 true;
523 authorized = true;
524 } else if (this.chargingStation.getMayAuthorizeAtRemoteStart()) {
525 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(
526 transactionConnectorId,
527 commandPayload.idTag
528 );
529 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
530 authorized = true;
531 }
532 } else {
533 logger.warn(
534 `${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
535 );
536 }
537 if (authorized) {
538 // Authorization successful, start transaction
539 if (
540 this.setRemoteStartTransactionChargingProfile(
541 transactionConnectorId,
542 commandPayload.chargingProfile
543 )
544 ) {
545 this.chargingStation.getConnectorStatus(
546 transactionConnectorId
547 ).transactionRemoteStarted = true;
548 if (
549 (
550 await this.chargingStation.ocppRequestService.sendStartTransaction(
551 transactionConnectorId,
552 commandPayload.idTag
553 )
554 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
555 ) {
556 logger.debug(
557 this.chargingStation.logPrefix() +
558 ' Transaction remotely STARTED on ' +
559 this.chargingStation.stationInfo.chargingStationId +
560 '#' +
561 transactionConnectorId.toString() +
562 ' for idTag ' +
563 commandPayload.idTag
564 );
565 return Constants.OCPP_RESPONSE_ACCEPTED;
566 }
567 return this.notifyRemoteStartTransactionRejected(
568 transactionConnectorId,
569 commandPayload.idTag
570 );
571 }
572 return this.notifyRemoteStartTransactionRejected(
573 transactionConnectorId,
574 commandPayload.idTag
575 );
576 }
577 return this.notifyRemoteStartTransactionRejected(
578 transactionConnectorId,
579 commandPayload.idTag
580 );
581 }
582 // No authorization check required, start transaction
583 if (
584 this.setRemoteStartTransactionChargingProfile(
585 transactionConnectorId,
586 commandPayload.chargingProfile
587 )
588 ) {
589 this.chargingStation.getConnectorStatus(transactionConnectorId).transactionRemoteStarted =
590 true;
591 if (
592 (
593 await this.chargingStation.ocppRequestService.sendStartTransaction(
594 transactionConnectorId,
595 commandPayload.idTag
596 )
597 ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
598 ) {
599 logger.debug(
600 this.chargingStation.logPrefix() +
601 ' Transaction remotely STARTED on ' +
602 this.chargingStation.stationInfo.chargingStationId +
603 '#' +
604 transactionConnectorId.toString() +
605 ' for idTag ' +
606 commandPayload.idTag
607 );
608 return Constants.OCPP_RESPONSE_ACCEPTED;
609 }
610 return this.notifyRemoteStartTransactionRejected(
611 transactionConnectorId,
612 commandPayload.idTag
613 );
614 }
615 return this.notifyRemoteStartTransactionRejected(
616 transactionConnectorId,
617 commandPayload.idTag
618 );
619 }
620 return this.notifyRemoteStartTransactionRejected(
621 transactionConnectorId,
622 commandPayload.idTag
623 );
624 }
625 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
626 }
627
628 private async notifyRemoteStartTransactionRejected(
629 connectorId: number,
630 idTag: string
631 ): Promise<DefaultResponse> {
632 if (
633 this.chargingStation.getConnectorStatus(connectorId).status !==
634 OCPP16ChargePointStatus.AVAILABLE
635 ) {
636 await this.chargingStation.ocppRequestService.sendStatusNotification(
637 connectorId,
638 OCPP16ChargePointStatus.AVAILABLE
639 );
640 this.chargingStation.getConnectorStatus(connectorId).status =
641 OCPP16ChargePointStatus.AVAILABLE;
642 }
643 logger.warn(
644 this.chargingStation.logPrefix() +
645 ' Remote starting transaction REJECTED on connector Id ' +
646 connectorId.toString() +
647 ', idTag ' +
648 idTag +
649 ', availability ' +
650 this.chargingStation.getConnectorStatus(connectorId).availability +
651 ', status ' +
652 this.chargingStation.getConnectorStatus(connectorId).status
653 );
654 return Constants.OCPP_RESPONSE_REJECTED;
655 }
656
657 private setRemoteStartTransactionChargingProfile(
658 connectorId: number,
659 cp: OCPP16ChargingProfile
660 ): boolean {
661 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
662 this.chargingStation.setChargingProfile(connectorId, cp);
663 logger.debug(
664 `${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`,
665 this.chargingStation.getConnectorStatus(connectorId).chargingProfiles
666 );
667 return true;
668 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
669 logger.warn(
670 `${this.chargingStation.logPrefix()} Not allowed to set ${
671 cp.chargingProfilePurpose
672 } charging profile(s) at remote start transaction`
673 );
674 return false;
675 } else if (!cp) {
676 return true;
677 }
678 }
679
680 private async handleRequestRemoteStopTransaction(
681 commandPayload: RemoteStopTransactionRequest
682 ): Promise<DefaultResponse> {
683 const transactionId = commandPayload.transactionId;
684 for (const connectorId of this.chargingStation.connectors.keys()) {
685 if (
686 connectorId > 0 &&
687 this.chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
688 ) {
689 await this.chargingStation.ocppRequestService.sendStatusNotification(
690 connectorId,
691 OCPP16ChargePointStatus.FINISHING
692 );
693 this.chargingStation.getConnectorStatus(connectorId).status =
694 OCPP16ChargePointStatus.FINISHING;
695 await this.chargingStation.ocppRequestService.sendStopTransaction(
696 transactionId,
697 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
698 this.chargingStation.getTransactionIdTag(transactionId)
699 );
700 return Constants.OCPP_RESPONSE_ACCEPTED;
701 }
702 }
703 logger.info(
704 this.chargingStation.logPrefix() +
705 ' Trying to remote stop a non existing transaction ' +
706 transactionId.toString()
707 );
708 return Constants.OCPP_RESPONSE_REJECTED;
709 }
710
711 private async handleRequestGetDiagnostics(
712 commandPayload: GetDiagnosticsRequest
713 ): Promise<GetDiagnosticsResponse> {
714 logger.debug(
715 this.chargingStation.logPrefix() +
716 ' ' +
717 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS +
718 ' request received: %j',
719 commandPayload
720 );
721 const uri = new URL(commandPayload.location);
722 if (uri.protocol.startsWith('ftp:')) {
723 let ftpClient: Client;
724 try {
725 const logFiles = fs
726 .readdirSync(path.resolve(__dirname, '../../../../'))
727 .filter((file) => file.endsWith('.log'))
728 .map((file) => path.join('./', file));
729 const diagnosticsArchive =
730 this.chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
731 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
732 ftpClient = new Client();
733 const accessResponse = await ftpClient.access({
734 host: uri.host,
735 ...(!Utils.isEmptyString(uri.port) && { port: Utils.convertToInt(uri.port) }),
736 ...(!Utils.isEmptyString(uri.username) && { user: uri.username }),
737 ...(!Utils.isEmptyString(uri.password) && { password: uri.password }),
738 });
739 let uploadResponse: FTPResponse;
740 if (accessResponse.code === 220) {
741 // eslint-disable-next-line @typescript-eslint/no-misused-promises
742 ftpClient.trackProgress(async (info) => {
743 logger.info(
744 `${this.chargingStation.logPrefix()} ${
745 info.bytes / 1024
746 } bytes transferred from diagnostics archive ${info.name}`
747 );
748 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
749 OCPP16DiagnosticsStatus.Uploading
750 );
751 });
752 uploadResponse = await ftpClient.uploadFrom(
753 path.join(path.resolve(__dirname, '../../../../'), diagnosticsArchive),
754 uri.pathname + diagnosticsArchive
755 );
756 if (uploadResponse.code === 226) {
757 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
758 OCPP16DiagnosticsStatus.Uploaded
759 );
760 if (ftpClient) {
761 ftpClient.close();
762 }
763 return { fileName: diagnosticsArchive };
764 }
765 throw new OCPPError(
766 ErrorType.GENERIC_ERROR,
767 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
768 uploadResponse?.code && '|' + uploadResponse?.code.toString()
769 }`,
770 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
771 );
772 }
773 throw new OCPPError(
774 ErrorType.GENERIC_ERROR,
775 `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${
776 uploadResponse?.code && '|' + uploadResponse?.code.toString()
777 }`,
778 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
779 );
780 } catch (error) {
781 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
782 OCPP16DiagnosticsStatus.UploadFailed
783 );
784 if (ftpClient) {
785 ftpClient.close();
786 }
787 return this.handleIncomingRequestError(
788 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
789 error as Error,
790 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
791 );
792 }
793 } else {
794 logger.error(
795 `${this.chargingStation.logPrefix()} Unsupported protocol ${
796 uri.protocol
797 } to transfer the diagnostic logs archive`
798 );
799 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
800 OCPP16DiagnosticsStatus.UploadFailed
801 );
802 return Constants.OCPP_RESPONSE_EMPTY;
803 }
804 }
805
806 private handleRequestTriggerMessage(
807 commandPayload: OCPP16TriggerMessageRequest
808 ): OCPP16TriggerMessageResponse {
809 try {
810 switch (commandPayload.requestedMessage) {
811 case MessageTrigger.BootNotification:
812 setTimeout(() => {
813 this.chargingStation.ocppRequestService
814 .sendMessageHandler(
815 OCPP16RequestCommand.BOOT_NOTIFICATION,
816 {
817 chargePointModel:
818 this.chargingStation.getBootNotificationRequest().chargePointModel,
819 chargePointVendor:
820 this.chargingStation.getBootNotificationRequest().chargePointVendor,
821 chargeBoxSerialNumber:
822 this.chargingStation.getBootNotificationRequest().chargeBoxSerialNumber,
823 firmwareVersion:
824 this.chargingStation.getBootNotificationRequest().firmwareVersion,
825 chargePointSerialNumber:
826 this.chargingStation.getBootNotificationRequest().chargePointSerialNumber,
827 iccid: this.chargingStation.getBootNotificationRequest().iccid,
828 imsi: this.chargingStation.getBootNotificationRequest().imsi,
829 meterSerialNumber:
830 this.chargingStation.getBootNotificationRequest().meterSerialNumber,
831 meterType: this.chargingStation.getBootNotificationRequest().meterType,
832 },
833 { skipBufferingOnError: true, triggerMessage: true }
834 )
835 .catch(() => {
836 /* This is intentional */
837 });
838 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
839 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
840 case MessageTrigger.Heartbeat:
841 setTimeout(() => {
842 this.chargingStation.ocppRequestService
843 .sendMessageHandler(OCPP16RequestCommand.HEARTBEAT, null, { triggerMessage: true })
844 .catch(() => {
845 /* This is intentional */
846 });
847 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
848 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
849 default:
850 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
851 }
852 } catch (error) {
853 return this.handleIncomingRequestError(
854 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
855 error as Error,
856 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
857 );
858 }
859 }
860 }