Commit | Line | Data |
---|---|---|
edd13439 | 1 | // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved. |
c8eeb62b | 2 | |
b52c969d JB |
3 | import fs from 'fs'; |
4 | import path from 'path'; | |
5 | import { fileURLToPath } from 'url'; | |
6 | ||
6c1761d4 | 7 | import type { JSONSchemaType } from 'ajv'; |
b52c969d | 8 | |
8114d10e | 9 | import OCPPError from '../../../exception/OCPPError'; |
6c1761d4 JB |
10 | import type { JsonObject, JsonType } from '../../../types/JsonType'; |
11 | import type { OCPP16MeterValuesRequest } from '../../../types/ocpp/1.6/MeterValues'; | |
b52c969d | 12 | import { |
27782dbc JB |
13 | type DiagnosticsStatusNotificationRequest, |
14 | type OCPP16BootNotificationRequest, | |
15 | type OCPP16DataTransferRequest, | |
16 | type OCPP16HeartbeatRequest, | |
b52c969d | 17 | OCPP16RequestCommand, |
27782dbc | 18 | type OCPP16StatusNotificationRequest, |
b52c969d | 19 | } from '../../../types/ocpp/1.6/Requests'; |
6c1761d4 | 20 | import type { |
b52c969d JB |
21 | OCPP16AuthorizeRequest, |
22 | OCPP16StartTransactionRequest, | |
23 | OCPP16StopTransactionRequest, | |
24 | } from '../../../types/ocpp/1.6/Transaction'; | |
8114d10e | 25 | import { ErrorType } from '../../../types/ocpp/ErrorType'; |
d270cc87 | 26 | import { OCPPVersion } from '../../../types/ocpp/OCPPVersion'; |
6c1761d4 | 27 | import type { RequestParams } from '../../../types/ocpp/Requests'; |
8114d10e | 28 | import Constants from '../../../utils/Constants'; |
b52c969d | 29 | import logger from '../../../utils/Logger'; |
c0560973 | 30 | import Utils from '../../../utils/Utils'; |
8114d10e JB |
31 | import type ChargingStation from '../../ChargingStation'; |
32 | import OCPPRequestService from '../OCPPRequestService'; | |
33 | import type OCPPResponseService from '../OCPPResponseService'; | |
34 | import { OCPP16ServiceUtils } from './OCPP16ServiceUtils'; | |
c0560973 | 35 | |
909dcf2d JB |
36 | const moduleName = 'OCPP16RequestService'; |
37 | ||
c0560973 | 38 | export default class OCPP16RequestService extends OCPPRequestService { |
b52c969d JB |
39 | private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>; |
40 | ||
08f130a0 | 41 | public constructor(ocppResponseService: OCPPResponseService) { |
909dcf2d | 42 | if (new.target?.name === moduleName) { |
06127450 | 43 | throw new TypeError(`Cannot construct ${new.target?.name} instances directly`); |
9f2e3130 | 44 | } |
d270cc87 | 45 | super(OCPPVersion.VERSION_16, ocppResponseService); |
b52c969d JB |
46 | this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([ |
47 | [ | |
48 | OCPP16RequestCommand.AUTHORIZE, | |
49 | JSON.parse( | |
50 | fs.readFileSync( | |
51 | path.resolve( | |
52 | path.dirname(fileURLToPath(import.meta.url)), | |
53 | '../../../assets/json-schemas/ocpp/1.6/Authorize.json' | |
54 | ), | |
55 | 'utf8' | |
56 | ) | |
57 | ) as JSONSchemaType<OCPP16AuthorizeRequest>, | |
58 | ], | |
59 | [ | |
60 | OCPP16RequestCommand.BOOT_NOTIFICATION, | |
61 | JSON.parse( | |
62 | fs.readFileSync( | |
63 | path.resolve( | |
64 | path.dirname(fileURLToPath(import.meta.url)), | |
65 | '../../../assets/json-schemas/ocpp/1.6/BootNotification.json' | |
66 | ), | |
67 | 'utf8' | |
68 | ) | |
69 | ) as JSONSchemaType<OCPP16BootNotificationRequest>, | |
70 | ], | |
71 | [ | |
72 | OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, | |
73 | JSON.parse( | |
74 | fs.readFileSync( | |
75 | path.resolve( | |
76 | path.dirname(fileURLToPath(import.meta.url)), | |
77 | '../../../assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json' | |
78 | ), | |
79 | 'utf8' | |
80 | ) | |
81 | ) as JSONSchemaType<DiagnosticsStatusNotificationRequest>, | |
82 | ], | |
83 | [ | |
84 | OCPP16RequestCommand.HEARTBEAT, | |
85 | JSON.parse( | |
86 | fs.readFileSync( | |
87 | path.resolve( | |
88 | path.dirname(fileURLToPath(import.meta.url)), | |
89 | '../../../assets/json-schemas/ocpp/1.6/Heartbeat.json' | |
90 | ), | |
91 | 'utf8' | |
92 | ) | |
93 | ) as JSONSchemaType<OCPP16HeartbeatRequest>, | |
94 | ], | |
95 | [ | |
96 | OCPP16RequestCommand.METER_VALUES, | |
97 | JSON.parse( | |
98 | fs.readFileSync( | |
99 | path.resolve( | |
100 | path.dirname(fileURLToPath(import.meta.url)), | |
101 | '../../../assets/json-schemas/ocpp/1.6/MeterValues.json' | |
102 | ), | |
103 | 'utf8' | |
104 | ) | |
105 | ) as JSONSchemaType<OCPP16MeterValuesRequest>, | |
106 | ], | |
107 | [ | |
108 | OCPP16RequestCommand.STATUS_NOTIFICATION, | |
109 | JSON.parse( | |
110 | fs.readFileSync( | |
111 | path.resolve( | |
112 | path.dirname(fileURLToPath(import.meta.url)), | |
113 | '../../../assets/json-schemas/ocpp/1.6/StatusNotification.json' | |
114 | ), | |
115 | 'utf8' | |
116 | ) | |
117 | ) as JSONSchemaType<OCPP16StatusNotificationRequest>, | |
118 | ], | |
119 | [ | |
120 | OCPP16RequestCommand.START_TRANSACTION, | |
121 | JSON.parse( | |
122 | fs.readFileSync( | |
123 | path.resolve( | |
124 | path.dirname(fileURLToPath(import.meta.url)), | |
125 | '../../../assets/json-schemas/ocpp/1.6/StartTransaction.json' | |
126 | ), | |
127 | 'utf8' | |
128 | ) | |
129 | ) as JSONSchemaType<OCPP16StartTransactionRequest>, | |
130 | ], | |
131 | [ | |
132 | OCPP16RequestCommand.STOP_TRANSACTION, | |
133 | JSON.parse( | |
134 | fs.readFileSync( | |
135 | path.resolve( | |
136 | path.dirname(fileURLToPath(import.meta.url)), | |
137 | '../../../assets/json-schemas/ocpp/1.6/StopTransaction.json' | |
138 | ), | |
139 | 'utf8' | |
140 | ) | |
141 | ) as JSONSchemaType<OCPP16StopTransactionRequest>, | |
142 | ], | |
91a7d3ea JB |
143 | [ |
144 | OCPP16RequestCommand.DATA_TRANSFER, | |
145 | JSON.parse( | |
146 | fs.readFileSync( | |
147 | path.resolve( | |
148 | path.dirname(fileURLToPath(import.meta.url)), | |
149 | '../../../assets/json-schemas/ocpp/1.6/DataTransfer.json' | |
150 | ), | |
151 | 'utf8' | |
152 | ) | |
153 | ) as JSONSchemaType<OCPP16DataTransferRequest>, | |
154 | ], | |
b52c969d | 155 | ]); |
9952c548 JB |
156 | this.buildRequestPayload.bind(this); |
157 | this.validatePayload.bind(this); | |
9f2e3130 JB |
158 | } |
159 | ||
6c1761d4 | 160 | public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>( |
08f130a0 | 161 | chargingStation: ChargingStation, |
94a464f9 | 162 | commandName: OCPP16RequestCommand, |
5cc4b63b | 163 | commandParams?: JsonType, |
be9b0d50 | 164 | params?: RequestParams |
6c1761d4 | 165 | ): Promise<ResponseType> { |
ed6cfcff | 166 | if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true) { |
6c1761d4 | 167 | const requestPayload = this.buildRequestPayload<RequestType>( |
b52c969d JB |
168 | chargingStation, |
169 | commandName, | |
170 | commandParams | |
171 | ); | |
9c5c4195 | 172 | this.validatePayload(chargingStation, commandName, requestPayload); |
f22266fd | 173 | return (await this.sendMessage( |
08f130a0 | 174 | chargingStation, |
94a464f9 | 175 | Utils.generateUUID(), |
b52c969d | 176 | requestPayload, |
94a464f9 JB |
177 | commandName, |
178 | params | |
6c1761d4 | 179 | )) as unknown as ResponseType; |
94a464f9 | 180 | } |
e909d2a7 | 181 | // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). |
94a464f9 JB |
182 | throw new OCPPError( |
183 | ErrorType.NOT_SUPPORTED, | |
6c8f5d90 | 184 | `Unsupported OCPP command '${commandName}'`, |
94a464f9 | 185 | commandName, |
7369e417 | 186 | commandParams |
94a464f9 | 187 | ); |
c0560973 JB |
188 | } |
189 | ||
5cc4b63b | 190 | private buildRequestPayload<Request extends JsonType>( |
08f130a0 | 191 | chargingStation: ChargingStation, |
78085c42 | 192 | commandName: OCPP16RequestCommand, |
5cc4b63b | 193 | commandParams?: JsonType |
f22266fd | 194 | ): Request { |
68c993d5 | 195 | let connectorId: number; |
cf058664 | 196 | let energyActiveImportRegister: number; |
5cc4b63b | 197 | commandParams = commandParams as JsonObject; |
78085c42 JB |
198 | switch (commandName) { |
199 | case OCPP16RequestCommand.AUTHORIZE: | |
200 | return { | |
201 | ...(!Utils.isUndefined(commandParams?.idTag) | |
202 | ? { idTag: commandParams.idTag } | |
203 | : { idTag: Constants.DEFAULT_IDTAG }), | |
f22266fd | 204 | } as unknown as Request; |
78085c42 JB |
205 | case OCPP16RequestCommand.BOOT_NOTIFICATION: |
206 | return { | |
207 | chargePointModel: commandParams?.chargePointModel, | |
208 | chargePointVendor: commandParams?.chargePointVendor, | |
209 | ...(!Utils.isUndefined(commandParams?.chargeBoxSerialNumber) && { | |
210 | chargeBoxSerialNumber: commandParams.chargeBoxSerialNumber, | |
211 | }), | |
212 | ...(!Utils.isUndefined(commandParams?.chargePointSerialNumber) && { | |
213 | chargePointSerialNumber: commandParams.chargePointSerialNumber, | |
214 | }), | |
215 | ...(!Utils.isUndefined(commandParams?.firmwareVersion) && { | |
216 | firmwareVersion: commandParams.firmwareVersion, | |
217 | }), | |
218 | ...(!Utils.isUndefined(commandParams?.iccid) && { iccid: commandParams.iccid }), | |
219 | ...(!Utils.isUndefined(commandParams?.imsi) && { imsi: commandParams.imsi }), | |
220 | ...(!Utils.isUndefined(commandParams?.meterSerialNumber) && { | |
221 | meterSerialNumber: commandParams.meterSerialNumber, | |
222 | }), | |
223 | ...(!Utils.isUndefined(commandParams?.meterType) && { | |
224 | meterType: commandParams.meterType, | |
225 | }), | |
f22266fd | 226 | } as unknown as Request; |
78085c42 JB |
227 | case OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION: |
228 | return { | |
229 | status: commandParams?.diagnosticsStatus, | |
f22266fd | 230 | } as unknown as Request; |
78085c42 | 231 | case OCPP16RequestCommand.HEARTBEAT: |
f22266fd | 232 | return {} as unknown as Request; |
78085c42 JB |
233 | case OCPP16RequestCommand.METER_VALUES: |
234 | return { | |
235 | connectorId: commandParams?.connectorId, | |
236 | transactionId: commandParams?.transactionId, | |
7369e417 | 237 | meterValue: commandParams?.meterValue, |
f22266fd | 238 | } as unknown as Request; |
78085c42 JB |
239 | case OCPP16RequestCommand.STATUS_NOTIFICATION: |
240 | return { | |
241 | connectorId: commandParams?.connectorId, | |
78085c42 | 242 | status: commandParams?.status, |
93b4a429 | 243 | errorCode: commandParams?.errorCode, |
f22266fd | 244 | } as unknown as Request; |
78085c42 JB |
245 | case OCPP16RequestCommand.START_TRANSACTION: |
246 | return { | |
247 | connectorId: commandParams?.connectorId, | |
248 | ...(!Utils.isUndefined(commandParams?.idTag) | |
249 | ? { idTag: commandParams?.idTag } | |
250 | : { idTag: Constants.DEFAULT_IDTAG }), | |
08f130a0 | 251 | meterStart: chargingStation.getEnergyActiveImportRegisterByConnectorId( |
78085c42 JB |
252 | commandParams?.connectorId as number |
253 | ), | |
254 | timestamp: new Date().toISOString(), | |
f22266fd | 255 | } as unknown as Request; |
78085c42 | 256 | case OCPP16RequestCommand.STOP_TRANSACTION: |
08f130a0 | 257 | connectorId = chargingStation.getConnectorIdByTransactionId( |
f479a792 JB |
258 | commandParams?.transactionId as number |
259 | ); | |
7acb3f7b JB |
260 | commandParams?.meterStop && |
261 | (energyActiveImportRegister = | |
262 | chargingStation.getEnergyActiveImportRegisterByTransactionId( | |
263 | commandParams?.transactionId as number, | |
264 | true | |
265 | )); | |
78085c42 JB |
266 | return { |
267 | transactionId: commandParams?.transactionId, | |
cf058664 JB |
268 | idTag: |
269 | commandParams?.idTag ?? | |
270 | chargingStation.getTransactionIdTag(commandParams?.transactionId as number), | |
271 | meterStop: commandParams?.meterStop ?? energyActiveImportRegister, | |
78085c42 | 272 | timestamp: new Date().toISOString(), |
cf058664 | 273 | reason: commandParams?.reason, |
08f130a0 | 274 | ...(chargingStation.getTransactionDataMeterValues() && { |
78085c42 | 275 | transactionData: OCPP16ServiceUtils.buildTransactionDataMeterValues( |
08f130a0 | 276 | chargingStation.getConnectorStatus(connectorId).transactionBeginMeterValue, |
78085c42 | 277 | OCPP16ServiceUtils.buildTransactionEndMeterValue( |
08f130a0 | 278 | chargingStation, |
68c993d5 | 279 | connectorId, |
cf058664 | 280 | (commandParams?.meterStop as number) ?? energyActiveImportRegister |
78085c42 JB |
281 | ) |
282 | ), | |
283 | }), | |
f22266fd | 284 | } as unknown as Request; |
91a7d3ea JB |
285 | case OCPP16RequestCommand.DATA_TRANSFER: |
286 | return commandParams as unknown as Request; | |
78085c42 | 287 | default: |
e909d2a7 | 288 | // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError(). |
78085c42 JB |
289 | throw new OCPPError( |
290 | ErrorType.NOT_SUPPORTED, | |
291 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
6c8f5d90 | 292 | `Unsupported OCPP command '${commandName}'`, |
78085c42 | 293 | commandName, |
7369e417 | 294 | commandParams |
78085c42 JB |
295 | ); |
296 | } | |
297 | } | |
9c5c4195 JB |
298 | |
299 | private validatePayload<Request extends JsonType>( | |
300 | chargingStation: ChargingStation, | |
301 | commandName: OCPP16RequestCommand, | |
302 | requestPayload: Request | |
303 | ): boolean { | |
304 | if (this.jsonSchemas.has(commandName)) { | |
305 | return this.validateRequestPayload( | |
306 | chargingStation, | |
307 | commandName, | |
308 | this.jsonSchemas.get(commandName), | |
309 | requestPayload | |
310 | ); | |
311 | } | |
312 | logger.warn( | |
ee307db5 | 313 | `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command ${commandName} PDU validation` |
9c5c4195 JB |
314 | ); |
315 | return false; | |
316 | } | |
c0560973 | 317 | } |