fix: ensure inflight requests id cannot be duplicated
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16RequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import type { ValidateFunction } from 'ajv'
4
5 import type { ChargingStation } from '../../../charging-station/index.js'
6 import { OCPPError } from '../../../exception/index.js'
7 import {
8 ErrorType,
9 type JsonObject,
10 type JsonType,
11 type OCPP16AuthorizeRequest,
12 type OCPP16BootNotificationRequest,
13 OCPP16ChargePointStatus,
14 type OCPP16DataTransferRequest,
15 type OCPP16DiagnosticsStatusNotificationRequest,
16 type OCPP16FirmwareStatusNotificationRequest,
17 type OCPP16HeartbeatRequest,
18 type OCPP16MeterValuesRequest,
19 OCPP16RequestCommand,
20 type OCPP16StartTransactionRequest,
21 type OCPP16StatusNotificationRequest,
22 type OCPP16StopTransactionRequest,
23 OCPPVersion,
24 type RequestParams
25 } from '../../../types/index.js'
26 import { Constants, generateUUID } from '../../../utils/index.js'
27 import { OCPPRequestService } from '../OCPPRequestService.js'
28 import type { OCPPResponseService } from '../OCPPResponseService.js'
29 import { OCPP16Constants } from './OCPP16Constants.js'
30 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
31
32 const moduleName = 'OCPP16RequestService'
33
34 export class OCPP16RequestService extends OCPPRequestService {
35 protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
36
37 public constructor (ocppResponseService: OCPPResponseService) {
38 // if (new.target.name === moduleName) {
39 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
40 // }
41 super(OCPPVersion.VERSION_16, ocppResponseService)
42 this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
43 [
44 OCPP16RequestCommand.AUTHORIZE,
45 this.ajv
46 .compile(
47 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeRequest>(
48 'assets/json-schemas/ocpp/1.6/Authorize.json',
49 moduleName,
50 'constructor'
51 )
52 )
53 .bind(this)
54 ],
55 [
56 OCPP16RequestCommand.BOOT_NOTIFICATION,
57 this.ajv
58 .compile(
59 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationRequest>(
60 'assets/json-schemas/ocpp/1.6/BootNotification.json',
61 moduleName,
62 'constructor'
63 )
64 )
65 .bind(this)
66 ],
67 [
68 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
69 this.ajv
70 .compile(
71 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationRequest>(
72 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotification.json',
73 moduleName,
74 'constructor'
75 )
76 )
77 .bind(this)
78 ],
79 [
80 OCPP16RequestCommand.HEARTBEAT,
81 this.ajv
82 .compile(
83 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatRequest>(
84 'assets/json-schemas/ocpp/1.6/Heartbeat.json',
85 moduleName,
86 'constructor'
87 )
88 )
89 .bind(this)
90 ],
91 [
92 OCPP16RequestCommand.METER_VALUES,
93 this.ajv
94 .compile(
95 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesRequest>(
96 'assets/json-schemas/ocpp/1.6/MeterValues.json',
97 moduleName,
98 'constructor'
99 )
100 )
101 .bind(this)
102 ],
103 [
104 OCPP16RequestCommand.STATUS_NOTIFICATION,
105 this.ajv
106 .compile(
107 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationRequest>(
108 'assets/json-schemas/ocpp/1.6/StatusNotification.json',
109 moduleName,
110 'constructor'
111 )
112 )
113 .bind(this)
114 ],
115 [
116 OCPP16RequestCommand.START_TRANSACTION,
117 this.ajv
118 .compile(
119 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionRequest>(
120 'assets/json-schemas/ocpp/1.6/StartTransaction.json',
121 moduleName,
122 'constructor'
123 )
124 )
125 .bind(this)
126 ],
127 [
128 OCPP16RequestCommand.STOP_TRANSACTION,
129 this.ajv
130 .compile(
131 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionRequest>(
132 'assets/json-schemas/ocpp/1.6/StopTransaction.json',
133 moduleName,
134 'constructor'
135 )
136 )
137 .bind(this)
138 ],
139 [
140 OCPP16RequestCommand.DATA_TRANSFER,
141 this.ajv
142 .compile(
143 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
144 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
145 moduleName,
146 'constructor'
147 )
148 )
149 .bind(this)
150 ],
151 [
152 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
153 this.ajv
154 .compile(
155 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationRequest>(
156 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotification.json',
157 moduleName,
158 'constructor'
159 )
160 )
161 .bind(this)
162 ]
163 ])
164 this.buildRequestPayload = this.buildRequestPayload.bind(this)
165 }
166
167 public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
168 chargingStation: ChargingStation,
169 commandName: OCPP16RequestCommand,
170 commandParams?: RequestType,
171 params?: RequestParams
172 ): Promise<ResponseType> {
173 // FIXME?: add sanity checks on charging station availability, connector availability, connector status, etc.
174 if (OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)) {
175 // Pre request actions hook
176 switch (commandName) {
177 case OCPP16RequestCommand.START_TRANSACTION:
178 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
179 chargingStation,
180 (commandParams as OCPP16StartTransactionRequest).connectorId,
181 OCPP16ChargePointStatus.Preparing
182 )
183 break
184 }
185 return (await this.sendMessage(
186 chargingStation,
187 generateUUID(),
188 this.buildRequestPayload<RequestType>(chargingStation, commandName, commandParams),
189 commandName,
190 params
191 )) as ResponseType
192 }
193 // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
194 throw new OCPPError(
195 ErrorType.NOT_SUPPORTED,
196 `Unsupported OCPP command ${commandName}`,
197 commandName,
198 commandParams
199 )
200 }
201
202 private buildRequestPayload<Request extends JsonType>(
203 chargingStation: ChargingStation,
204 commandName: OCPP16RequestCommand,
205 commandParams?: JsonType
206 ): Request {
207 let connectorId: number | undefined
208 let energyActiveImportRegister: number
209 commandParams = commandParams as JsonObject
210 switch (commandName) {
211 case OCPP16RequestCommand.BOOT_NOTIFICATION:
212 case OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION:
213 case OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION:
214 case OCPP16RequestCommand.METER_VALUES:
215 case OCPP16RequestCommand.STATUS_NOTIFICATION:
216 case OCPP16RequestCommand.DATA_TRANSFER:
217 return commandParams as unknown as Request
218 case OCPP16RequestCommand.AUTHORIZE:
219 return {
220 idTag: Constants.DEFAULT_IDTAG,
221 ...commandParams
222 } as unknown as Request
223 case OCPP16RequestCommand.HEARTBEAT:
224 return OCPP16Constants.OCPP_REQUEST_EMPTY as unknown as Request
225 case OCPP16RequestCommand.START_TRANSACTION:
226 return {
227 idTag: Constants.DEFAULT_IDTAG,
228 meterStart: chargingStation.getEnergyActiveImportRegisterByConnectorId(
229 commandParams.connectorId as number,
230 true
231 ),
232 timestamp: new Date(),
233 ...(OCPP16ServiceUtils.hasReservation(
234 chargingStation,
235 commandParams.connectorId as number,
236 commandParams.idTag as string
237 ) && {
238 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
239 reservationId: chargingStation.getReservationBy(
240 'connectorId',
241 chargingStation.getConnectorStatus(0)?.status === OCPP16ChargePointStatus.Reserved
242 ? 0
243 : (commandParams.connectorId as number)
244 )!.reservationId
245 }),
246 ...commandParams
247 } as unknown as Request
248 case OCPP16RequestCommand.STOP_TRANSACTION:
249 chargingStation.stationInfo?.transactionDataMeterValues === true &&
250 (connectorId = chargingStation.getConnectorIdByTransactionId(
251 commandParams.transactionId as number
252 ))
253 energyActiveImportRegister = chargingStation.getEnergyActiveImportRegisterByTransactionId(
254 commandParams.transactionId as number,
255 true
256 )
257 return {
258 idTag: chargingStation.getTransactionIdTag(commandParams.transactionId as number),
259 meterStop: energyActiveImportRegister,
260 timestamp: new Date(),
261 ...(chargingStation.stationInfo?.transactionDataMeterValues === true && {
262 transactionData: OCPP16ServiceUtils.buildTransactionDataMeterValues(
263 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
264 chargingStation.getConnectorStatus(connectorId!)!.transactionBeginMeterValue!,
265 OCPP16ServiceUtils.buildTransactionEndMeterValue(
266 chargingStation,
267 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
268 connectorId!,
269 energyActiveImportRegister
270 )
271 )
272 }),
273 ...commandParams
274 } as unknown as Request
275 default:
276 // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
277 throw new OCPPError(
278 ErrorType.NOT_SUPPORTED,
279 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
280 `Unsupported OCPP command ${commandName}`,
281 commandName,
282 commandParams
283 )
284 }
285 }
286 }