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