Commit | Line | Data |
---|---|---|
c8eeb62b JB |
1 | // Partial Copyright Jerome Benoit. 2021. All Rights Reserved. |
2 | ||
844e496b JB |
3 | import fs from 'fs'; |
4 | import path from 'path'; | |
5 | import { fileURLToPath } from 'url'; | |
6 | ||
7 | import { JSONSchemaType } from 'ajv'; | |
8 | ||
8114d10e JB |
9 | import OCPPError from '../../../exception/OCPPError'; |
10 | import { JsonType } from '../../../types/JsonType'; | |
11 | import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode'; | |
12 | import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus'; | |
13 | import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration'; | |
e7aeea18 | 14 | import { |
8114d10e JB |
15 | OCPP16MeterValuesRequest, |
16 | OCPP16MeterValuesResponse, | |
17 | } from '../../../types/ocpp/1.6/MeterValues'; | |
e7aeea18 | 18 | import { |
ef6fa3fb | 19 | OCPP16BootNotificationRequest, |
e7aeea18 | 20 | OCPP16RequestCommand, |
ef6fa3fb | 21 | OCPP16StatusNotificationRequest, |
e7aeea18 JB |
22 | } from '../../../types/ocpp/1.6/Requests'; |
23 | import { | |
e7aeea18 | 24 | OCPP16BootNotificationResponse, |
844e496b | 25 | OCPP16HeartbeatResponse, |
e7aeea18 | 26 | OCPP16RegistrationStatus, |
f22266fd | 27 | OCPP16StatusNotificationResponse, |
e7aeea18 | 28 | } from '../../../types/ocpp/1.6/Responses'; |
ef6fa3fb | 29 | import { |
8114d10e JB |
30 | OCPP16AuthorizationStatus, |
31 | OCPP16AuthorizeRequest, | |
32 | OCPP16AuthorizeResponse, | |
33 | OCPP16StartTransactionRequest, | |
34 | OCPP16StartTransactionResponse, | |
35 | OCPP16StopTransactionRequest, | |
36 | OCPP16StopTransactionResponse, | |
37 | } from '../../../types/ocpp/1.6/Transaction'; | |
a4bc2942 | 38 | import { ErrorType } from '../../../types/ocpp/ErrorType'; |
74c6a954 | 39 | import { ResponseHandler } from '../../../types/ocpp/Responses'; |
9f2e3130 | 40 | import logger from '../../../utils/Logger'; |
8114d10e JB |
41 | import Utils from '../../../utils/Utils'; |
42 | import type ChargingStation from '../../ChargingStation'; | |
43 | import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils'; | |
65554cc3 | 44 | import { ChargingStationUtils } from '../../ChargingStationUtils'; |
8114d10e JB |
45 | import OCPPResponseService from '../OCPPResponseService'; |
46 | import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; | |
c0560973 | 47 | |
909dcf2d JB |
48 | const moduleName = 'OCPP16ResponseService'; |
49 | ||
c0560973 | 50 | export default class OCPP16ResponseService extends OCPPResponseService { |
58144adb | 51 | private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>; |
844e496b JB |
52 | private bootNotificationResponseJsonSchema: JSONSchemaType<OCPP16BootNotificationResponse>; |
53 | private heartbeatResponseJsonSchema: JSONSchemaType<OCPP16HeartbeatResponse>; | |
54 | private authorizeResponseJsonSchema: JSONSchemaType<OCPP16AuthorizeResponse>; | |
55 | private startTransactionResponseJsonSchema: JSONSchemaType<OCPP16StartTransactionResponse>; | |
56 | private stopTransactionResponseJsonSchema: JSONSchemaType<OCPP16StopTransactionResponse>; | |
57 | private statusNotificationResponseJsonSchema: JSONSchemaType<OCPP16StatusNotificationResponse>; | |
58 | private meterValuesResponseJsonSchema: JSONSchemaType<OCPP16MeterValuesResponse>; | |
58144adb | 59 | |
08f130a0 | 60 | public constructor() { |
909dcf2d | 61 | if (new.target?.name === moduleName) { |
06127450 | 62 | throw new TypeError(`Cannot construct ${new.target?.name} instances directly`); |
9f2e3130 | 63 | } |
08f130a0 | 64 | super(); |
58144adb JB |
65 | this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([ |
66 | [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)], | |
67 | [OCPP16RequestCommand.HEARTBEAT, this.handleResponseHeartbeat.bind(this)], | |
68 | [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)], | |
69 | [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)], | |
70 | [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)], | |
71 | [OCPP16RequestCommand.STATUS_NOTIFICATION, this.handleResponseStatusNotification.bind(this)], | |
e7aeea18 | 72 | [OCPP16RequestCommand.METER_VALUES, this.handleResponseMeterValues.bind(this)], |
58144adb | 73 | ]); |
844e496b JB |
74 | this.bootNotificationResponseJsonSchema = JSON.parse( |
75 | fs.readFileSync( | |
76 | path.resolve( | |
77 | path.dirname(fileURLToPath(import.meta.url)), | |
78 | '../../../assets/json-schemas/ocpp/1.6/BootNotificationResponse.json' | |
79 | ), | |
80 | 'utf8' | |
81 | ) | |
82 | ) as JSONSchemaType<OCPP16BootNotificationResponse>; | |
83 | this.heartbeatResponseJsonSchema = JSON.parse( | |
84 | fs.readFileSync( | |
85 | path.resolve( | |
86 | path.dirname(fileURLToPath(import.meta.url)), | |
87 | '../../../assets/json-schemas/ocpp/1.6/HeartbeatResponse.json' | |
88 | ), | |
89 | 'utf8' | |
90 | ) | |
91 | ) as JSONSchemaType<OCPP16HeartbeatResponse>; | |
92 | this.authorizeResponseJsonSchema = JSON.parse( | |
93 | fs.readFileSync( | |
94 | path.resolve( | |
95 | path.dirname(fileURLToPath(import.meta.url)), | |
96 | '../../../assets/json-schemas/ocpp/1.6/AuthorizeResponse.json' | |
97 | ), | |
98 | 'utf8' | |
99 | ) | |
100 | ) as JSONSchemaType<OCPP16AuthorizeResponse>; | |
101 | this.startTransactionResponseJsonSchema = JSON.parse( | |
102 | fs.readFileSync( | |
103 | path.resolve( | |
104 | path.dirname(fileURLToPath(import.meta.url)), | |
105 | '../../../assets/json-schemas/ocpp/1.6/StartTransactionResponse.json' | |
106 | ), | |
107 | 'utf8' | |
108 | ) | |
109 | ) as JSONSchemaType<OCPP16StartTransactionResponse>; | |
110 | this.stopTransactionResponseJsonSchema = JSON.parse( | |
111 | fs.readFileSync( | |
112 | path.resolve( | |
113 | path.dirname(fileURLToPath(import.meta.url)), | |
114 | '../../../assets/json-schemas/ocpp/1.6/StopTransactionResponse.json' | |
115 | ), | |
116 | 'utf8' | |
117 | ) | |
118 | ) as JSONSchemaType<OCPP16StopTransactionResponse>; | |
119 | this.statusNotificationResponseJsonSchema = JSON.parse( | |
120 | fs.readFileSync( | |
121 | path.resolve( | |
122 | path.dirname(fileURLToPath(import.meta.url)), | |
123 | '../../../assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json' | |
124 | ), | |
125 | 'utf8' | |
126 | ) | |
127 | ) as JSONSchemaType<OCPP16StatusNotificationResponse>; | |
128 | this.meterValuesResponseJsonSchema = JSON.parse( | |
129 | fs.readFileSync( | |
130 | path.resolve( | |
131 | path.dirname(fileURLToPath(import.meta.url)), | |
132 | '../../../assets/json-schemas/ocpp/1.6/MeterValuesResponse.json' | |
133 | ), | |
134 | 'utf8' | |
135 | ) | |
136 | ) as JSONSchemaType<OCPP16MeterValuesResponse>; | |
58144adb JB |
137 | } |
138 | ||
f7f98c68 | 139 | public async responseHandler( |
08f130a0 | 140 | chargingStation: ChargingStation, |
e7aeea18 | 141 | commandName: OCPP16RequestCommand, |
5cc4b63b JB |
142 | payload: JsonType, |
143 | requestPayload: JsonType | |
e7aeea18 | 144 | ): Promise<void> { |
08f130a0 | 145 | if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) { |
65554cc3 JB |
146 | if ( |
147 | this.responseHandlers.has(commandName) && | |
ada189a8 | 148 | ChargingStationUtils.isRequestCommandSupported(commandName, chargingStation) |
65554cc3 | 149 | ) { |
124f3553 | 150 | try { |
08f130a0 | 151 | await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload); |
124f3553 | 152 | } catch (error) { |
08f130a0 | 153 | logger.error(chargingStation.logPrefix() + ' Handle request response error: %j', error); |
124f3553 JB |
154 | throw error; |
155 | } | |
156 | } else { | |
157 | // Throw exception | |
e7aeea18 JB |
158 | throw new OCPPError( |
159 | ErrorType.NOT_IMPLEMENTED, | |
e3018bc4 | 160 | `${commandName} is not implemented to handle request response PDU ${JSON.stringify( |
e7aeea18 JB |
161 | payload, |
162 | null, | |
163 | 2 | |
164 | )}`, | |
7369e417 JB |
165 | commandName, |
166 | payload | |
e7aeea18 | 167 | ); |
887fef76 | 168 | } |
c0560973 | 169 | } else { |
e7aeea18 JB |
170 | throw new OCPPError( |
171 | ErrorType.SECURITY_ERROR, | |
e3018bc4 | 172 | `${commandName} cannot be issued to handle request response PDU ${JSON.stringify( |
e7aeea18 JB |
173 | payload, |
174 | null, | |
175 | 2 | |
176 | )} while the charging station is not registered on the central server. `, | |
7369e417 JB |
177 | commandName, |
178 | payload | |
e7aeea18 | 179 | ); |
c0560973 JB |
180 | } |
181 | } | |
182 | ||
08f130a0 JB |
183 | private handleResponseBootNotification( |
184 | chargingStation: ChargingStation, | |
185 | payload: OCPP16BootNotificationResponse | |
186 | ): void { | |
844e496b JB |
187 | this.validateResponsePayload( |
188 | chargingStation, | |
189 | OCPP16RequestCommand.BOOT_NOTIFICATION, | |
190 | this.bootNotificationResponseJsonSchema, | |
191 | payload | |
192 | ); | |
c0560973 | 193 | if (payload.status === OCPP16RegistrationStatus.ACCEPTED) { |
17ac262c JB |
194 | ChargingStationConfigurationUtils.addConfigurationKey( |
195 | chargingStation, | |
f0f65a62 | 196 | OCPP16StandardParametersKey.HeartbeatInterval, |
a95873d8 JB |
197 | payload.interval.toString(), |
198 | {}, | |
199 | { overwrite: true, save: true } | |
e7aeea18 | 200 | ); |
17ac262c JB |
201 | ChargingStationConfigurationUtils.addConfigurationKey( |
202 | chargingStation, | |
f0f65a62 | 203 | OCPP16StandardParametersKey.HeartBeatInterval, |
e7aeea18 | 204 | payload.interval.toString(), |
00db15b8 | 205 | { visible: false }, |
a95873d8 | 206 | { overwrite: true, save: true } |
e7aeea18 | 207 | ); |
08f130a0 JB |
208 | chargingStation.heartbeatSetInterval |
209 | ? chargingStation.restartHeartbeat() | |
210 | : chargingStation.startHeartbeat(); | |
672fed6e | 211 | } |
74c6a954 | 212 | if (Object.values(OCPP16RegistrationStatus).includes(payload.status)) { |
08f130a0 | 213 | const logMsg = `${chargingStation.logPrefix()} Charging station in '${ |
e7aeea18 JB |
214 | payload.status |
215 | }' state on the central server`; | |
216 | payload.status === OCPP16RegistrationStatus.REJECTED | |
217 | ? logger.warn(logMsg) | |
218 | : logger.info(logMsg); | |
c0560973 | 219 | } else { |
e7aeea18 | 220 | logger.error( |
08f130a0 | 221 | chargingStation.logPrefix() + |
e7aeea18 JB |
222 | ' Charging station boot notification response received: %j with undefined registration status', |
223 | payload | |
224 | ); | |
c0560973 JB |
225 | } |
226 | } | |
227 | ||
844e496b JB |
228 | private handleResponseHeartbeat( |
229 | chargingStation: ChargingStation, | |
230 | payload: OCPP16HeartbeatResponse | |
231 | ): void { | |
232 | this.validateResponsePayload( | |
233 | chargingStation, | |
234 | OCPP16RequestCommand.HEARTBEAT, | |
235 | this.heartbeatResponseJsonSchema, | |
236 | payload | |
237 | ); | |
238 | } | |
58144adb | 239 | |
e7aeea18 | 240 | private handleResponseAuthorize( |
08f130a0 | 241 | chargingStation: ChargingStation, |
e7aeea18 | 242 | payload: OCPP16AuthorizeResponse, |
ef6fa3fb | 243 | requestPayload: OCPP16AuthorizeRequest |
e7aeea18 | 244 | ): void { |
844e496b JB |
245 | this.validateResponsePayload( |
246 | chargingStation, | |
247 | OCPP16RequestCommand.AUTHORIZE, | |
248 | this.authorizeResponseJsonSchema, | |
249 | payload | |
250 | ); | |
58144adb | 251 | let authorizeConnectorId: number; |
08f130a0 | 252 | for (const connectorId of chargingStation.connectors.keys()) { |
e7aeea18 JB |
253 | if ( |
254 | connectorId > 0 && | |
08f130a0 | 255 | chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag |
e7aeea18 | 256 | ) { |
734d790d | 257 | authorizeConnectorId = connectorId; |
58144adb JB |
258 | break; |
259 | } | |
260 | } | |
261 | if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) { | |
08f130a0 | 262 | chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = true; |
e7aeea18 | 263 | logger.debug( |
08f130a0 | 264 | `${chargingStation.logPrefix()} IdTag ${ |
e7aeea18 JB |
265 | requestPayload.idTag |
266 | } authorized on connector ${authorizeConnectorId}` | |
267 | ); | |
58144adb | 268 | } else { |
08f130a0 JB |
269 | chargingStation.getConnectorStatus(authorizeConnectorId).idTagAuthorized = false; |
270 | delete chargingStation.getConnectorStatus(authorizeConnectorId).authorizeIdTag; | |
e7aeea18 | 271 | logger.debug( |
08f130a0 | 272 | `${chargingStation.logPrefix()} IdTag ${requestPayload.idTag} refused with status '${ |
e7aeea18 | 273 | payload.idTagInfo.status |
e8e865ea | 274 | }' on connector ${authorizeConnectorId}` |
e7aeea18 | 275 | ); |
58144adb JB |
276 | } |
277 | } | |
278 | ||
e7aeea18 | 279 | private async handleResponseStartTransaction( |
08f130a0 | 280 | chargingStation: ChargingStation, |
e7aeea18 | 281 | payload: OCPP16StartTransactionResponse, |
ef6fa3fb | 282 | requestPayload: OCPP16StartTransactionRequest |
e7aeea18 | 283 | ): Promise<void> { |
844e496b JB |
284 | this.validateResponsePayload( |
285 | chargingStation, | |
286 | OCPP16RequestCommand.START_TRANSACTION, | |
287 | this.startTransactionResponseJsonSchema, | |
288 | payload | |
289 | ); | |
c0560973 JB |
290 | const connectorId = requestPayload.connectorId; |
291 | ||
292 | let transactionConnectorId: number; | |
08f130a0 | 293 | for (const id of chargingStation.connectors.keys()) { |
734d790d JB |
294 | if (id > 0 && id === connectorId) { |
295 | transactionConnectorId = id; | |
c0560973 JB |
296 | break; |
297 | } | |
298 | } | |
299 | if (!transactionConnectorId) { | |
e7aeea18 | 300 | logger.error( |
08f130a0 | 301 | chargingStation.logPrefix() + |
e7aeea18 JB |
302 | ' Trying to start a transaction on a non existing connector Id ' + |
303 | connectorId.toString() | |
304 | ); | |
c0560973 JB |
305 | return; |
306 | } | |
e7aeea18 | 307 | if ( |
08f130a0 JB |
308 | chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted && |
309 | chargingStation.getAuthorizeRemoteTxRequests() && | |
310 | chargingStation.getLocalAuthListEnabled() && | |
311 | chargingStation.hasAuthorizedTags() && | |
312 | !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized | |
e7aeea18 JB |
313 | ) { |
314 | logger.error( | |
08f130a0 | 315 | chargingStation.logPrefix() + |
e7aeea18 | 316 | ' Trying to start a transaction with a not local authorized idTag ' + |
08f130a0 | 317 | chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag + |
e7aeea18 JB |
318 | ' on connector Id ' + |
319 | connectorId.toString() | |
320 | ); | |
08f130a0 | 321 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId); |
a2653482 JB |
322 | return; |
323 | } | |
e7aeea18 | 324 | if ( |
08f130a0 JB |
325 | chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted && |
326 | chargingStation.getAuthorizeRemoteTxRequests() && | |
327 | chargingStation.getMayAuthorizeAtRemoteStart() && | |
328 | !chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized && | |
329 | !chargingStation.getConnectorStatus(connectorId).idTagAuthorized | |
e7aeea18 JB |
330 | ) { |
331 | logger.error( | |
08f130a0 | 332 | chargingStation.logPrefix() + |
e7aeea18 | 333 | ' Trying to start a transaction with a not authorized idTag ' + |
08f130a0 | 334 | chargingStation.getConnectorStatus(connectorId).authorizeIdTag + |
e7aeea18 JB |
335 | ' on connector Id ' + |
336 | connectorId.toString() | |
337 | ); | |
08f130a0 | 338 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId); |
a2653482 JB |
339 | return; |
340 | } | |
e7aeea18 | 341 | if ( |
08f130a0 JB |
342 | chargingStation.getConnectorStatus(connectorId).idTagAuthorized && |
343 | chargingStation.getConnectorStatus(connectorId).authorizeIdTag !== requestPayload.idTag | |
e7aeea18 JB |
344 | ) { |
345 | logger.error( | |
08f130a0 | 346 | chargingStation.logPrefix() + |
e7aeea18 JB |
347 | ' Trying to start a transaction with an idTag ' + |
348 | requestPayload.idTag + | |
349 | ' different from the authorize request one ' + | |
08f130a0 | 350 | chargingStation.getConnectorStatus(connectorId).authorizeIdTag + |
e7aeea18 JB |
351 | ' on connector Id ' + |
352 | connectorId.toString() | |
353 | ); | |
08f130a0 | 354 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId); |
a2653482 JB |
355 | return; |
356 | } | |
e7aeea18 | 357 | if ( |
08f130a0 JB |
358 | chargingStation.getConnectorStatus(connectorId).idTagLocalAuthorized && |
359 | chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag !== requestPayload.idTag | |
e7aeea18 JB |
360 | ) { |
361 | logger.error( | |
08f130a0 | 362 | chargingStation.logPrefix() + |
e7aeea18 JB |
363 | ' Trying to start a transaction with an idTag ' + |
364 | requestPayload.idTag + | |
365 | ' different from the local authorized one ' + | |
08f130a0 | 366 | chargingStation.getConnectorStatus(connectorId).localAuthorizeIdTag + |
e7aeea18 JB |
367 | ' on connector Id ' + |
368 | connectorId.toString() | |
369 | ); | |
08f130a0 | 370 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId); |
163547b1 JB |
371 | return; |
372 | } | |
08f130a0 | 373 | if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted) { |
e7aeea18 | 374 | logger.debug( |
08f130a0 | 375 | chargingStation.logPrefix() + |
e7aeea18 JB |
376 | ' Trying to start a transaction on an already used connector ' + |
377 | connectorId.toString() + | |
378 | ': %j', | |
08f130a0 | 379 | chargingStation.getConnectorStatus(connectorId) |
e7aeea18 | 380 | ); |
c0560973 JB |
381 | return; |
382 | } | |
e7aeea18 | 383 | if ( |
08f130a0 | 384 | chargingStation.getConnectorStatus(connectorId)?.status !== |
e7aeea18 | 385 | OCPP16ChargePointStatus.AVAILABLE && |
08f130a0 | 386 | chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.PREPARING |
e7aeea18 JB |
387 | ) { |
388 | logger.error( | |
08f130a0 JB |
389 | `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with status ${ |
390 | chargingStation.getConnectorStatus(connectorId)?.status | |
e7aeea18 JB |
391 | }` |
392 | ); | |
290d006c JB |
393 | return; |
394 | } | |
f3a6f63b | 395 | if (!Number.isInteger(payload.transactionId)) { |
e7aeea18 | 396 | logger.warn( |
08f130a0 | 397 | `${chargingStation.logPrefix()} Trying to start a transaction on connector ${connectorId.toString()} with a non integer transaction Id ${ |
e7aeea18 JB |
398 | payload.transactionId |
399 | }, converting to integer` | |
400 | ); | |
f3a6f63b JB |
401 | payload.transactionId = Utils.convertToInt(payload.transactionId); |
402 | } | |
c0560973 | 403 | |
f0c6ed89 | 404 | if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { |
08f130a0 JB |
405 | chargingStation.getConnectorStatus(connectorId).transactionStarted = true; |
406 | chargingStation.getConnectorStatus(connectorId).transactionId = payload.transactionId; | |
407 | chargingStation.getConnectorStatus(connectorId).transactionIdTag = requestPayload.idTag; | |
408 | chargingStation.getConnectorStatus( | |
e7aeea18 JB |
409 | connectorId |
410 | ).transactionEnergyActiveImportRegisterValue = 0; | |
08f130a0 | 411 | chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue = |
e7aeea18 | 412 | OCPP16ServiceUtils.buildTransactionBeginMeterValue( |
08f130a0 | 413 | chargingStation, |
e7aeea18 JB |
414 | connectorId, |
415 | requestPayload.meterStart | |
416 | ); | |
08f130a0 JB |
417 | chargingStation.getBeginEndMeterValues() && |
418 | (await chargingStation.ocppRequestService.requestHandler< | |
ef6fa3fb JB |
419 | OCPP16MeterValuesRequest, |
420 | OCPP16MeterValuesResponse | |
08f130a0 | 421 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { |
93b4a429 | 422 | connectorId, |
ef6fa3fb | 423 | transactionId: payload.transactionId, |
7369e417 | 424 | meterValue: [chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue], |
ef6fa3fb | 425 | })); |
08f130a0 | 426 | await chargingStation.ocppRequestService.requestHandler< |
ef6fa3fb JB |
427 | OCPP16StatusNotificationRequest, |
428 | OCPP16StatusNotificationResponse | |
08f130a0 | 429 | >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, { |
ef6fa3fb JB |
430 | connectorId, |
431 | status: OCPP16ChargePointStatus.CHARGING, | |
432 | errorCode: OCPP16ChargePointErrorCode.NO_ERROR, | |
433 | }); | |
08f130a0 | 434 | chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.CHARGING; |
e7aeea18 | 435 | logger.info( |
08f130a0 | 436 | chargingStation.logPrefix() + |
e7aeea18 JB |
437 | ' Transaction ' + |
438 | payload.transactionId.toString() + | |
439 | ' STARTED on ' + | |
08f130a0 | 440 | chargingStation.stationInfo.chargingStationId + |
e7aeea18 JB |
441 | '#' + |
442 | connectorId.toString() + | |
443 | ' for idTag ' + | |
444 | requestPayload.idTag | |
445 | ); | |
08f130a0 | 446 | if (chargingStation.stationInfo.powerSharedByConnectors) { |
fa7bccf4 | 447 | chargingStation.powerDivider++; |
c0560973 | 448 | } |
17ac262c JB |
449 | const configuredMeterValueSampleInterval = |
450 | ChargingStationConfigurationUtils.getConfigurationKey( | |
451 | chargingStation, | |
452 | OCPP16StandardParametersKey.MeterValueSampleInterval | |
453 | ); | |
08f130a0 | 454 | chargingStation.startMeterValues( |
e7aeea18 JB |
455 | connectorId, |
456 | configuredMeterValueSampleInterval | |
457 | ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000 | |
458 | : 60000 | |
459 | ); | |
c0560973 | 460 | } else { |
e7aeea18 | 461 | logger.warn( |
08f130a0 | 462 | chargingStation.logPrefix() + |
e7aeea18 JB |
463 | ' Starting transaction id ' + |
464 | payload.transactionId.toString() + | |
e8e865ea | 465 | " REJECTED with status '" + |
e7aeea18 | 466 | payload?.idTagInfo?.status + |
e8e865ea | 467 | "', idTag " + |
e7aeea18 JB |
468 | requestPayload.idTag |
469 | ); | |
08f130a0 | 470 | await this.resetConnectorOnStartTransactionError(chargingStation, connectorId); |
a2653482 JB |
471 | } |
472 | } | |
473 | ||
08f130a0 JB |
474 | private async resetConnectorOnStartTransactionError( |
475 | chargingStation: ChargingStation, | |
476 | connectorId: number | |
477 | ): Promise<void> { | |
478 | chargingStation.resetConnectorStatus(connectorId); | |
e7aeea18 | 479 | if ( |
08f130a0 | 480 | chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE |
e7aeea18 | 481 | ) { |
08f130a0 | 482 | await chargingStation.ocppRequestService.requestHandler< |
ef6fa3fb JB |
483 | OCPP16StatusNotificationRequest, |
484 | OCPP16StatusNotificationResponse | |
08f130a0 | 485 | >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, { |
ef6fa3fb JB |
486 | connectorId, |
487 | status: OCPP16ChargePointStatus.AVAILABLE, | |
488 | errorCode: OCPP16ChargePointErrorCode.NO_ERROR, | |
489 | }); | |
08f130a0 | 490 | chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE; |
c0560973 JB |
491 | } |
492 | } | |
493 | ||
e7aeea18 | 494 | private async handleResponseStopTransaction( |
08f130a0 | 495 | chargingStation: ChargingStation, |
e7aeea18 | 496 | payload: OCPP16StopTransactionResponse, |
ef6fa3fb | 497 | requestPayload: OCPP16StopTransactionRequest |
e7aeea18 | 498 | ): Promise<void> { |
844e496b JB |
499 | this.validateResponsePayload( |
500 | chargingStation, | |
501 | OCPP16RequestCommand.STOP_TRANSACTION, | |
502 | this.stopTransactionResponseJsonSchema, | |
503 | payload | |
504 | ); | |
08f130a0 | 505 | const transactionConnectorId = chargingStation.getConnectorIdByTransactionId( |
f479a792 JB |
506 | requestPayload.transactionId |
507 | ); | |
c0560973 | 508 | if (!transactionConnectorId) { |
e7aeea18 | 509 | logger.error( |
08f130a0 | 510 | chargingStation.logPrefix() + |
e7aeea18 JB |
511 | ' Trying to stop a non existing transaction ' + |
512 | requestPayload.transactionId.toString() | |
513 | ); | |
c0560973 JB |
514 | return; |
515 | } | |
516 | if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { | |
08f130a0 JB |
517 | chargingStation.getBeginEndMeterValues() && |
518 | !chargingStation.getOcppStrictCompliance() && | |
519 | chargingStation.getOutOfOrderEndMeterValues() && | |
520 | (await chargingStation.ocppRequestService.requestHandler< | |
ef6fa3fb JB |
521 | OCPP16MeterValuesRequest, |
522 | OCPP16MeterValuesResponse | |
08f130a0 | 523 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { |
ef6fa3fb JB |
524 | connectorId: transactionConnectorId, |
525 | transactionId: requestPayload.transactionId, | |
7369e417 JB |
526 | meterValue: [ |
527 | OCPP16ServiceUtils.buildTransactionEndMeterValue( | |
528 | chargingStation, | |
529 | transactionConnectorId, | |
530 | requestPayload.meterStop | |
531 | ), | |
532 | ], | |
ef6fa3fb | 533 | })); |
e7aeea18 | 534 | if ( |
08f130a0 JB |
535 | !chargingStation.isChargingStationAvailable() || |
536 | !chargingStation.isConnectorAvailable(transactionConnectorId) | |
e7aeea18 | 537 | ) { |
08f130a0 | 538 | await chargingStation.ocppRequestService.requestHandler< |
ef6fa3fb JB |
539 | OCPP16StatusNotificationRequest, |
540 | OCPP16StatusNotificationResponse | |
08f130a0 | 541 | >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, { |
ef6fa3fb JB |
542 | connectorId: transactionConnectorId, |
543 | status: OCPP16ChargePointStatus.UNAVAILABLE, | |
544 | errorCode: OCPP16ChargePointErrorCode.NO_ERROR, | |
545 | }); | |
08f130a0 | 546 | chargingStation.getConnectorStatus(transactionConnectorId).status = |
e7aeea18 | 547 | OCPP16ChargePointStatus.UNAVAILABLE; |
c0560973 | 548 | } else { |
08f130a0 | 549 | await chargingStation.ocppRequestService.requestHandler< |
ef6fa3fb JB |
550 | OCPP16BootNotificationRequest, |
551 | OCPP16BootNotificationResponse | |
08f130a0 | 552 | >(chargingStation, OCPP16RequestCommand.STATUS_NOTIFICATION, { |
ef6fa3fb JB |
553 | connectorId: transactionConnectorId, |
554 | status: OCPP16ChargePointStatus.AVAILABLE, | |
555 | errorCode: OCPP16ChargePointErrorCode.NO_ERROR, | |
556 | }); | |
08f130a0 | 557 | chargingStation.getConnectorStatus(transactionConnectorId).status = |
e7aeea18 | 558 | OCPP16ChargePointStatus.AVAILABLE; |
c0560973 | 559 | } |
08f130a0 | 560 | if (chargingStation.stationInfo.powerSharedByConnectors) { |
fa7bccf4 | 561 | chargingStation.powerDivider--; |
c0560973 | 562 | } |
e7aeea18 | 563 | logger.info( |
08f130a0 | 564 | chargingStation.logPrefix() + |
e7aeea18 JB |
565 | ' Transaction ' + |
566 | requestPayload.transactionId.toString() + | |
567 | ' STOPPED on ' + | |
08f130a0 | 568 | chargingStation.stationInfo.chargingStationId + |
e7aeea18 JB |
569 | '#' + |
570 | transactionConnectorId.toString() | |
571 | ); | |
08f130a0 | 572 | chargingStation.resetConnectorStatus(transactionConnectorId); |
c0560973 | 573 | } else { |
e7aeea18 | 574 | logger.warn( |
08f130a0 | 575 | chargingStation.logPrefix() + |
e7aeea18 JB |
576 | ' Stopping transaction id ' + |
577 | requestPayload.transactionId.toString() + | |
e8e865ea JB |
578 | " REJECTED with status '" + |
579 | payload.idTagInfo?.status + | |
580 | "'" | |
e7aeea18 | 581 | ); |
c0560973 JB |
582 | } |
583 | } | |
584 | ||
844e496b JB |
585 | private handleResponseStatusNotification( |
586 | chargingStation: ChargingStation, | |
587 | payload: OCPP16StatusNotificationResponse | |
588 | ): void { | |
589 | this.validateResponsePayload( | |
590 | chargingStation, | |
591 | OCPP16RequestCommand.STATUS_NOTIFICATION, | |
592 | this.statusNotificationResponseJsonSchema, | |
593 | payload | |
594 | ); | |
595 | } | |
c0560973 | 596 | |
844e496b JB |
597 | private handleResponseMeterValues( |
598 | chargingStation: ChargingStation, | |
599 | payload: OCPP16MeterValuesResponse | |
600 | ): void { | |
601 | this.validateResponsePayload( | |
602 | chargingStation, | |
603 | OCPP16RequestCommand.METER_VALUES, | |
604 | this.meterValuesResponseJsonSchema, | |
605 | payload | |
606 | ); | |
607 | } | |
c0560973 | 608 | } |