1 import { BaseError
, type OCPPError
} from
'../../../exception/index.js'
3 BroadcastChannelProcedureName
,
4 type BroadcastChannelRequestPayload
,
5 type ChargingStationOptions
,
11 type ProtocolRequestHandler
,
12 type ProtocolResponse
,
17 type StorageConfiguration
18 } from
'../../../types/index.js'
19 import { Configuration
, isAsyncFunction
, isNotEmptyArray
, logger
} from
'../../../utils/index.js'
20 import { Bootstrap
} from
'../../Bootstrap.js'
21 import { UIServiceWorkerBroadcastChannel
} from
'../../broadcast-channel/UIServiceWorkerBroadcastChannel.js'
22 import type { AbstractUIServer
} from
'../AbstractUIServer.js'
24 const moduleName
= 'AbstractUIService'
26 export abstract class AbstractUIService
{
27 protected static readonly ProcedureNameToBroadCastChannelProcedureNameMapping
= new Map
<
29 BroadcastChannelProcedureName
31 [ProcedureName
.START_CHARGING_STATION
, BroadcastChannelProcedureName
.START_CHARGING_STATION
],
32 [ProcedureName
.STOP_CHARGING_STATION
, BroadcastChannelProcedureName
.STOP_CHARGING_STATION
],
34 ProcedureName
.DELETE_CHARGING_STATIONS
,
35 BroadcastChannelProcedureName
.DELETE_CHARGING_STATIONS
37 [ProcedureName
.CLOSE_CONNECTION
, BroadcastChannelProcedureName
.CLOSE_CONNECTION
],
38 [ProcedureName
.OPEN_CONNECTION
, BroadcastChannelProcedureName
.OPEN_CONNECTION
],
40 ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
41 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
44 ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
45 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
47 [ProcedureName
.SET_SUPERVISION_URL
, BroadcastChannelProcedureName
.SET_SUPERVISION_URL
],
48 [ProcedureName
.START_TRANSACTION
, BroadcastChannelProcedureName
.START_TRANSACTION
],
49 [ProcedureName
.STOP_TRANSACTION
, BroadcastChannelProcedureName
.STOP_TRANSACTION
],
50 [ProcedureName
.AUTHORIZE
, BroadcastChannelProcedureName
.AUTHORIZE
],
51 [ProcedureName
.BOOT_NOTIFICATION
, BroadcastChannelProcedureName
.BOOT_NOTIFICATION
],
52 [ProcedureName
.STATUS_NOTIFICATION
, BroadcastChannelProcedureName
.STATUS_NOTIFICATION
],
53 [ProcedureName
.HEARTBEAT
, BroadcastChannelProcedureName
.HEARTBEAT
],
54 [ProcedureName
.METER_VALUES
, BroadcastChannelProcedureName
.METER_VALUES
],
55 [ProcedureName
.DATA_TRANSFER
, BroadcastChannelProcedureName
.DATA_TRANSFER
],
57 ProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
58 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
61 ProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
62 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
66 protected readonly requestHandlers
: Map
<ProcedureName
, ProtocolRequestHandler
>
67 private readonly version
: ProtocolVersion
68 private readonly uiServer
: AbstractUIServer
69 private readonly uiServiceWorkerBroadcastChannel
: UIServiceWorkerBroadcastChannel
70 private readonly broadcastChannelRequests
: Map
<
71 `${string}-${string}-${string}-${string}-${string}`,
75 constructor (uiServer
: AbstractUIServer
, version
: ProtocolVersion
) {
76 this.uiServer
= uiServer
77 this.version
= version
78 this.requestHandlers
= new Map
<ProcedureName
, ProtocolRequestHandler
>([
79 [ProcedureName
.LIST_TEMPLATES
, this.handleListTemplates
.bind(this)],
80 [ProcedureName
.LIST_CHARGING_STATIONS
, this.handleListChargingStations
.bind(this)],
81 [ProcedureName
.ADD_CHARGING_STATIONS
, this.handleAddChargingStations
.bind(this)],
82 [ProcedureName
.PERFORMANCE_STATISTICS
, this.handlePerformanceStatistics
.bind(this)],
83 [ProcedureName
.SIMULATOR_STATE
, this.handleSimulatorState
.bind(this)],
84 [ProcedureName
.START_SIMULATOR
, this.handleStartSimulator
.bind(this)],
85 [ProcedureName
.STOP_SIMULATOR
, this.handleStopSimulator
.bind(this)]
87 this.uiServiceWorkerBroadcastChannel
= new UIServiceWorkerBroadcastChannel(this)
88 this.broadcastChannelRequests
= new Map
<
89 `${string}-${string}-${string}-${string}-${string}`,
94 public stop (): void {
95 this.broadcastChannelRequests
.clear()
96 this.uiServiceWorkerBroadcastChannel
.close()
99 public async requestHandler (request
: ProtocolRequest
): Promise
<ProtocolResponse
| undefined> {
100 let uuid
: `${string}-${string}-${string}-${string}-${string}` | undefined
101 let command
: ProcedureName
| undefined
102 let requestPayload
: RequestPayload
| undefined
103 let responsePayload
: ResponsePayload
| undefined
105 [uuid
, command
, requestPayload
] = request
107 if (!this.requestHandlers
.has(command
)) {
109 `'${command}' is not implemented to handle message payload ${JSON.stringify(
117 // Call the request handler to build the response payload
118 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
119 const requestHandler
= this.requestHandlers
.get(command
)!
120 if (isAsyncFunction(requestHandler
)) {
121 responsePayload
= await requestHandler(uuid
, command
, requestPayload
)
126 procedureName
?: ProcedureName
,
127 payload
?: RequestPayload
128 ) => undefined | ResponsePayload
129 )(uuid
, command
, requestPayload
)
133 logger
.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error
)
135 hashIds
: requestPayload
?.hashIds
,
136 status: ResponseStatus
.FAILURE
,
140 errorMessage
: (error
as OCPPError
).message
,
141 errorStack
: (error
as OCPPError
).stack
,
142 errorDetails
: (error
as OCPPError
).details
143 } satisfies ResponsePayload
145 if (responsePayload
!= null) {
146 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
147 return this.uiServer
.buildProtocolResponse(uuid
!, responsePayload
)
151 // public sendRequest (
152 // uuid: `${string}-${string}-${string}-${string}-${string}`,
153 // procedureName: ProcedureName,
154 // requestPayload: RequestPayload
156 // this.uiServer.sendRequest(
157 // this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
161 public sendResponse (
162 uuid
: `${string}-${string}-${string}-${string}-${string}`,
163 responsePayload
: ResponsePayload
165 if (this.uiServer
.hasResponseHandler(uuid
)) {
166 this.uiServer
.sendResponse(this.uiServer
.buildProtocolResponse(uuid
, responsePayload
))
170 public logPrefix
= (modName
: string, methodName
: string): string => {
171 return this.uiServer
.logPrefix(modName
, methodName
, this.version
)
174 public deleteBroadcastChannelRequest (
175 uuid
: `${string}-${string}-${string}-${string}-${string}`
177 this.broadcastChannelRequests
.delete(uuid
)
180 public getBroadcastChannelExpectedResponses (
181 uuid
: `${string}-${string}-${string}-${string}-${string}`
183 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
184 return this.broadcastChannelRequests
.get(uuid
)!
187 protected handleProtocolRequest (
188 uuid
: `${string}-${string}-${string}-${string}-${string}`,
189 procedureName
: ProcedureName
,
190 payload
: RequestPayload
192 this.sendBroadcastChannelRequest(
194 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
195 AbstractUIService
.ProcedureNameToBroadCastChannelProcedureNameMapping
.get(procedureName
)!,
200 private sendBroadcastChannelRequest (
201 uuid
: `${string}-${string}-${string}-${string}-${string}`,
202 procedureName
: BroadcastChannelProcedureName
,
203 payload
: BroadcastChannelRequestPayload
205 if (isNotEmptyArray(payload
.hashIds
)) {
206 payload
.hashIds
= payload
.hashIds
208 if (this.uiServer
.chargingStations
.has(hashId
)) {
214 'sendBroadcastChannelRequest'
215 )} Charging station with hashId '${hashId}' not found`
219 .filter(hashId
=> hashId
!= null) as string[]
221 delete payload
.hashIds
223 const expectedNumberOfResponses
= Array.isArray(payload
.hashIds
)
224 ? payload
.hashIds
.length
225 : this.uiServer
.chargingStations
.size
226 if (expectedNumberOfResponses
=== 0) {
228 'hashIds array in the request payload does not contain any valid charging station hashId'
231 this.uiServiceWorkerBroadcastChannel
.sendRequest([uuid
, procedureName
, payload
])
232 this.broadcastChannelRequests
.set(uuid
, expectedNumberOfResponses
)
235 private handleListTemplates (): ResponsePayload
{
237 status: ResponseStatus
.SUCCESS
,
238 templates
: [...this.uiServer
.chargingStationTemplates
.values()] as JsonType
[]
239 } satisfies ResponsePayload
242 private handleListChargingStations (): ResponsePayload
{
244 status: ResponseStatus
.SUCCESS
,
245 chargingStations
: [...this.uiServer
.chargingStations
.values()] as JsonType
[]
246 } satisfies ResponsePayload
249 private async handleAddChargingStations (
250 _uuid
?: `${string}-${string}-${string}-${string}-${string}`,
251 _procedureName
?: ProcedureName
,
252 requestPayload
?: RequestPayload
253 ): Promise
<ResponsePayload
> {
254 const { template
, numberOfStations
, options
} = requestPayload
as {
256 numberOfStations
: number
257 options
?: ChargingStationOptions
259 if (!Bootstrap
.getInstance().getState().started
) {
261 status: ResponseStatus
.FAILURE
,
263 'Cannot add charging station(s) while the charging stations simulator is not started'
264 } satisfies ResponsePayload
266 if (typeof template
!== 'string' || typeof numberOfStations
!== 'number') {
268 status: ResponseStatus
.FAILURE
,
269 errorMessage
: 'Invalid request payload'
270 } satisfies ResponsePayload
272 if (!this.uiServer
.chargingStationTemplates
.has(template
)) {
274 status: ResponseStatus
.FAILURE
,
275 errorMessage
: `Template '${template}' not found`
276 } satisfies ResponsePayload
278 for (let i
= 0; i
< numberOfStations
; i
++) {
280 await Bootstrap
.getInstance().addChargingStation(
281 Bootstrap
.getInstance().getLastIndex(template
) + 1,
287 status: ResponseStatus
.FAILURE
,
288 errorMessage
: (error
as Error).message
,
289 errorStack
: (error
as Error).stack
290 } satisfies ResponsePayload
294 status: ResponseStatus
.SUCCESS
298 private handlePerformanceStatistics (): ResponsePayload
{
300 Configuration
.getConfigurationSection
<StorageConfiguration
>(
301 ConfigurationSection
.performanceStorage
305 status: ResponseStatus
.FAILURE
,
306 errorMessage
: 'Performance statistics storage is not enabled'
307 } satisfies ResponsePayload
311 status: ResponseStatus
.SUCCESS
,
312 performanceStatistics
: [
313 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
314 ...Bootstrap
.getInstance().getPerformanceStatistics()!
319 status: ResponseStatus
.FAILURE
,
320 errorMessage
: (error
as Error).message
,
321 errorStack
: (error
as Error).stack
322 } satisfies ResponsePayload
326 private handleSimulatorState (): ResponsePayload
{
329 status: ResponseStatus
.SUCCESS
,
330 state
: Bootstrap
.getInstance().getState() as unknown
as JsonObject
331 } satisfies ResponsePayload
334 status: ResponseStatus
.FAILURE
,
335 errorMessage
: (error
as Error).message
,
336 errorStack
: (error
as Error).stack
337 } satisfies ResponsePayload
341 private async handleStartSimulator (): Promise
<ResponsePayload
> {
343 await Bootstrap
.getInstance().start()
344 return { status: ResponseStatus
.SUCCESS
}
347 status: ResponseStatus
.FAILURE
,
348 errorMessage
: (error
as Error).message
,
349 errorStack
: (error
as Error).stack
350 } satisfies ResponsePayload
354 private async handleStopSimulator (): Promise
<ResponsePayload
> {
356 await Bootstrap
.getInstance().stop()
357 return { status: ResponseStatus
.SUCCESS
}
360 status: ResponseStatus
.FAILURE
,
361 errorMessage
: (error
as Error).message
,
362 errorStack
: (error
as Error).stack
363 } satisfies ResponsePayload