1 import { BaseError
, type OCPPError
} from
'../../../exception/index.js'
3 BroadcastChannelProcedureName
,
4 type BroadcastChannelRequestPayload
,
5 type ChargingStationOptions
,
10 type ProtocolRequestHandler
,
11 type ProtocolResponse
,
16 type StorageConfiguration
17 } from
'../../../types/index.js'
18 import { Configuration
, isAsyncFunction
, isNotEmptyArray
, logger
} from
'../../../utils/index.js'
19 import { Bootstrap
} from
'../../Bootstrap.js'
20 import { UIServiceWorkerBroadcastChannel
} from
'../../broadcast-channel/UIServiceWorkerBroadcastChannel.js'
21 import type { AbstractUIServer
} from
'../AbstractUIServer.js'
23 const moduleName
= 'AbstractUIService'
25 export abstract class AbstractUIService
{
26 protected static readonly ProcedureNameToBroadCastChannelProcedureNameMapping
= new Map
<
28 BroadcastChannelProcedureName
30 [ProcedureName
.START_CHARGING_STATION
, BroadcastChannelProcedureName
.START_CHARGING_STATION
],
31 [ProcedureName
.STOP_CHARGING_STATION
, BroadcastChannelProcedureName
.STOP_CHARGING_STATION
],
33 ProcedureName
.DELETE_CHARGING_STATIONS
,
34 BroadcastChannelProcedureName
.DELETE_CHARGING_STATIONS
36 [ProcedureName
.CLOSE_CONNECTION
, BroadcastChannelProcedureName
.CLOSE_CONNECTION
],
37 [ProcedureName
.OPEN_CONNECTION
, BroadcastChannelProcedureName
.OPEN_CONNECTION
],
39 ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
40 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
43 ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
44 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
46 [ProcedureName
.SET_SUPERVISION_URL
, BroadcastChannelProcedureName
.SET_SUPERVISION_URL
],
47 [ProcedureName
.START_TRANSACTION
, BroadcastChannelProcedureName
.START_TRANSACTION
],
48 [ProcedureName
.STOP_TRANSACTION
, BroadcastChannelProcedureName
.STOP_TRANSACTION
],
49 [ProcedureName
.AUTHORIZE
, BroadcastChannelProcedureName
.AUTHORIZE
],
50 [ProcedureName
.BOOT_NOTIFICATION
, BroadcastChannelProcedureName
.BOOT_NOTIFICATION
],
51 [ProcedureName
.STATUS_NOTIFICATION
, BroadcastChannelProcedureName
.STATUS_NOTIFICATION
],
52 [ProcedureName
.HEARTBEAT
, BroadcastChannelProcedureName
.HEARTBEAT
],
53 [ProcedureName
.METER_VALUES
, BroadcastChannelProcedureName
.METER_VALUES
],
54 [ProcedureName
.DATA_TRANSFER
, BroadcastChannelProcedureName
.DATA_TRANSFER
],
56 ProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
57 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
60 ProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
61 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
65 protected readonly requestHandlers
: Map
<ProcedureName
, ProtocolRequestHandler
>
66 private readonly version
: ProtocolVersion
67 private readonly uiServer
: AbstractUIServer
68 private readonly uiServiceWorkerBroadcastChannel
: UIServiceWorkerBroadcastChannel
69 private readonly broadcastChannelRequests
: Map
<
70 `${string}-${string}-${string}-${string}-${string}`,
74 constructor (uiServer
: AbstractUIServer
, version
: ProtocolVersion
) {
75 this.uiServer
= uiServer
76 this.version
= version
77 this.requestHandlers
= new Map
<ProcedureName
, ProtocolRequestHandler
>([
78 [ProcedureName
.LIST_TEMPLATES
, this.handleListTemplates
.bind(this)],
79 [ProcedureName
.LIST_CHARGING_STATIONS
, this.handleListChargingStations
.bind(this)],
80 [ProcedureName
.ADD_CHARGING_STATIONS
, this.handleAddChargingStations
.bind(this)],
81 [ProcedureName
.PERFORMANCE_STATISTICS
, this.handlePerformanceStatistics
.bind(this)],
82 [ProcedureName
.SIMULATOR_STATE
, this.handleSimulatorState
.bind(this)],
83 [ProcedureName
.START_SIMULATOR
, this.handleStartSimulator
.bind(this)],
84 [ProcedureName
.STOP_SIMULATOR
, this.handleStopSimulator
.bind(this)]
86 this.uiServiceWorkerBroadcastChannel
= new UIServiceWorkerBroadcastChannel(this)
87 this.broadcastChannelRequests
= new Map
<
88 `${string}-${string}-${string}-${string}-${string}`,
93 public stop (): void {
94 this.broadcastChannelRequests
.clear()
95 this.uiServiceWorkerBroadcastChannel
.close()
98 public async requestHandler (request
: ProtocolRequest
): Promise
<ProtocolResponse
| undefined> {
99 let uuid
: `${string}-${string}-${string}-${string}-${string}` | undefined
100 let command
: ProcedureName
| undefined
101 let requestPayload
: RequestPayload
| undefined
102 let responsePayload
: ResponsePayload
| undefined
104 [uuid
, command
, requestPayload
] = request
106 if (!this.requestHandlers
.has(command
)) {
108 `'${command}' is not implemented to handle message payload ${JSON.stringify(
116 // Call the request handler to build the response payload
117 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
118 const requestHandler
= this.requestHandlers
.get(command
)!
119 if (isAsyncFunction(requestHandler
)) {
120 responsePayload
= await requestHandler(uuid
, command
, requestPayload
)
125 procedureName
?: ProcedureName
,
126 payload
?: RequestPayload
127 ) => undefined | ResponsePayload
128 )(uuid
, command
, requestPayload
)
132 logger
.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error
)
134 hashIds
: requestPayload
?.hashIds
,
135 status: ResponseStatus
.FAILURE
,
139 errorMessage
: (error
as OCPPError
).message
,
140 errorStack
: (error
as OCPPError
).stack
,
141 errorDetails
: (error
as OCPPError
).details
142 } satisfies ResponsePayload
144 if (responsePayload
!= null) {
145 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
146 return this.uiServer
.buildProtocolResponse(uuid
!, responsePayload
)
150 // public sendRequest (
151 // uuid: `${string}-${string}-${string}-${string}-${string}`,
152 // procedureName: ProcedureName,
153 // requestPayload: RequestPayload
155 // this.uiServer.sendRequest(
156 // this.uiServer.buildProtocolRequest(uuid, procedureName, requestPayload)
160 public sendResponse (
161 uuid
: `${string}-${string}-${string}-${string}-${string}`,
162 responsePayload
: ResponsePayload
164 if (this.uiServer
.hasResponseHandler(uuid
)) {
165 this.uiServer
.sendResponse(this.uiServer
.buildProtocolResponse(uuid
, responsePayload
))
169 public logPrefix
= (modName
: string, methodName
: string): string => {
170 return this.uiServer
.logPrefix(modName
, methodName
, this.version
)
173 public deleteBroadcastChannelRequest (
174 uuid
: `${string}-${string}-${string}-${string}-${string}`
176 this.broadcastChannelRequests
.delete(uuid
)
179 public getBroadcastChannelExpectedResponses (
180 uuid
: `${string}-${string}-${string}-${string}-${string}`
182 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
183 return this.broadcastChannelRequests
.get(uuid
)!
186 protected handleProtocolRequest (
187 uuid
: `${string}-${string}-${string}-${string}-${string}`,
188 procedureName
: ProcedureName
,
189 payload
: RequestPayload
191 this.sendBroadcastChannelRequest(
193 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
194 AbstractUIService
.ProcedureNameToBroadCastChannelProcedureNameMapping
.get(procedureName
)!,
199 private sendBroadcastChannelRequest (
200 uuid
: `${string}-${string}-${string}-${string}-${string}`,
201 procedureName
: BroadcastChannelProcedureName
,
202 payload
: BroadcastChannelRequestPayload
204 if (isNotEmptyArray(payload
.hashIds
)) {
205 payload
.hashIds
= payload
.hashIds
207 if (this.uiServer
.chargingStations
.has(hashId
)) {
213 'sendBroadcastChannelRequest'
214 )} Charging station with hashId '${hashId}' not found`
218 .filter(hashId
=> hashId
!= null) as string[]
220 delete payload
.hashIds
222 const expectedNumberOfResponses
= Array.isArray(payload
.hashIds
)
223 ? payload
.hashIds
.length
224 : this.uiServer
.chargingStations
.size
225 if (expectedNumberOfResponses
=== 0) {
227 'hashIds array in the request payload does not contain any valid charging station hashId'
230 this.uiServiceWorkerBroadcastChannel
.sendRequest([uuid
, procedureName
, payload
])
231 this.broadcastChannelRequests
.set(uuid
, expectedNumberOfResponses
)
234 private handleListTemplates (): ResponsePayload
{
236 status: ResponseStatus
.SUCCESS
,
237 templates
: [...this.uiServer
.chargingStationTemplates
.values()] as JsonType
[]
238 } satisfies ResponsePayload
241 private handleListChargingStations (): ResponsePayload
{
243 status: ResponseStatus
.SUCCESS
,
244 chargingStations
: [...this.uiServer
.chargingStations
.values()] as JsonType
[]
245 } satisfies ResponsePayload
248 private async handleAddChargingStations (
249 _uuid
?: `${string}-${string}-${string}-${string}-${string}`,
250 _procedureName
?: ProcedureName
,
251 requestPayload
?: RequestPayload
252 ): Promise
<ResponsePayload
> {
253 const { template
, numberOfStations
, options
} = requestPayload
as {
255 numberOfStations
: number
256 options
?: ChargingStationOptions
258 if (!Bootstrap
.getInstance().getState().started
) {
260 status: ResponseStatus
.FAILURE
,
262 'Cannot add charging station(s) while the charging stations simulator is not started'
263 } satisfies ResponsePayload
265 if (typeof template
!== 'string' || typeof numberOfStations
!== 'number') {
267 status: ResponseStatus
.FAILURE
,
268 errorMessage
: 'Invalid request payload'
269 } satisfies ResponsePayload
271 if (!this.uiServer
.chargingStationTemplates
.has(template
)) {
273 status: ResponseStatus
.FAILURE
,
274 errorMessage
: `Template '${template}' not found`
275 } satisfies ResponsePayload
277 for (let i
= 0; i
< numberOfStations
; i
++) {
279 await Bootstrap
.getInstance().addChargingStation(
280 Bootstrap
.getInstance().getLastIndex(template
) + 1,
286 status: ResponseStatus
.FAILURE
,
287 errorMessage
: (error
as Error).message
,
288 errorStack
: (error
as Error).stack
289 } satisfies ResponsePayload
293 status: ResponseStatus
.SUCCESS
297 private handlePerformanceStatistics (): ResponsePayload
{
299 Configuration
.getConfigurationSection
<StorageConfiguration
>(
300 ConfigurationSection
.performanceStorage
304 status: ResponseStatus
.FAILURE
,
305 errorMessage
: 'Performance statistics storage is not enabled'
306 } satisfies ResponsePayload
310 status: ResponseStatus
.SUCCESS
,
311 performanceStatistics
: [
312 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
313 ...Bootstrap
.getInstance().getPerformanceStatistics()!
318 status: ResponseStatus
.FAILURE
,
319 errorMessage
: (error
as Error).message
,
320 errorStack
: (error
as Error).stack
321 } satisfies ResponsePayload
325 private handleSimulatorState (): ResponsePayload
{
328 status: ResponseStatus
.SUCCESS
,
329 state
: Bootstrap
.getInstance().getState()
330 } satisfies ResponsePayload
333 status: ResponseStatus
.FAILURE
,
334 errorMessage
: (error
as Error).message
,
335 errorStack
: (error
as Error).stack
336 } satisfies ResponsePayload
340 private async handleStartSimulator (): Promise
<ResponsePayload
> {
342 await Bootstrap
.getInstance().start()
343 return { status: ResponseStatus
.SUCCESS
}
346 status: ResponseStatus
.FAILURE
,
347 errorMessage
: (error
as Error).message
,
348 errorStack
: (error
as Error).stack
349 } satisfies ResponsePayload
353 private async handleStopSimulator (): Promise
<ResponsePayload
> {
355 await Bootstrap
.getInstance().stop()
356 return { status: ResponseStatus
.SUCCESS
}
359 status: ResponseStatus
.FAILURE
,
360 errorMessage
: (error
as Error).message
,
361 errorStack
: (error
as Error).stack
362 } satisfies ResponsePayload