Convert sendStatusNotification to OCPP message sending handler
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
e7aeea18
JB
3import {
4 ChangeAvailabilityRequest,
5 ChangeConfigurationRequest,
6 ClearChargingProfileRequest,
7 GetConfigurationRequest,
8 GetDiagnosticsRequest,
9 MessageTrigger,
10 OCPP16AvailabilityType,
11 OCPP16IncomingRequestCommand,
94a464f9 12 OCPP16RequestCommand,
e7aeea18
JB
13 OCPP16TriggerMessageRequest,
14 RemoteStartTransactionRequest,
15 RemoteStopTransactionRequest,
16 ResetRequest,
17 SetChargingProfileRequest,
18 UnlockConnectorRequest,
19} from '../../../types/ocpp/1.6/Requests';
20import {
21 ChangeAvailabilityResponse,
22 ChangeConfigurationResponse,
23 ClearChargingProfileResponse,
24 GetConfigurationResponse,
25 GetDiagnosticsResponse,
26 OCPP16TriggerMessageResponse,
27 SetChargingProfileResponse,
28 UnlockConnectorResponse,
29} from '../../../types/ocpp/1.6/Responses';
30import {
31 ChargingProfilePurposeType,
32 OCPP16ChargingProfile,
33} from '../../../types/ocpp/1.6/ChargingProfile';
47e22477 34import { Client, FTPResponse } from 'basic-ftp';
e7aeea18
JB
35import {
36 OCPP16AuthorizationStatus,
37 OCPP16StopTransactionReason,
38} from '../../../types/ocpp/1.6/Transaction';
c0560973 39
73b9adec 40import type ChargingStation from '../../ChargingStation';
c0560973 41import Constants from '../../../utils/Constants';
9ccca265 42import { DefaultResponse } from '../../../types/ocpp/Responses';
c0560973 43import { ErrorType } from '../../../types/ocpp/ErrorType';
58144adb 44import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
d1888640 45import { JsonType } from '../../../types/JsonType';
93b4a429 46import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
c0560973 47import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
47e22477 48import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
c0560973
JB
49import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
50import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
e58068fd 51import OCPPError from '../../../exception/OCPPError';
c0560973 52import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
a3868ec4 53import { URL } from 'url';
c0560973 54import Utils from '../../../utils/Utils';
47e22477 55import fs from 'fs';
9f2e3130 56import logger from '../../../utils/Logger';
47e22477
JB
57import path from 'path';
58import tar from 'tar';
c0560973 59
2a115f87 60const moduleName = 'OCPP16IncomingRequestService';
909dcf2d 61
c0560973 62export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
58144adb
JB
63 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
64
9f2e3130 65 public constructor(chargingStation: ChargingStation) {
909dcf2d 66 if (new.target?.name === moduleName) {
06127450 67 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 68 }
58144adb
JB
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)],
e7aeea18
JB
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 ],
734d790d 102 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
e7aeea18 103 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
58144adb
JB
104 ]);
105 }
106
e7aeea18
JB
107 public async handleRequest(
108 messageId: string,
109 commandName: OCPP16IncomingRequestCommand,
110 commandPayload: JsonType
111 ): Promise<void> {
d1888640 112 let result: JsonType;
e7aeea18
JB
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 );
caad9d6b 128 }
e7aeea18
JB
129 if (
130 this.chargingStation.isRegistered() ||
131 (!this.chargingStation.getOcppStrictCompliance() && this.chargingStation.isInUnknownState())
132 ) {
124f3553
JB
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
9f2e3130 139 logger.error(this.chargingStation.logPrefix() + ' Handle request error: %j', error);
124f3553
JB
140 throw error;
141 }
142 } else {
143 // Throw exception
e7aeea18
JB
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 );
c0560973
JB
153 }
154 } else {
e7aeea18
JB
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 );
c0560973 164 }
de3dbcf5
JB
165 // Send the built result
166 await this.chargingStation.ocppRequestService.sendResult(messageId, result, commandName);
c0560973
JB
167 }
168
169 // Simulate charging station restart
170 private handleRequestReset(commandPayload: ResetRequest): DefaultResponse {
71623267
JB
171 // eslint-disable-next-line @typescript-eslint/no-misused-promises
172 setImmediate(async (): Promise<void> => {
e7aeea18
JB
173 await this.chargingStation.stop(
174 (commandPayload.type + 'Reset') as OCPP16StopTransactionReason
175 );
c0560973 176 await Utils.sleep(this.chargingStation.stationInfo.resetTime);
71623267 177 this.chargingStation.start();
c0560973 178 });
e7aeea18
JB
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 );
c0560973
JB
186 return Constants.OCPP_RESPONSE_ACCEPTED;
187 }
188
189 private handleRequestClearCache(): DefaultResponse {
190 return Constants.OCPP_RESPONSE_ACCEPTED;
191 }
192
e7aeea18
JB
193 private async handleRequestUnlockConnector(
194 commandPayload: UnlockConnectorRequest
195 ): Promise<UnlockConnectorResponse> {
c0560973
JB
196 const connectorId = commandPayload.connectorId;
197 if (connectorId === 0) {
e7aeea18
JB
198 logger.error(
199 this.chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString()
200 );
c0560973
JB
201 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
202 }
734d790d
JB
203 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
204 const transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
e7aeea18
JB
205 const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(
206 transactionId,
6ed92bc1
JB
207 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
208 this.chargingStation.getTransactionIdTag(transactionId),
e7aeea18
JB
209 OCPP16StopTransactionReason.UNLOCK_COMMAND
210 );
c0560973
JB
211 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
212 return Constants.OCPP_RESPONSE_UNLOCKED;
213 }
214 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
215 }
93b4a429
JB
216 await this.chargingStation.ocppRequestService.sendMessageHandler(
217 OCPP16RequestCommand.STATUS_NOTIFICATION,
218 {
219 connectorId,
220 status: OCPP16ChargePointStatus.AVAILABLE,
221 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
222 }
e7aeea18 223 );
734d790d 224 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
225 return Constants.OCPP_RESPONSE_UNLOCKED;
226 }
227
e7aeea18
JB
228 private handleRequestGetConfiguration(
229 commandPayload: GetConfigurationRequest
230 ): GetConfigurationResponse {
c0560973
JB
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
e7aeea18
JB
273 private handleRequestChangeConfiguration(
274 commandPayload: ChangeConfigurationRequest
275 ): ChangeConfigurationResponse {
c0560973
JB
276 // JSON request fields type sanity check
277 if (!Utils.isString(commandPayload.key)) {
e7aeea18
JB
278 logger.error(
279 `${this.chargingStation.logPrefix()} ${
78085c42 280 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
e7aeea18
JB
281 } request key field is not a string:`,
282 commandPayload
283 );
c0560973
JB
284 }
285 if (!Utils.isString(commandPayload.value)) {
e7aeea18
JB
286 logger.error(
287 `${this.chargingStation.logPrefix()} ${
78085c42 288 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION
e7aeea18
JB
289 } request value field is not a string:`,
290 commandPayload
291 );
c0560973
JB
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;
e7aeea18
JB
301 if (
302 this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value
303 ) {
c0560973
JB
304 this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
305 valueChanged = true;
306 }
307 let triggerHeartbeatRestart = false;
308 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
e7aeea18
JB
309 this.chargingStation.setConfigurationKeyValue(
310 OCPP16StandardParametersKey.HeartbeatInterval,
311 commandPayload.value
312 );
c0560973
JB
313 triggerHeartbeatRestart = true;
314 }
315 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
e7aeea18
JB
316 this.chargingStation.setConfigurationKeyValue(
317 OCPP16StandardParametersKey.HeartBeatInterval,
318 commandPayload.value
319 );
c0560973
JB
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
e7aeea18
JB
335 private handleRequestSetChargingProfile(
336 commandPayload: SetChargingProfileRequest
337 ): SetChargingProfileResponse {
734d790d 338 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18
JB
339 logger.error(
340 `${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${
341 commandPayload.connectorId
342 }`
343 );
c0560973
JB
344 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
345 }
e7aeea18
JB
346 if (
347 commandPayload.csChargingProfiles.chargingProfilePurpose ===
348 ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
349 commandPayload.connectorId !== 0
350 ) {
c0560973
JB
351 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
352 }
e7aeea18
JB
353 if (
354 commandPayload.csChargingProfiles.chargingProfilePurpose ===
355 ChargingProfilePurposeType.TX_PROFILE &&
356 (commandPayload.connectorId === 0 ||
357 !this.chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)
358 ) {
c0560973
JB
359 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
360 }
e7aeea18
JB
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 );
c0560973
JB
369 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
370 }
371
e7aeea18
JB
372 private handleRequestClearChargingProfile(
373 commandPayload: ClearChargingProfileRequest
374 ): ClearChargingProfileResponse {
734d790d 375 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
e7aeea18
JB
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 );
c0560973
JB
381 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
382 }
e7aeea18
JB
383 if (
384 commandPayload.connectorId &&
385 !Utils.isEmptyArray(
386 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
387 )
388 ) {
734d790d 389 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
e7aeea18
JB
390 logger.debug(
391 `${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`,
392 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles
393 );
c0560973
JB
394 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
395 }
396 if (!commandPayload.connectorId) {
397 let clearedCP = false;
734d790d 398 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
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 });
c0560973
JB
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
e7aeea18
JB
448 private async handleRequestChangeAvailability(
449 commandPayload: ChangeAvailabilityRequest
450 ): Promise<ChangeAvailabilityResponse> {
c0560973 451 const connectorId: number = commandPayload.connectorId;
734d790d 452 if (!this.chargingStation.getConnectorStatus(connectorId)) {
e7aeea18
JB
453 logger.error(
454 `${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`
455 );
c0560973
JB
456 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
457 }
e7aeea18
JB
458 const chargePointStatus: OCPP16ChargePointStatus =
459 commandPayload.type === OCPP16AvailabilityType.OPERATIVE
460 ? OCPP16ChargePointStatus.AVAILABLE
461 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
462 if (connectorId === 0) {
463 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
734d790d
JB
464 for (const id of this.chargingStation.connectors.keys()) {
465 if (this.chargingStation.getConnectorStatus(id)?.transactionStarted) {
c0560973
JB
466 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
467 }
734d790d 468 this.chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 469 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
93b4a429
JB
470 await this.chargingStation.ocppRequestService.sendMessageHandler(
471 OCPP16RequestCommand.STATUS_NOTIFICATION,
472 {
473 connectorId: id,
474 status: chargePointStatus,
475 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
476 }
e7aeea18 477 );
734d790d 478 this.chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
479 }
480 }
481 return response;
e7aeea18
JB
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 ) {
734d790d
JB
490 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
491 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
492 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
493 }
734d790d 494 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
93b4a429
JB
495 await this.chargingStation.ocppRequestService.sendMessageHandler(
496 OCPP16RequestCommand.STATUS_NOTIFICATION,
497 { connectorId, status: chargePointStatus, errorCode: OCPP16ChargePointErrorCode.NO_ERROR }
e7aeea18 498 );
734d790d 499 this.chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
500 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
501 }
502 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
503 }
504
e7aeea18
JB
505 private async handleRequestRemoteStartTransaction(
506 commandPayload: RemoteStartTransactionRequest
507 ): Promise<DefaultResponse> {
a7fc8211
JB
508 const transactionConnectorId: number = commandPayload.connectorId;
509 if (transactionConnectorId) {
93b4a429
JB
510 await this.chargingStation.ocppRequestService.sendMessageHandler(
511 OCPP16RequestCommand.STATUS_NOTIFICATION,
512 {
513 connectorId: transactionConnectorId,
514 status: OCPP16ChargePointStatus.PREPARING,
515 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
516 }
e7aeea18
JB
517 );
518 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
519 OCPP16ChargePointStatus.PREPARING;
520 if (
521 this.chargingStation.isChargingStationAvailable() &&
522 this.chargingStation.isConnectorAvailable(transactionConnectorId)
523 ) {
e060fe58 524 // Check if authorized
a7fc8211
JB
525 if (this.chargingStation.getAuthorizeRemoteTxRequests()) {
526 let authorized = false;
e7aeea18
JB
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;
36f6a92e 536 authorized = true;
71068fb9 537 } else if (this.chargingStation.getMayAuthorizeAtRemoteStart()) {
e7aeea18
JB
538 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(
539 transactionConnectorId,
540 commandPayload.idTag
541 );
a7fc8211
JB
542 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
543 authorized = true;
a7fc8211 544 }
71068fb9 545 } else {
e7aeea18
JB
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 );
a7fc8211
JB
549 }
550 if (authorized) {
551 // Authorization successful, start transaction
e7aeea18
JB
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 );
e060fe58
JB
578 return Constants.OCPP_RESPONSE_ACCEPTED;
579 }
e7aeea18
JB
580 return this.notifyRemoteStartTransactionRejected(
581 transactionConnectorId,
582 commandPayload.idTag
583 );
e060fe58 584 }
e7aeea18
JB
585 return this.notifyRemoteStartTransactionRejected(
586 transactionConnectorId,
587 commandPayload.idTag
588 );
a7fc8211 589 }
e7aeea18
JB
590 return this.notifyRemoteStartTransactionRejected(
591 transactionConnectorId,
592 commandPayload.idTag
593 );
36f6a92e 594 }
a7fc8211 595 // No authorization check required, start transaction
e7aeea18
JB
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 );
e060fe58
JB
621 return Constants.OCPP_RESPONSE_ACCEPTED;
622 }
e7aeea18
JB
623 return this.notifyRemoteStartTransactionRejected(
624 transactionConnectorId,
625 commandPayload.idTag
626 );
e060fe58 627 }
e7aeea18
JB
628 return this.notifyRemoteStartTransactionRejected(
629 transactionConnectorId,
630 commandPayload.idTag
631 );
c0560973 632 }
e7aeea18
JB
633 return this.notifyRemoteStartTransactionRejected(
634 transactionConnectorId,
635 commandPayload.idTag
636 );
c0560973 637 }
57939a9d 638 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
a7fc8211
JB
639 }
640
e7aeea18
JB
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 ) {
93b4a429
JB
649 await this.chargingStation.ocppRequestService.sendMessageHandler(
650 OCPP16RequestCommand.STATUS_NOTIFICATION,
651 {
652 connectorId,
653 status: OCPP16ChargePointStatus.AVAILABLE,
654 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
655 }
e7aeea18
JB
656 );
657 this.chargingStation.getConnectorStatus(connectorId).status =
658 OCPP16ChargePointStatus.AVAILABLE;
e060fe58 659 }
e7aeea18
JB
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 );
c0560973
JB
671 return Constants.OCPP_RESPONSE_REJECTED;
672 }
673
e7aeea18
JB
674 private setRemoteStartTransactionChargingProfile(
675 connectorId: number,
676 cp: OCPP16ChargingProfile
677 ): boolean {
a7fc8211
JB
678 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
679 this.chargingStation.setChargingProfile(connectorId, cp);
e7aeea18
JB
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 );
a7fc8211
JB
684 return true;
685 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
e7aeea18
JB
686 logger.warn(
687 `${this.chargingStation.logPrefix()} Not allowed to set ${
688 cp.chargingProfilePurpose
689 } charging profile(s) at remote start transaction`
690 );
a7fc8211 691 return false;
e060fe58
JB
692 } else if (!cp) {
693 return true;
a7fc8211
JB
694 }
695 }
696
e7aeea18
JB
697 private async handleRequestRemoteStopTransaction(
698 commandPayload: RemoteStopTransactionRequest
699 ): Promise<DefaultResponse> {
c0560973 700 const transactionId = commandPayload.transactionId;
734d790d 701 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
702 if (
703 connectorId > 0 &&
704 this.chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId
705 ) {
93b4a429
JB
706 await this.chargingStation.ocppRequestService.sendMessageHandler(
707 OCPP16RequestCommand.STATUS_NOTIFICATION,
708 {
709 connectorId,
710 status: OCPP16ChargePointStatus.FINISHING,
711 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
712 }
e7aeea18
JB
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 );
c0560973
JB
721 return Constants.OCPP_RESPONSE_ACCEPTED;
722 }
723 }
e7aeea18
JB
724 logger.info(
725 this.chargingStation.logPrefix() +
726 ' Trying to remote stop a non existing transaction ' +
727 transactionId.toString()
728 );
c0560973
JB
729 return Constants.OCPP_RESPONSE_REJECTED;
730 }
47e22477 731
e7aeea18
JB
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 );
a3868ec4 742 const uri = new URL(commandPayload.location);
47e22477
JB
743 if (uri.protocol.startsWith('ftp:')) {
744 let ftpClient: Client;
745 try {
e7aeea18
JB
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';
47e22477
JB
752 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
753 ftpClient = new Client();
754 const accessResponse = await ftpClient.access({
755 host: uri.host,
e8191622
JB
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 }),
47e22477
JB
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) => {
e7aeea18
JB
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 );
47e22477 772 });
e7aeea18
JB
773 uploadResponse = await ftpClient.uploadFrom(
774 path.join(path.resolve(__dirname, '../../../../'), diagnosticsArchive),
775 uri.pathname + diagnosticsArchive
776 );
47e22477 777 if (uploadResponse.code === 226) {
e7aeea18
JB
778 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
779 OCPP16DiagnosticsStatus.Uploaded
780 );
47e22477
JB
781 if (ftpClient) {
782 ftpClient.close();
783 }
784 return { fileName: diagnosticsArchive };
785 }
e7aeea18
JB
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 );
47e22477 793 }
e7aeea18
JB
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 );
47e22477 801 } catch (error) {
e7aeea18
JB
802 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(
803 OCPP16DiagnosticsStatus.UploadFailed
804 );
47e22477
JB
805 if (ftpClient) {
806 ftpClient.close();
807 }
e7aeea18
JB
808 return this.handleIncomingRequestError(
809 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
810 error as Error,
811 { errorResponse: Constants.OCPP_RESPONSE_EMPTY }
812 );
47e22477
JB
813 }
814 } else {
e7aeea18
JB
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 );
47e22477
JB
823 return Constants.OCPP_RESPONSE_EMPTY;
824 }
825 }
802cfa13 826
e7aeea18
JB
827 private handleRequestTriggerMessage(
828 commandPayload: OCPP16TriggerMessageRequest
829 ): OCPP16TriggerMessageResponse {
802cfa13
JB
830 try {
831 switch (commandPayload.requestedMessage) {
832 case MessageTrigger.BootNotification:
833 setTimeout(() => {
e7aeea18 834 this.chargingStation.ocppRequestService
6a8b180d
JB
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 }
e7aeea18
JB
855 )
856 .catch(() => {
857 /* This is intentional */
858 });
802cfa13
JB
859 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
860 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
861 case MessageTrigger.Heartbeat:
862 setTimeout(() => {
e7aeea18 863 this.chargingStation.ocppRequestService
94a464f9 864 .sendMessageHandler(OCPP16RequestCommand.HEARTBEAT, null, { triggerMessage: true })
e7aeea18
JB
865 .catch(() => {
866 /* This is intentional */
867 });
802cfa13
JB
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) {
e7aeea18
JB
874 return this.handleIncomingRequestError(
875 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
876 error as Error,
877 { errorResponse: Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED }
878 );
802cfa13
JB
879 }
880 }
c0560973 881}