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