1 import { BaseError
, type OCPPError
} from
'../../../exception/index.js'
3 BroadcastChannelProcedureName
,
4 type BroadcastChannelRequestPayload
,
8 type ProtocolRequestHandler
,
14 } from
'../../../types/index.js'
15 import { isAsyncFunction
, isNotEmptyArray
, logger
} from
'../../../utils/index.js'
16 import { Bootstrap
} from
'../../Bootstrap.js'
17 import { UIServiceWorkerBroadcastChannel
} from
'../../broadcast-channel/UIServiceWorkerBroadcastChannel.js'
18 import type { AbstractUIServer
} from
'../AbstractUIServer.js'
20 const moduleName
= 'AbstractUIService'
22 export abstract class AbstractUIService
{
23 protected static readonly ProcedureNameToBroadCastChannelProcedureNameMapping
= new Map
<
25 BroadcastChannelProcedureName
27 [ProcedureName
.START_CHARGING_STATION
, BroadcastChannelProcedureName
.START_CHARGING_STATION
],
28 [ProcedureName
.STOP_CHARGING_STATION
, BroadcastChannelProcedureName
.STOP_CHARGING_STATION
],
29 [ProcedureName
.CLOSE_CONNECTION
, BroadcastChannelProcedureName
.CLOSE_CONNECTION
],
30 [ProcedureName
.OPEN_CONNECTION
, BroadcastChannelProcedureName
.OPEN_CONNECTION
],
32 ProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
,
33 BroadcastChannelProcedureName
.START_AUTOMATIC_TRANSACTION_GENERATOR
36 ProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
,
37 BroadcastChannelProcedureName
.STOP_AUTOMATIC_TRANSACTION_GENERATOR
39 [ProcedureName
.SET_SUPERVISION_URL
, BroadcastChannelProcedureName
.SET_SUPERVISION_URL
],
40 [ProcedureName
.START_TRANSACTION
, BroadcastChannelProcedureName
.START_TRANSACTION
],
41 [ProcedureName
.STOP_TRANSACTION
, BroadcastChannelProcedureName
.STOP_TRANSACTION
],
42 [ProcedureName
.AUTHORIZE
, BroadcastChannelProcedureName
.AUTHORIZE
],
43 [ProcedureName
.BOOT_NOTIFICATION
, BroadcastChannelProcedureName
.BOOT_NOTIFICATION
],
44 [ProcedureName
.STATUS_NOTIFICATION
, BroadcastChannelProcedureName
.STATUS_NOTIFICATION
],
45 [ProcedureName
.HEARTBEAT
, BroadcastChannelProcedureName
.HEARTBEAT
],
46 [ProcedureName
.METER_VALUES
, BroadcastChannelProcedureName
.METER_VALUES
],
47 [ProcedureName
.DATA_TRANSFER
, BroadcastChannelProcedureName
.DATA_TRANSFER
],
49 ProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
,
50 BroadcastChannelProcedureName
.DIAGNOSTICS_STATUS_NOTIFICATION
53 ProcedureName
.FIRMWARE_STATUS_NOTIFICATION
,
54 BroadcastChannelProcedureName
.FIRMWARE_STATUS_NOTIFICATION
58 protected readonly requestHandlers
: Map
<ProcedureName
, ProtocolRequestHandler
>
59 private readonly version
: ProtocolVersion
60 private readonly uiServer
: AbstractUIServer
61 private readonly uiServiceWorkerBroadcastChannel
: UIServiceWorkerBroadcastChannel
62 private readonly broadcastChannelRequests
: Map
<string, number>
64 constructor (uiServer
: AbstractUIServer
, version
: ProtocolVersion
) {
65 this.uiServer
= uiServer
66 this.version
= version
67 this.requestHandlers
= new Map
<ProcedureName
, ProtocolRequestHandler
>([
68 [ProcedureName
.LIST_TEMPLATES
, this.handleListTemplates
.bind(this)],
69 [ProcedureName
.LIST_CHARGING_STATIONS
, this.handleListChargingStations
.bind(this)],
70 [ProcedureName
.START_SIMULATOR
, this.handleStartSimulator
.bind(this)],
71 [ProcedureName
.STOP_SIMULATOR
, this.handleStopSimulator
.bind(this)]
73 this.uiServiceWorkerBroadcastChannel
= new UIServiceWorkerBroadcastChannel(this)
74 this.broadcastChannelRequests
= new Map
<string, number>()
77 public async requestHandler (request
: ProtocolRequest
): Promise
<ProtocolResponse
| undefined> {
78 let messageId
: string | undefined
79 let command
: ProcedureName
| undefined
80 let requestPayload
: RequestPayload
| undefined
81 let responsePayload
: ResponsePayload
| undefined
83 [messageId
, command
, requestPayload
] = request
85 if (!this.requestHandlers
.has(command
)) {
87 `${command} is not implemented to handle message payload ${JSON.stringify(
95 // Call the request handler to build the response payload
96 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
97 const requestHandler
= this.requestHandlers
.get(command
)!
98 if (isAsyncFunction(requestHandler
)) {
99 responsePayload
= await requestHandler(messageId
, command
, requestPayload
)
104 procedureName
?: ProcedureName
,
105 payload
?: RequestPayload
106 ) => undefined | ResponsePayload
107 )(messageId
, command
, requestPayload
)
111 logger
.error(`${this.logPrefix(moduleName, 'requestHandler')} Handle request error:`, error
)
113 hashIds
: requestPayload
?.hashIds
,
114 status: ResponseStatus
.FAILURE
,
118 errorMessage
: (error
as OCPPError
).message
,
119 errorStack
: (error
as OCPPError
).stack
,
120 errorDetails
: (error
as OCPPError
).details
123 if (responsePayload
!= null) {
124 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125 return this.uiServer
.buildProtocolResponse(messageId
!, responsePayload
)
129 // public sendRequest (
130 // messageId: string,
131 // procedureName: ProcedureName,
132 // requestPayload: RequestPayload
134 // this.uiServer.sendRequest(
135 // this.uiServer.buildProtocolRequest(messageId, procedureName, requestPayload)
139 public sendResponse (messageId
: string, responsePayload
: ResponsePayload
): void {
140 if (this.uiServer
.hasResponseHandler(messageId
)) {
141 this.uiServer
.sendResponse(this.uiServer
.buildProtocolResponse(messageId
, responsePayload
))
145 public logPrefix
= (modName
: string, methodName
: string): string => {
146 return this.uiServer
.logPrefix(modName
, methodName
, this.version
)
149 public deleteBroadcastChannelRequest (uuid
: string): void {
150 this.broadcastChannelRequests
.delete(uuid
)
153 public getBroadcastChannelExpectedResponses (uuid
: string): number {
154 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155 return this.broadcastChannelRequests
.get(uuid
)!
158 protected handleProtocolRequest (
160 procedureName
: ProcedureName
,
161 payload
: RequestPayload
163 this.sendBroadcastChannelRequest(
165 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
166 AbstractUIService
.ProcedureNameToBroadCastChannelProcedureNameMapping
.get(procedureName
)!,
171 private sendBroadcastChannelRequest (
173 procedureName
: BroadcastChannelProcedureName
,
174 payload
: BroadcastChannelRequestPayload
176 if (isNotEmptyArray(payload
.hashIds
)) {
177 payload
.hashIds
= payload
.hashIds
179 if (this.uiServer
.chargingStations
.has(hashId
)) {
185 'sendBroadcastChannelRequest'
186 )} Charging station with hashId '${hashId}' not found`
190 .filter(hashId
=> hashId
!= null) as string[]
192 delete payload
.hashIds
194 const expectedNumberOfResponses
= Array.isArray(payload
.hashIds
)
195 ? payload
.hashIds
.length
196 : this.uiServer
.chargingStations
.size
197 if (expectedNumberOfResponses
=== 0) {
199 'hashIds array in the request payload does not contain any valid charging station hashId'
202 this.uiServiceWorkerBroadcastChannel
.sendRequest([uuid
, procedureName
, payload
])
203 this.broadcastChannelRequests
.set(uuid
, expectedNumberOfResponses
)
206 private handleListTemplates (): ResponsePayload
{
208 status: ResponseStatus
.SUCCESS
,
209 templates
: [...this.uiServer
.chargingStationTemplates
.values()] as JsonType
[]
210 } satisfies ResponsePayload
213 private handleListChargingStations (): ResponsePayload
{
215 status: ResponseStatus
.SUCCESS
,
216 chargingStations
: [...this.uiServer
.chargingStations
.values()] as JsonType
[]
217 } satisfies ResponsePayload
220 private async handleStartSimulator (): Promise
<ResponsePayload
> {
222 await Bootstrap
.getInstance().start()
223 return { status: ResponseStatus
.SUCCESS
}
225 return { status: ResponseStatus
.FAILURE
}
229 private async handleStopSimulator (): Promise
<ResponsePayload
> {
231 await Bootstrap
.getInstance().stop()
232 return { status: ResponseStatus
.SUCCESS
}
234 return { status: ResponseStatus
.FAILURE
}