Convert sendMeterValues to OCPP message sending handler
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
e7aeea18
JB
3import {
4 AuthorizeRequest,
5 OCPP16AuthorizationStatus,
6 OCPP16AuthorizeResponse,
7 OCPP16StartTransactionResponse,
8 OCPP16StopTransactionResponse,
9 StartTransactionRequest,
10 StopTransactionRequest,
11} from '../../../types/ocpp/1.6/Transaction';
12import {
13 HeartbeatRequest,
14 OCPP16RequestCommand,
15 StatusNotificationRequest,
16} from '../../../types/ocpp/1.6/Requests';
17import {
18 HeartbeatResponse,
19 OCPP16BootNotificationResponse,
20 OCPP16RegistrationStatus,
21 StatusNotificationResponse,
22} from '../../../types/ocpp/1.6/Responses';
c0560973
JB
23import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';
24
73b9adec 25import type ChargingStation from '../../ChargingStation';
a4bc2942 26import { ErrorType } from '../../../types/ocpp/ErrorType';
d1888640 27import { JsonType } from '../../../types/JsonType';
93b4a429 28import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
c0560973 29import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
fd0c36fa 30import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
c0560973 31import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
e58068fd 32import OCPPError from '../../../exception/OCPPError';
c0560973 33import OCPPResponseService from '../OCPPResponseService';
74c6a954 34import { ResponseHandler } from '../../../types/ocpp/Responses';
c0560973 35import Utils from '../../../utils/Utils';
9f2e3130 36import logger from '../../../utils/Logger';
c0560973 37
909dcf2d
JB
38const moduleName = 'OCPP16ResponseService';
39
c0560973 40export default class OCPP16ResponseService extends OCPPResponseService {
58144adb
JB
41 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
42
9f2e3130 43 public constructor(chargingStation: ChargingStation) {
909dcf2d 44 if (new.target?.name === moduleName) {
06127450 45 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
9f2e3130 46 }
58144adb
JB
47 super(chargingStation);
48 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
49 [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
50 [OCPP16RequestCommand.HEARTBEAT, this.handleResponseHeartbeat.bind(this)],
51 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)],
52 [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)],
53 [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)],
54 [OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)],
e7aeea18 55 [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)],
58144adb
JB
56 ]);
57 }
58
e7aeea18
JB
59 public async handleResponse(
60 commandName: OCPP16RequestCommand,
61 payload: JsonType | string,
62 requestPayload: JsonType
63 ): Promise<void> {
64 if (
65 this.chargingStation.isRegistered() ||
66 commandName === OCPP16RequestCommand.BOOT_NOTIFICATION
67 ) {
124f3553
JB
68 if (this.responseHandlers.has(commandName)) {
69 try {
70 await this.responseHandlers.get(commandName)(payload, requestPayload);
71 } catch (error) {
e7aeea18
JB
72 logger.error(
73 this.chargingStation.logPrefix() + ' Handle request response error: %j',
74 error
75 );
124f3553
JB
76 throw error;
77 }
78 } else {
79 // Throw exception
e7aeea18
JB
80 throw new OCPPError(
81 ErrorType.NOT_IMPLEMENTED,
82 `${commandName} is not implemented to handle request response payload ${JSON.stringify(
83 payload,
84 null,
85 2
86 )}`,
87 commandName
88 );
887fef76 89 }
c0560973 90 } else {
e7aeea18
JB
91 throw new OCPPError(
92 ErrorType.SECURITY_ERROR,
93 `${commandName} cannot be issued to handle request response payload ${JSON.stringify(
94 payload,
95 null,
96 2
97 )} while the charging station is not registered on the central server. `,
98 commandName
99 );
c0560973
JB
100 }
101 }
102
624df199 103 private handleResponseBootNotification(payload: OCPP16BootNotificationResponse): void {
c0560973 104 if (payload.status === OCPP16RegistrationStatus.ACCEPTED) {
e7aeea18
JB
105 this.chargingStation.addConfigurationKey(
106 OCPP16StandardParametersKey.HeartBeatInterval,
107 payload.interval.toString()
108 );
109 this.chargingStation.addConfigurationKey(
110 OCPP16StandardParametersKey.HeartbeatInterval,
111 payload.interval.toString(),
112 { visible: false }
113 );
114 this.chargingStation.heartbeatSetInterval
115 ? this.chargingStation.restartHeartbeat()
116 : this.chargingStation.startHeartbeat();
672fed6e 117 }
74c6a954 118 if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) {
e7aeea18
JB
119 const logMsg = `${this.chargingStation.logPrefix()} Charging station in '${
120 payload.status
121 }' state on the central server`;
122 payload.status === OCPP16RegistrationStatus.REJECTED
123 ? logger.warn(logMsg)
124 : logger.info(logMsg);
c0560973 125 } else {
e7aeea18
JB
126 logger.error(
127 this.chargingStation.logPrefix() +
128 ' Charging station boot notification response received: %j with undefined registration status',
129 payload
130 );
c0560973
JB
131 }
132 }
133
e7aeea18
JB
134 private handleResponseHeartbeat(
135 payload: HeartbeatResponse,
136 requestPayload: HeartbeatRequest
137 ): void {
138 logger.debug(
139 this.chargingStation.logPrefix() +
140 ' Heartbeat response received: %j to Heartbeat request: %j',
141 payload,
142 requestPayload
143 );
58144adb
JB
144 }
145
e7aeea18
JB
146 private handleResponseAuthorize(
147 payload: OCPP16AuthorizeResponse,
148 requestPayload: AuthorizeRequest
149 ): void {
58144adb 150 let authorizeConnectorId: number;
734d790d 151 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
152 if (
153 connectorId > 0 &&
154 this.chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag ===
155 requestPayload.idTag
156 ) {
734d790d 157 authorizeConnectorId = connectorId;
58144adb
JB
158 break;
159 }
160 }
161 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
a2653482 162 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true;
e7aeea18
JB
163 logger.debug(
164 `${this.chargingStation.logPrefix()} IdTag ${
165 requestPayload.idTag
166 } authorized on connector ${authorizeConnectorId}`
167 );
58144adb 168 } else {
a2653482 169 this.chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false;
734d790d 170 delete this.chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag;
e7aeea18
JB
171 logger.debug(
172 `${this.chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status ${
173 payload.idTagInfo.status
174 } on connector ${authorizeConnectorId}`
175 );
58144adb
JB
176 }
177 }
178
e7aeea18
JB
179 private async handleResponseStartTransaction(
180 payload: OCPP16StartTransactionResponse,
181 requestPayload: StartTransactionRequest
182 ): Promise<void> {
c0560973
JB
183 const connectorId = requestPayload.connectorId;
184
185 let transactionConnectorId: number;
734d790d
JB
186 for (const id of this.chargingStation.connectors.keys()) {
187 if (id > 0 && id === connectorId) {
188 transactionConnectorId = id;
c0560973
JB
189 break;
190 }
191 }
192 if (!transactionConnectorId) {
e7aeea18
JB
193 logger.error(
194 this.chargingStation.logPrefix() +
195 ' Trying to start a transaction on a non existing connector Id ' +
196 connectorId.toString()
197 );
c0560973
JB
198 return;
199 }
e7aeea18
JB
200 if (
201 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
202 this.chargingStation.getAuthorizeRemoteTxRequests() &&
203 this.chargingStation.getLocalAuthListEnabled() &&
204 this.chargingStation.hasAuthorizedTags() &&
205 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized
206 ) {
207 logger.error(
208 this.chargingStation.logPrefix() +
209 ' Trying to start a transaction with a not local authorized idTag ' +
210 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
211 ' on connector Id ' +
212 connectorId.toString()
213 );
a2653482
JB
214 await this.resetConnectorOnStartTransactionError(connectorId);
215 return;
216 }
e7aeea18
JB
217 if (
218 this.chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted &&
219 this.chargingStation.getAuthorizeRemoteTxRequests() &&
220 this.chargingStation.getMayAuthorizeAtRemoteStart() &&
221 !this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
222 !this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized
223 ) {
224 logger.error(
225 this.chargingStation.logPrefix() +
226 ' Trying to start a transaction with a not authorized idTag ' +
227 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
228 ' on connector Id ' +
229 connectorId.toString()
230 );
a2653482
JB
231 await this.resetConnectorOnStartTransactionError(connectorId);
232 return;
233 }
e7aeea18
JB
234 if (
235 this.chargingStation.getConnectorStatus(connectorId).idTagAuthorized &&
236 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag
237 ) {
238 logger.error(
239 this.chargingStation.logPrefix() +
240 ' Trying to start a transaction with an idTag ' +
241 requestPayload.idTag +
242 ' different from the authorize request one ' +
243 this.chargingStation.getConnectorStatus(connectorId).authorizeIdTag +
244 ' on connector Id ' +
245 connectorId.toString()
246 );
a2653482
JB
247 await this.resetConnectorOnStartTransactionError(connectorId);
248 return;
249 }
e7aeea18
JB
250 if (
251 this.chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized &&
252 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !==
253 requestPayload.idTag
254 ) {
255 logger.error(
256 this.chargingStation.logPrefix() +
257 ' Trying to start a transaction with an idTag ' +
258 requestPayload.idTag +
259 ' different from the local authorized one ' +
260 this.chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag +
261 ' on connector Id ' +
262 connectorId.toString()
263 );
a2653482 264 await this.resetConnectorOnStartTransactionError(connectorId);
163547b1
JB
265 return;
266 }
734d790d 267 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
e7aeea18
JB
268 logger.debug(
269 this.chargingStation.logPrefix() +
270 ' Trying to start a transaction on an already used connector ' +
271 connectorId.toString() +
272 ': %j',
273 this.chargingStation.getConnectorStatus(connectorId)
274 );
c0560973
JB
275 return;
276 }
e7aeea18
JB
277 if (
278 this.chargingStation.getConnectorStatus(connectorId)?.status !==
279 OCPP16ChargePointStatus.AVAILABLE &&
280 this.chargingStation.getConnectorStatus(connectorId)?.status !==
281 OCPP16ChargePointStatus.PREPARING
282 ) {
283 logger.error(
284 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${
285 this.chargingStation.getConnectorStatus(connectorId)?.status
286 }`
287 );
290d006c
JB
288 return;
289 }
f3a6f63b 290 if (!Number.isInteger(payload.transactionId)) {
e7aeea18
JB
291 logger.warn(
292 `${this.chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${
293 payload.transactionId
294 }, converting to integer`
295 );
f3a6f63b
JB
296 payload.transactionId = Utils.convertToInt(payload.transactionId);
297 }
c0560973 298
f0c6ed89 299 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
734d790d
JB
300 this.chargingStation.getConnectorStatus(connectorId).transactionStarted = true;
301 this.chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId;
302 this.chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag;
e7aeea18
JB
303 this.chargingStation.getConnectorStatus(
304 connectorId
305 ).transactionEnergyActiveImportRegisterValue = 0;
306 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue =
307 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
308 this.chargingStation,
309 connectorId,
310 requestPayload.meterStart
311 );
312 this.chargingStation.getBeginEndMeterValues() &&
313 (await this.chargingStation.ocppRequestService.sendTransactionBeginMeterValues(
314 connectorId,
315 payload.transactionId,
316 this.chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue
317 ));
93b4a429
JB
318 await this.chargingStation.ocppRequestService.sendMessageHandler(
319 OCPP16RequestCommand.STATUS_NOTIFICATION,
320 {
321 connectorId,
322 status: OCPP16ChargePointStatus.CHARGING,
323 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
324 }
e7aeea18
JB
325 );
326 this.chargingStation.getConnectorStatus(connectorId).status =
327 OCPP16ChargePointStatus.CHARGING;
328 logger.info(
329 this.chargingStation.logPrefix() +
330 ' Transaction ' +
331 payload.transactionId.toString() +
332 ' STARTED on ' +
333 this.chargingStation.stationInfo.chargingStationId +
334 '#' +
335 connectorId.toString() +
336 ' for idTag ' +
337 requestPayload.idTag
338 );
c0560973
JB
339 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
340 this.chargingStation.stationInfo.powerDivider++;
341 }
e7aeea18
JB
342 const configuredMeterValueSampleInterval = this.chargingStation.getConfigurationKey(
343 OCPP16StandardParametersKey.MeterValueSampleInterval
344 );
345 this.chargingStation.startMeterValues(
346 connectorId,
347 configuredMeterValueSampleInterval
348 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
349 : 60000
350 );
c0560973 351 } else {
e7aeea18
JB
352 logger.warn(
353 this.chargingStation.logPrefix() +
354 ' Starting transaction id ' +
355 payload.transactionId.toString() +
356 ' REJECTED with status ' +
357 payload?.idTagInfo?.status +
358 ', idTag ' +
359 requestPayload.idTag
360 );
a2653482
JB
361 await this.resetConnectorOnStartTransactionError(connectorId);
362 }
363 }
364
365 private async resetConnectorOnStartTransactionError(connectorId: number): Promise<void> {
366 this.chargingStation.resetConnectorStatus(connectorId);
e7aeea18
JB
367 if (
368 this.chargingStation.getConnectorStatus(connectorId).status !==
369 OCPP16ChargePointStatus.AVAILABLE
370 ) {
93b4a429
JB
371 await this.chargingStation.ocppRequestService.sendMessageHandler(
372 OCPP16RequestCommand.STATUS_NOTIFICATION,
373 {
374 connectorId,
375 status: OCPP16ChargePointStatus.AVAILABLE,
376 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
377 }
e7aeea18
JB
378 );
379 this.chargingStation.getConnectorStatus(connectorId).status =
380 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
381 }
382 }
383
e7aeea18
JB
384 private async handleResponseStopTransaction(
385 payload: OCPP16StopTransactionResponse,
386 requestPayload: StopTransactionRequest
387 ): Promise<void> {
c0560973 388 let transactionConnectorId: number;
734d790d 389 for (const connectorId of this.chargingStation.connectors.keys()) {
e7aeea18
JB
390 if (
391 connectorId > 0 &&
392 this.chargingStation.getConnectorStatus(connectorId)?.transactionId ===
393 requestPayload.transactionId
394 ) {
734d790d 395 transactionConnectorId = connectorId;
c0560973
JB
396 break;
397 }
398 }
399 if (!transactionConnectorId) {
e7aeea18
JB
400 logger.error(
401 this.chargingStation.logPrefix() +
402 ' Trying to stop a non existing transaction ' +
403 requestPayload.transactionId.toString()
404 );
c0560973
JB
405 return;
406 }
407 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
e7aeea18
JB
408 this.chargingStation.getBeginEndMeterValues() &&
409 !this.chargingStation.getOcppStrictCompliance() &&
410 this.chargingStation.getOutOfOrderEndMeterValues() &&
411 (await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(
412 transactionConnectorId,
413 requestPayload.transactionId,
414 OCPP16ServiceUtils.buildTransactionEndMeterValue(
415 this.chargingStation,
416 transactionConnectorId,
417 requestPayload.meterStop
418 )
419 ));
420 if (
421 !this.chargingStation.isChargingStationAvailable() ||
422 !this.chargingStation.isConnectorAvailable(transactionConnectorId)
423 ) {
93b4a429
JB
424 await this.chargingStation.ocppRequestService.sendMessageHandler(
425 OCPP16RequestCommand.STATUS_NOTIFICATION,
426 {
427 connectorId: transactionConnectorId,
428 status: OCPP16ChargePointStatus.UNAVAILABLE,
429 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
430 }
e7aeea18
JB
431 );
432 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
433 OCPP16ChargePointStatus.UNAVAILABLE;
c0560973 434 } else {
93b4a429
JB
435 await this.chargingStation.ocppRequestService.sendMessageHandler(
436 OCPP16RequestCommand.STATUS_NOTIFICATION,
437 {
438 connectorId: transactionConnectorId,
439 status: OCPP16ChargePointStatus.AVAILABLE,
440 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
441 }
e7aeea18
JB
442 );
443 this.chargingStation.getConnectorStatus(transactionConnectorId).status =
444 OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
445 }
446 if (this.chargingStation.stationInfo.powerSharedByConnectors) {
447 this.chargingStation.stationInfo.powerDivider--;
448 }
e7aeea18
JB
449 logger.info(
450 this.chargingStation.logPrefix() +
451 ' Transaction ' +
452 requestPayload.transactionId.toString() +
453 ' STOPPED on ' +
454 this.chargingStation.stationInfo.chargingStationId +
455 '#' +
456 transactionConnectorId.toString()
457 );
a2653482 458 this.chargingStation.resetConnectorStatus(transactionConnectorId);
c0560973 459 } else {
e7aeea18
JB
460 logger.warn(
461 this.chargingStation.logPrefix() +
462 ' Stopping transaction id ' +
463 requestPayload.transactionId.toString() +
464 ' REJECTED with status ' +
465 payload.idTagInfo?.status
466 );
c0560973
JB
467 }
468 }
469
e7aeea18
JB
470 private handleResponseStatusNotification(
471 payload: StatusNotificationRequest,
472 requestPayload: StatusNotificationResponse
473 ): void {
474 logger.debug(
475 this.chargingStation.logPrefix() +
476 ' Status notification response received: %j to StatusNotification request: %j',
477 payload,
478 requestPayload
479 );
c0560973
JB
480 }
481
e7aeea18
JB
482 private handleResponseMeterValues(
483 payload: MeterValuesRequest,
484 requestPayload: MeterValuesResponse
485 ): void {
486 logger.debug(
487 this.chargingStation.logPrefix() +
488 ' MeterValues response received: %j to MeterValues request: %j',
489 payload,
490 requestPayload
491 );
c0560973 492 }
c0560973 493}