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