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