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