Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
04b1261c JB |
3 | import { parentPort } from 'node:worker_threads'; |
4 | ||
6c1761d4 | 5 | import type { JSONSchemaType } from 'ajv'; |
844e496b | 6 | |
4c3c0d59 | 7 | import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; |
04b1261c JB |
8 | import { |
9 | type ChargingStation, | |
10 | ChargingStationConfigurationUtils, | |
fba11dc6 | 11 | resetConnectorStatus, |
04b1261c | 12 | } from '../../../charging-station'; |
268a74bb | 13 | import { OCPPError } from '../../../exception'; |
ef6fa3fb | 14 | import { |
268a74bb JB |
15 | type ChangeAvailabilityResponse, |
16 | type ChangeConfigurationResponse, | |
17 | type ClearChargingProfileResponse, | |
18 | ErrorType, | |
19 | type GenericResponse, | |
20 | type GetConfigurationResponse, | |
21 | type GetDiagnosticsResponse, | |
22 | type JsonObject, | |
23 | type JsonType, | |
8114d10e | 24 | OCPP16AuthorizationStatus, |
27782dbc JB |
25 | type OCPP16AuthorizeRequest, |
26 | type OCPP16AuthorizeResponse, | |
268a74bb | 27 | type OCPP16BootNotificationResponse, |
268a74bb JB |
28 | OCPP16ChargePointStatus, |
29 | type OCPP16DataTransferResponse, | |
30 | type OCPP16DiagnosticsStatusNotificationResponse, | |
31 | type OCPP16FirmwareStatusNotificationResponse, | |
41189456 | 32 | type OCPP16GetCompositeScheduleResponse, |
268a74bb JB |
33 | type OCPP16HeartbeatResponse, |
34 | OCPP16IncomingRequestCommand, | |
35 | type OCPP16MeterValuesRequest, | |
36 | type OCPP16MeterValuesResponse, | |
37 | OCPP16RequestCommand, | |
66dd3447 | 38 | type OCPP16ReserveNowResponse, |
268a74bb | 39 | OCPP16StandardParametersKey, |
27782dbc JB |
40 | type OCPP16StartTransactionRequest, |
41 | type OCPP16StartTransactionResponse, | |
268a74bb | 42 | type OCPP16StatusNotificationResponse, |
27782dbc JB |
43 | type OCPP16StopTransactionRequest, |
44 | type OCPP16StopTransactionResponse, | |
268a74bb JB |
45 | type OCPP16TriggerMessageResponse, |
46 | type OCPP16UpdateFirmwareResponse, | |
47 | OCPPVersion, | |
02887891 JB |
48 | RegistrationStatusEnumType, |
49 | type ResponseHandler, | |
268a74bb JB |
50 | type SetChargingProfileResponse, |
51 | type UnlockConnectorResponse, | |
52 | } from '../../../types'; | |
9bf0ef23 JB |
53 | import { |
54 | Constants, | |
55 | buildUpdatedMessage, | |
56 | convertToInt, | |
57 | isNullOrUndefined, | |
58 | logger, | |
59 | } from '../../../utils'; | |
4c3c0d59 | 60 | import { OCPPResponseService } from '../OCPPResponseService'; |
c0560973 | 61 | |
909dcf2d JB |
62 | const moduleName = 'OCPP16ResponseService'; |
63 | ||
268a74bb | 64 | export class OCPP16ResponseService extends OCPPResponseService { |
b3fc3ff5 JB |
65 | public jsonIncomingRequestResponseSchemas: Map< |
66 | OCPP16IncomingRequestCommand, | |
67 | JSONSchemaType<JsonObject> | |
68 | >; | |
69 | ||
58144adb | 70 | private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>; |
b52c969d | 71 | private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>; |
58144adb | 72 | |
08f130a0 | 73 | public constructor() { |
b768993d JB |
74 | // if (new.target?.name === moduleName) { |
75 | // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`); | |
76 | // } | |
d270cc87 | 77 | super(OCPPVersion.VERSION_16); |
58144adb JB |
78 | this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([ |
79 | [OCPP16RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)], | |
b52c969d | 80 | [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)], |
58144adb JB |
81 | [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this)], |
82 | [OCPP16RequestCommand.START_TRANSACTION, this.handleResponseStartTransaction.bind(this)], | |
83 | [OCPP16RequestCommand.STOP_TRANSACTION, this.handleResponseStopTransaction.bind(this)], | |
b52c969d JB |
84 | [OCPP16RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)], |
85 | [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this)], | |
86 | [OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)], | |
91a7d3ea | 87 | [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this)], |
c9a4f9ea | 88 | [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)], |
b52c969d JB |
89 | ]); |
90 | this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([ | |
91 | [ | |
92 | OCPP16RequestCommand.BOOT_NOTIFICATION, | |
130783a7 | 93 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>( |
51022aa0 | 94 | 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json', |
1b271a54 | 95 | moduleName, |
5edd8ba0 | 96 | 'constructor', |
e9a4164c | 97 | ), |
b52c969d JB |
98 | ], |
99 | [ | |
100 | OCPP16RequestCommand.HEARTBEAT, | |
130783a7 | 101 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>( |
51022aa0 | 102 | 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json', |
1b271a54 | 103 | moduleName, |
5edd8ba0 | 104 | 'constructor', |
e9a4164c | 105 | ), |
b52c969d JB |
106 | ], |
107 | [ | |
108 | OCPP16RequestCommand.AUTHORIZE, | |
130783a7 | 109 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>( |
51022aa0 | 110 | 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json', |
1b271a54 | 111 | moduleName, |
5edd8ba0 | 112 | 'constructor', |
e9a4164c | 113 | ), |
b52c969d JB |
114 | ], |
115 | [ | |
116 | OCPP16RequestCommand.START_TRANSACTION, | |
130783a7 | 117 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>( |
51022aa0 | 118 | 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json', |
1b271a54 | 119 | moduleName, |
5edd8ba0 | 120 | 'constructor', |
e9a4164c | 121 | ), |
b52c969d JB |
122 | ], |
123 | [ | |
124 | OCPP16RequestCommand.STOP_TRANSACTION, | |
130783a7 | 125 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>( |
51022aa0 | 126 | 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json', |
1b271a54 | 127 | moduleName, |
5edd8ba0 | 128 | 'constructor', |
e9a4164c | 129 | ), |
b52c969d JB |
130 | ], |
131 | [ | |
132 | OCPP16RequestCommand.STATUS_NOTIFICATION, | |
130783a7 | 133 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>( |
51022aa0 | 134 | 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json', |
1b271a54 | 135 | moduleName, |
5edd8ba0 | 136 | 'constructor', |
e9a4164c | 137 | ), |
b52c969d JB |
138 | ], |
139 | [ | |
140 | OCPP16RequestCommand.METER_VALUES, | |
130783a7 | 141 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>( |
51022aa0 | 142 | 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json', |
1b271a54 | 143 | moduleName, |
5edd8ba0 | 144 | 'constructor', |
e9a4164c | 145 | ), |
b52c969d JB |
146 | ], |
147 | [ | |
148 | OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, | |
130783a7 | 149 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>( |
51022aa0 | 150 | 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json', |
1b271a54 | 151 | moduleName, |
5edd8ba0 | 152 | 'constructor', |
e9a4164c | 153 | ), |
b52c969d | 154 | ], |
91a7d3ea JB |
155 | [ |
156 | OCPP16RequestCommand.DATA_TRANSFER, | |
130783a7 | 157 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>( |
51022aa0 | 158 | 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json', |
1b271a54 | 159 | moduleName, |
5edd8ba0 | 160 | 'constructor', |
e9a4164c | 161 | ), |
91a7d3ea | 162 | ], |
c9a4f9ea JB |
163 | [ |
164 | OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, | |
130783a7 | 165 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>( |
51022aa0 | 166 | 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json', |
1b271a54 | 167 | moduleName, |
5edd8ba0 | 168 | 'constructor', |
e9a4164c | 169 | ), |
c9a4f9ea | 170 | ], |
58144adb | 171 | ]); |
02887891 JB |
172 | this.jsonIncomingRequestResponseSchemas = new Map([ |
173 | [ | |
174 | OCPP16IncomingRequestCommand.RESET, | |
130783a7 | 175 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
51022aa0 | 176 | 'assets/json-schemas/ocpp/1.6/ResetResponse.json', |
1b271a54 | 177 | moduleName, |
5edd8ba0 | 178 | 'constructor', |
e9a4164c | 179 | ), |
02887891 JB |
180 | ], |
181 | [ | |
182 | OCPP16IncomingRequestCommand.CLEAR_CACHE, | |
130783a7 | 183 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
51022aa0 | 184 | 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json', |
1b271a54 | 185 | moduleName, |
5edd8ba0 | 186 | 'constructor', |
e9a4164c | 187 | ), |
02887891 JB |
188 | ], |
189 | [ | |
190 | OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, | |
130783a7 | 191 | OCPP16ServiceUtils.parseJsonSchemaFile<ChangeAvailabilityResponse>( |
51022aa0 | 192 | 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json', |
1b271a54 | 193 | moduleName, |
5edd8ba0 | 194 | 'constructor', |
e9a4164c | 195 | ), |
02887891 JB |
196 | ], |
197 | [ | |
198 | OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, | |
130783a7 | 199 | OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>( |
51022aa0 | 200 | 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json', |
1b271a54 | 201 | moduleName, |
5edd8ba0 | 202 | 'constructor', |
e9a4164c | 203 | ), |
02887891 JB |
204 | ], |
205 | [ | |
206 | OCPP16IncomingRequestCommand.GET_CONFIGURATION, | |
130783a7 | 207 | OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>( |
51022aa0 | 208 | 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json', |
1b271a54 | 209 | moduleName, |
5edd8ba0 | 210 | 'constructor', |
e9a4164c | 211 | ), |
02887891 JB |
212 | ], |
213 | [ | |
214 | OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, | |
130783a7 | 215 | OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>( |
51022aa0 | 216 | 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json', |
1b271a54 | 217 | moduleName, |
5edd8ba0 | 218 | 'constructor', |
e9a4164c | 219 | ), |
02887891 | 220 | ], |
41189456 JB |
221 | [ |
222 | OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE, | |
223 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>( | |
51022aa0 | 224 | 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json', |
41189456 | 225 | moduleName, |
5edd8ba0 | 226 | 'constructor', |
41189456 JB |
227 | ), |
228 | ], | |
02887891 JB |
229 | [ |
230 | OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, | |
130783a7 | 231 | OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>( |
51022aa0 | 232 | 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json', |
1b271a54 | 233 | moduleName, |
5edd8ba0 | 234 | 'constructor', |
e9a4164c | 235 | ), |
02887891 JB |
236 | ], |
237 | [ | |
238 | OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, | |
130783a7 | 239 | OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileResponse>( |
51022aa0 | 240 | 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json', |
1b271a54 | 241 | moduleName, |
5edd8ba0 | 242 | 'constructor', |
e9a4164c | 243 | ), |
02887891 JB |
244 | ], |
245 | [ | |
246 | OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, | |
130783a7 | 247 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
51022aa0 | 248 | 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json', |
1b271a54 | 249 | moduleName, |
5edd8ba0 | 250 | 'constructor', |
e9a4164c | 251 | ), |
02887891 JB |
252 | ], |
253 | [ | |
254 | OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, | |
130783a7 | 255 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
51022aa0 | 256 | 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json', |
1b271a54 | 257 | moduleName, |
5edd8ba0 | 258 | 'constructor', |
e9a4164c | 259 | ), |
02887891 JB |
260 | ], |
261 | [ | |
262 | OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, | |
130783a7 | 263 | OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>( |
51022aa0 | 264 | 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json', |
1b271a54 | 265 | moduleName, |
5edd8ba0 | 266 | 'constructor', |
e9a4164c | 267 | ), |
02887891 JB |
268 | ], |
269 | [ | |
270 | OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, | |
130783a7 | 271 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>( |
51022aa0 | 272 | 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json', |
1b271a54 | 273 | moduleName, |
5edd8ba0 | 274 | 'constructor', |
e9a4164c | 275 | ), |
02887891 JB |
276 | ], |
277 | [ | |
278 | OCPP16IncomingRequestCommand.DATA_TRANSFER, | |
130783a7 | 279 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>( |
51022aa0 | 280 | 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json', |
1b271a54 | 281 | moduleName, |
5edd8ba0 | 282 | 'constructor', |
e9a4164c | 283 | ), |
02887891 JB |
284 | ], |
285 | [ | |
286 | OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, | |
130783a7 | 287 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>( |
51022aa0 | 288 | 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json', |
1b271a54 | 289 | moduleName, |
5edd8ba0 | 290 | 'constructor', |
e9a4164c | 291 | ), |
02887891 | 292 | ], |
28fe900f JB |
293 | [ |
294 | OCPP16IncomingRequestCommand.RESERVE_NOW, | |
295 | OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>( | |
296 | 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json', | |
297 | moduleName, | |
5edd8ba0 | 298 | 'constructor', |
28fe900f JB |
299 | ), |
300 | ], | |
301 | [ | |
302 | OCPP16IncomingRequestCommand.CANCEL_RESERVATION, | |
b1f1b0f6 | 303 | OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>( |
28fe900f JB |
304 | 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json', |
305 | moduleName, | |
5edd8ba0 | 306 | 'constructor', |
28fe900f JB |
307 | ), |
308 | ], | |
02887891 | 309 | ]); |
31f59c6d JB |
310 | this.validatePayload = this.validatePayload.bind(this) as ( |
311 | chargingStation: ChargingStation, | |
312 | commandName: OCPP16RequestCommand, | |
5edd8ba0 | 313 | payload: JsonType, |
31f59c6d | 314 | ) => boolean; |
58144adb JB |
315 | } |
316 | ||
9429aa42 | 317 | public async responseHandler<ReqType extends JsonType, ResType extends JsonType>( |
08f130a0 | 318 | chargingStation: ChargingStation, |
e7aeea18 | 319 | commandName: OCPP16RequestCommand, |
9429aa42 JB |
320 | payload: ResType, |
321 | requestPayload: ReqType, | |
e7aeea18 | 322 | ): Promise<void> { |
ed6cfcff JB |
323 | if ( |
324 | chargingStation.isRegistered() === true || | |
325 | commandName === OCPP16RequestCommand.BOOT_NOTIFICATION | |
326 | ) { | |
65554cc3 | 327 | if ( |
ed6cfcff JB |
328 | this.responseHandlers.has(commandName) === true && |
329 | OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true | |
65554cc3 | 330 | ) { |
124f3553 | 331 | try { |
9c5c4195 | 332 | this.validatePayload(chargingStation, commandName, payload); |
e1d9a0f4 | 333 | await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload); |
124f3553 | 334 | } catch (error) { |
6c8f5d90 JB |
335 | logger.error( |
336 | `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`, | |
5edd8ba0 | 337 | error, |
6c8f5d90 | 338 | ); |
124f3553 JB |
339 | throw error; |
340 | } | |
341 | } else { | |
342 | // Throw exception | |
e7aeea18 JB |
343 | throw new OCPPError( |
344 | ErrorType.NOT_IMPLEMENTED, | |
6c8f5d90 | 345 | `${commandName} is not implemented to handle response PDU ${JSON.stringify( |
e7aeea18 JB |
346 | payload, |
347 | null, | |
5edd8ba0 | 348 | 2, |
e7aeea18 | 349 | )}`, |
7369e417 | 350 | commandName, |
5edd8ba0 | 351 | payload, |
e7aeea18 | 352 | ); |
887fef76 | 353 | } |
c0560973 | 354 | } else { |
e7aeea18 JB |
355 | throw new OCPPError( |
356 | ErrorType.SECURITY_ERROR, | |
6c8f5d90 | 357 | `${commandName} cannot be issued to handle response PDU ${JSON.stringify( |
e7aeea18 JB |
358 | payload, |
359 | null, | |
5edd8ba0 | 360 | 2, |
439fc71b | 361 | )} while the charging station is not registered on the central server.`, |
7369e417 | 362 | commandName, |
5edd8ba0 | 363 | payload, |
e7aeea18 | 364 | ); |
c0560973 JB |
365 | } |
366 | } | |
367 | ||
9c5c4195 JB |
368 | private validatePayload( |
369 | chargingStation: ChargingStation, | |
370 | commandName: OCPP16RequestCommand, | |
5edd8ba0 | 371 | payload: JsonType, |
9c5c4195 | 372 | ): boolean { |
45988780 | 373 | if (this.jsonSchemas.has(commandName) === true) { |
9c5c4195 JB |
374 | return this.validateResponsePayload( |
375 | chargingStation, | |
376 | commandName, | |
e1d9a0f4 | 377 | this.jsonSchemas.get(commandName)!, |
5edd8ba0 | 378 | payload, |
9c5c4195 JB |
379 | ); |
380 | } | |
381 | logger.warn( | |
5edd8ba0 | 382 | `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`, |
9c5c4195 JB |
383 | ); |
384 | return false; | |
385 | } | |
386 | ||
08f130a0 JB |
387 | private handleResponseBootNotification( |
388 | chargingStation: ChargingStation, | |
5edd8ba0 | 389 | payload: OCPP16BootNotificationResponse, |
08f130a0 | 390 | ): void { |
d270cc87 | 391 | if (payload.status === RegistrationStatusEnumType.ACCEPTED) { |
17ac262c JB |
392 | ChargingStationConfigurationUtils.addConfigurationKey( |
393 | chargingStation, | |
f0f65a62 | 394 | OCPP16StandardParametersKey.HeartbeatInterval, |
a95873d8 | 395 | payload.interval.toString(), |
abe9e9dd | 396 | {}, |
5edd8ba0 | 397 | { overwrite: true, save: true }, |
e7aeea18 | 398 | ); |
17ac262c JB |
399 | ChargingStationConfigurationUtils.addConfigurationKey( |
400 | chargingStation, | |
f0f65a62 | 401 | OCPP16StandardParametersKey.HeartBeatInterval, |
e7aeea18 | 402 | payload.interval.toString(), |
00db15b8 | 403 | { visible: false }, |
5edd8ba0 | 404 | { overwrite: true, save: true }, |
e7aeea18 | 405 | ); |
8f953431 | 406 | OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval); |
672fed6e | 407 | } |
d270cc87 | 408 | if (Object.values(RegistrationStatusEnumType).includes(payload.status)) { |
08f130a0 | 409 | const logMsg = `${chargingStation.logPrefix()} Charging station in '${ |
e7aeea18 JB |
410 | payload.status |
411 | }' state on the central server`; | |
d270cc87 | 412 | payload.status === RegistrationStatusEnumType.REJECTED |
e7aeea18 JB |
413 | ? logger.warn(logMsg) |
414 | : logger.info(logMsg); | |
c0560973 | 415 | } else { |
e7aeea18 | 416 | logger.error( |
44eb6026 | 417 | `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`, |
5edd8ba0 | 418 | payload, |
e7aeea18 | 419 | ); |
c0560973 JB |
420 | } |
421 | } | |
422 | ||
e7aeea18 | 423 | private handleResponseAuthorize( |
08f130a0 | 424 | chargingStation: ChargingStation, |
e7aeea18 | 425 | payload: OCPP16AuthorizeResponse, |
5edd8ba0 | 426 | requestPayload: OCPP16AuthorizeRequest, |
e7aeea18 | 427 | ): void { |
e1d9a0f4 | 428 | let authorizeConnectorId: number | undefined; |
ded57f02 JB |
429 | if (chargingStation.hasEvses) { |
430 | for (const [evseId, evseStatus] of chargingStation.evses) { | |
431 | if (evseId > 0) { | |
432 | for (const [connectorId, connectorStatus] of evseStatus.connectors) { | |
433 | if (connectorStatus?.authorizeIdTag === requestPayload.idTag) { | |
434 | authorizeConnectorId = connectorId; | |
435 | break; | |
436 | } | |
437 | } | |
438 | } | |
439 | } | |
440 | } else { | |
441 | for (const connectorId of chargingStation.connectors.keys()) { | |
442 | if ( | |
443 | connectorId > 0 && | |
444 | chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag | |
445 | ) { | |
446 | authorizeConnectorId = connectorId; | |
447 | break; | |
448 | } | |
58144adb JB |
449 | } |
450 | } | |
9bf0ef23 | 451 | const authorizeConnectorIdDefined = !isNullOrUndefined(authorizeConnectorId); |
58144adb | 452 | if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) { |
44eb6026 | 453 | authorizeConnectorIdDefined && |
e1d9a0f4 | 454 | (chargingStation.getConnectorStatus(authorizeConnectorId!)!.idTagAuthorized = true); |
e7aeea18 | 455 | logger.debug( |
2585c6e9 | 456 | `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' accepted${ |
54ebb82c | 457 | authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : '' |
5edd8ba0 | 458 | }`, |
e7aeea18 | 459 | ); |
58144adb | 460 | } else { |
44eb6026 | 461 | if (authorizeConnectorIdDefined) { |
e1d9a0f4 JB |
462 | chargingStation.getConnectorStatus(authorizeConnectorId!)!.idTagAuthorized = false; |
463 | delete chargingStation.getConnectorStatus(authorizeConnectorId!)?.authorizeIdTag; | |
1984f194 | 464 | } |
e7aeea18 | 465 | logger.debug( |
2585c6e9 | 466 | `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${ |
e7aeea18 | 467 | payload.idTagInfo.status |
5edd8ba0 | 468 | }'${authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''}`, |
e7aeea18 | 469 | ); |
58144adb JB |
470 | } |
471 | } | |
472 | ||
e7aeea18 | 473 | private async handleResponseStartTransaction( |
08f130a0 | 474 | chargingStation: ChargingStation, |
e7aeea18 | 475 | payload: OCPP16StartTransactionResponse, |
5edd8ba0 | 476 | requestPayload: OCPP16StartTransactionRequest, |
e7aeea18 | 477 | ): Promise<void> { |
649287f8 JB |
478 | const transactionConnectorId = requestPayload.connectorId; |
479 | if ( | |
480 | transactionConnectorId === 0 || | |
481 | chargingStation.hasConnector(transactionConnectorId) === false | |
482 | ) { | |
e7aeea18 | 483 | logger.error( |
5edd8ba0 | 484 | `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${transactionConnectorId.toString()}`, |
e7aeea18 | 485 | ); |
c0560973 JB |
486 | return; |
487 | } | |
e7aeea18 | 488 | if ( |
649287f8 JB |
489 | chargingStation.getConnectorStatus(transactionConnectorId)?.transactionRemoteStarted === |
490 | true && | |
3a13fc92 JB |
491 | chargingStation.getAuthorizeRemoteTxRequests() === true && |
492 | chargingStation.getLocalAuthListEnabled() === true && | |
f911a4af | 493 | chargingStation.hasIdTags() && |
649287f8 | 494 | chargingStation.getConnectorStatus(transactionConnectorId)?.idTagLocalAuthorized === false |
e7aeea18 JB |
495 | ) { |
496 | logger.error( | |
5edd8ba0 JB |
497 | `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${chargingStation.getConnectorStatus( |
498 | transactionConnectorId, | |
499 | )?.localAuthorizeIdTag} on connector id ${transactionConnectorId.toString()}`, | |
e7aeea18 | 500 | ); |
649287f8 | 501 | await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId); |
a2653482 JB |
502 | return; |
503 | } | |
e7aeea18 | 504 | if ( |
649287f8 JB |
505 | chargingStation.getConnectorStatus(transactionConnectorId)?.transactionRemoteStarted === |
506 | true && | |
3a13fc92 JB |
507 | chargingStation.getAuthorizeRemoteTxRequests() === true && |
508 | chargingStation.getMustAuthorizeAtRemoteStart() === true && | |
649287f8 JB |
509 | chargingStation.getConnectorStatus(transactionConnectorId)?.idTagLocalAuthorized === false && |
510 | chargingStation.getConnectorStatus(transactionConnectorId)?.idTagAuthorized === false | |
e7aeea18 JB |
511 | ) { |
512 | logger.error( | |
5edd8ba0 JB |
513 | `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${chargingStation.getConnectorStatus( |
514 | transactionConnectorId, | |
515 | )?.authorizeIdTag} on connector id ${transactionConnectorId.toString()}`, | |
e7aeea18 | 516 | ); |
649287f8 | 517 | await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId); |
a2653482 JB |
518 | return; |
519 | } | |
e7aeea18 | 520 | if ( |
649287f8 JB |
521 | chargingStation.getConnectorStatus(transactionConnectorId)?.idTagAuthorized && |
522 | chargingStation.getConnectorStatus(transactionConnectorId)?.authorizeIdTag !== | |
523 | requestPayload.idTag | |
e7aeea18 JB |
524 | ) { |
525 | logger.error( | |
44eb6026 JB |
526 | `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${ |
527 | requestPayload.idTag | |
5edd8ba0 JB |
528 | } different from the authorize request one ${chargingStation.getConnectorStatus( |
529 | transactionConnectorId, | |
530 | )?.authorizeIdTag} on connector id ${transactionConnectorId.toString()}`, | |
e7aeea18 | 531 | ); |
649287f8 | 532 | await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId); |
a2653482 JB |
533 | return; |
534 | } | |
e7aeea18 | 535 | if ( |
649287f8 JB |
536 | chargingStation.getConnectorStatus(transactionConnectorId)?.idTagLocalAuthorized && |
537 | chargingStation.getConnectorStatus(transactionConnectorId)?.localAuthorizeIdTag !== | |
538 | requestPayload.idTag | |
e7aeea18 JB |
539 | ) { |
540 | logger.error( | |
44eb6026 JB |
541 | `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${ |
542 | requestPayload.idTag | |
5edd8ba0 JB |
543 | } different from the local authorized one ${chargingStation.getConnectorStatus( |
544 | transactionConnectorId, | |
545 | )?.localAuthorizeIdTag} on connector id ${transactionConnectorId.toString()}`, | |
e7aeea18 | 546 | ); |
649287f8 | 547 | await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId); |
163547b1 JB |
548 | return; |
549 | } | |
649287f8 JB |
550 | if (chargingStation.getConnectorStatus(transactionConnectorId)?.transactionStarted === true) { |
551 | logger.error( | |
552 | `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${transactionConnectorId.toString()}:`, | |
5edd8ba0 | 553 | chargingStation.getConnectorStatus(transactionConnectorId), |
e7aeea18 | 554 | ); |
c0560973 JB |
555 | return; |
556 | } | |
649287f8 JB |
557 | if (chargingStation.hasEvses) { |
558 | for (const [evseId, evseStatus] of chargingStation.evses) { | |
559 | if (evseStatus.connectors.size > 1) { | |
560 | for (const [connectorId, connectorStatus] of evseStatus.connectors) { | |
561 | if ( | |
562 | transactionConnectorId !== connectorId && | |
563 | connectorStatus?.transactionStarted === true | |
564 | ) { | |
565 | logger.error( | |
566 | `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId.toString()}:`, | |
5edd8ba0 | 567 | evseStatus, |
649287f8 JB |
568 | ); |
569 | await this.resetConnectorOnStartTransactionError( | |
570 | chargingStation, | |
5edd8ba0 | 571 | transactionConnectorId, |
649287f8 JB |
572 | ); |
573 | return; | |
574 | } | |
575 | } | |
576 | } | |
577 | } | |
578 | } | |
e7aeea18 | 579 | if ( |
649287f8 | 580 | chargingStation.getConnectorStatus(transactionConnectorId)?.status !== |
721646e9 | 581 | OCPP16ChargePointStatus.Available && |
649287f8 JB |
582 | chargingStation.getConnectorStatus(transactionConnectorId)?.status !== |
583 | OCPP16ChargePointStatus.Preparing | |
e7aeea18 JB |
584 | ) { |
585 | logger.error( | |
5edd8ba0 JB |
586 | `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${transactionConnectorId.toString()} with status ${chargingStation.getConnectorStatus( |
587 | transactionConnectorId, | |
588 | )?.status}`, | |
e7aeea18 | 589 | ); |
290d006c JB |
590 | return; |
591 | } | |
54ebb82c JB |
592 | if (!Number.isInteger(payload.transactionId)) { |
593 | logger.warn( | |
649287f8 | 594 | `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${transactionConnectorId.toString()} with a non integer transaction id ${ |
54ebb82c | 595 | payload.transactionId |
5edd8ba0 | 596 | }, converting to integer`, |
54ebb82c | 597 | ); |
9bf0ef23 | 598 | payload.transactionId = convertToInt(payload.transactionId); |
54ebb82c | 599 | } |
c0560973 | 600 | |
f0c6ed89 | 601 | if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) { |
e1d9a0f4 JB |
602 | chargingStation.getConnectorStatus(transactionConnectorId)!.transactionStarted = true; |
603 | chargingStation.getConnectorStatus(transactionConnectorId)!.transactionId = | |
649287f8 | 604 | payload.transactionId; |
e1d9a0f4 | 605 | chargingStation.getConnectorStatus(transactionConnectorId)!.transactionIdTag = |
649287f8 | 606 | requestPayload.idTag; |
08f130a0 | 607 | chargingStation.getConnectorStatus( |
5edd8ba0 | 608 | transactionConnectorId, |
e1d9a0f4 JB |
609 | )!.transactionEnergyActiveImportRegisterValue = 0; |
610 | chargingStation.getConnectorStatus(transactionConnectorId)!.transactionBeginMeterValue = | |
e7aeea18 | 611 | OCPP16ServiceUtils.buildTransactionBeginMeterValue( |
08f130a0 | 612 | chargingStation, |
649287f8 | 613 | transactionConnectorId, |
5edd8ba0 | 614 | requestPayload.meterStart, |
e7aeea18 | 615 | ); |
08f130a0 JB |
616 | chargingStation.getBeginEndMeterValues() && |
617 | (await chargingStation.ocppRequestService.requestHandler< | |
ef6fa3fb JB |
618 | OCPP16MeterValuesRequest, |
619 | OCPP16MeterValuesResponse | |
08f130a0 | 620 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { |
649287f8 | 621 | connectorId: transactionConnectorId, |
ef6fa3fb | 622 | transactionId: payload.transactionId, |
649287f8 | 623 | meterValue: [ |
e1d9a0f4 | 624 | chargingStation.getConnectorStatus(transactionConnectorId)!.transactionBeginMeterValue, |
649287f8 | 625 | ], |
e1d9a0f4 | 626 | } as OCPP16MeterValuesRequest)); |
4ecff7ce JB |
627 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
628 | chargingStation, | |
649287f8 | 629 | transactionConnectorId, |
5edd8ba0 | 630 | OCPP16ChargePointStatus.Charging, |
4ecff7ce | 631 | ); |
e7aeea18 | 632 | logger.info( |
54ebb82c | 633 | `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId.toString()} STARTED on ${ |
44eb6026 | 634 | chargingStation.stationInfo.chargingStationId |
5edd8ba0 | 635 | }#${transactionConnectorId.toString()} for idTag '${requestPayload.idTag}'`, |
e7aeea18 | 636 | ); |
08f130a0 | 637 | if (chargingStation.stationInfo.powerSharedByConnectors) { |
1fe0632a | 638 | ++chargingStation.powerDivider; |
c0560973 | 639 | } |
17ac262c JB |
640 | const configuredMeterValueSampleInterval = |
641 | ChargingStationConfigurationUtils.getConfigurationKey( | |
642 | chargingStation, | |
5edd8ba0 | 643 | OCPP16StandardParametersKey.MeterValueSampleInterval, |
17ac262c | 644 | ); |
08f130a0 | 645 | chargingStation.startMeterValues( |
649287f8 | 646 | transactionConnectorId, |
e7aeea18 | 647 | configuredMeterValueSampleInterval |
9bf0ef23 | 648 | ? convertToInt(configuredMeterValueSampleInterval.value) * 1000 |
5edd8ba0 | 649 | : Constants.DEFAULT_METER_VALUES_INTERVAL, |
e7aeea18 | 650 | ); |
c0560973 | 651 | } else { |
e7aeea18 | 652 | logger.warn( |
5edd8ba0 JB |
653 | `${chargingStation.logPrefix()} Starting transaction with id ${payload.transactionId.toString()} REJECTED with status '${payload |
654 | .idTagInfo?.status}', idTag '${requestPayload.idTag}'`, | |
e7aeea18 | 655 | ); |
649287f8 | 656 | await this.resetConnectorOnStartTransactionError(chargingStation, transactionConnectorId); |
a2653482 JB |
657 | } |
658 | } | |
659 | ||
08f130a0 JB |
660 | private async resetConnectorOnStartTransactionError( |
661 | chargingStation: ChargingStation, | |
5edd8ba0 | 662 | connectorId: number, |
08f130a0 | 663 | ): Promise<void> { |
e1d9a0f4 | 664 | resetConnectorStatus(chargingStation.getConnectorStatus(connectorId)!); |
04b1261c | 665 | chargingStation.stopMeterValues(connectorId); |
c8faabc8 | 666 | parentPort?.postMessage(buildUpdatedMessage(chargingStation)); |
e7aeea18 | 667 | if ( |
721646e9 | 668 | chargingStation.getConnectorStatus(connectorId)?.status !== OCPP16ChargePointStatus.Available |
e7aeea18 | 669 | ) { |
4ecff7ce JB |
670 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
671 | chargingStation, | |
ef6fa3fb | 672 | connectorId, |
5edd8ba0 | 673 | OCPP16ChargePointStatus.Available, |
4ecff7ce | 674 | ); |
c0560973 JB |
675 | } |
676 | } | |
677 | ||
e7aeea18 | 678 | private async handleResponseStopTransaction( |
08f130a0 | 679 | chargingStation: ChargingStation, |
e7aeea18 | 680 | payload: OCPP16StopTransactionResponse, |
5edd8ba0 | 681 | requestPayload: OCPP16StopTransactionRequest, |
e7aeea18 | 682 | ): Promise<void> { |
08f130a0 | 683 | const transactionConnectorId = chargingStation.getConnectorIdByTransactionId( |
5edd8ba0 | 684 | requestPayload.transactionId, |
f479a792 | 685 | ); |
9bf0ef23 | 686 | if (isNullOrUndefined(transactionConnectorId)) { |
e7aeea18 | 687 | logger.error( |
5edd8ba0 | 688 | `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${requestPayload.transactionId.toString()}`, |
e7aeea18 | 689 | ); |
c0560973 JB |
690 | return; |
691 | } | |
2cace1a5 JB |
692 | chargingStation.getBeginEndMeterValues() === true && |
693 | chargingStation.getOcppStrictCompliance() === false && | |
694 | chargingStation.getOutOfOrderEndMeterValues() === true && | |
695 | (await chargingStation.ocppRequestService.requestHandler< | |
696 | OCPP16MeterValuesRequest, | |
697 | OCPP16MeterValuesResponse | |
698 | >(chargingStation, OCPP16RequestCommand.METER_VALUES, { | |
699 | connectorId: transactionConnectorId, | |
700 | transactionId: requestPayload.transactionId, | |
701 | meterValue: [ | |
702 | OCPP16ServiceUtils.buildTransactionEndMeterValue( | |
703 | chargingStation, | |
e1d9a0f4 | 704 | transactionConnectorId!, |
5edd8ba0 | 705 | requestPayload.meterStop, |
2cace1a5 JB |
706 | ), |
707 | ], | |
708 | })); | |
709 | if ( | |
710 | chargingStation.isChargingStationAvailable() === false || | |
e1d9a0f4 | 711 | chargingStation.isConnectorAvailable(transactionConnectorId!) === false |
2cace1a5 | 712 | ) { |
4ecff7ce JB |
713 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
714 | chargingStation, | |
e1d9a0f4 | 715 | transactionConnectorId!, |
5edd8ba0 | 716 | OCPP16ChargePointStatus.Unavailable, |
4ecff7ce | 717 | ); |
c0560973 | 718 | } else { |
4ecff7ce JB |
719 | await OCPP16ServiceUtils.sendAndSetConnectorStatus( |
720 | chargingStation, | |
e1d9a0f4 | 721 | transactionConnectorId!, |
5edd8ba0 | 722 | OCPP16ChargePointStatus.Available, |
4ecff7ce | 723 | ); |
2cace1a5 JB |
724 | } |
725 | if (chargingStation.stationInfo.powerSharedByConnectors) { | |
726 | chargingStation.powerDivider--; | |
727 | } | |
e1d9a0f4 JB |
728 | resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!); |
729 | chargingStation.stopMeterValues(transactionConnectorId!); | |
c8faabc8 | 730 | parentPort?.postMessage(buildUpdatedMessage(chargingStation)); |
54ebb82c | 731 | const logMsg = `${chargingStation.logPrefix()} Transaction with id ${requestPayload.transactionId.toString()} STOPPED on ${ |
8ca6874c | 732 | chargingStation.stationInfo.chargingStationId |
d812bdcb | 733 | }#${transactionConnectorId?.toString()} with status '${ |
8ca6874c JB |
734 | payload.idTagInfo?.status ?? 'undefined' |
735 | }'`; | |
2cace1a5 | 736 | if ( |
9bf0ef23 | 737 | isNullOrUndefined(payload.idTagInfo) || |
2cace1a5 JB |
738 | payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED |
739 | ) { | |
740 | logger.info(logMsg); | |
741 | } else { | |
742 | logger.warn(logMsg); | |
c0560973 JB |
743 | } |
744 | } | |
c0560973 | 745 | } |