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