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