5f6a029d8eb15f574858b11d870c281371aea2f2
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import type { ValidateFunction } from 'ajv'
4
5 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
6 import type { ChargingStation } from '../../../charging-station/index.js'
7 import { OCPPError } from '../../../exception/index.js'
8 import {
9 ErrorType,
10 type IncomingRequestHandler,
11 type JsonType,
12 type OCPP20ClearCacheRequest,
13 OCPP20IncomingRequestCommand,
14 OCPPVersion
15 } from '../../../types/index.js'
16 import { isAsyncFunction, logger } from '../../../utils/index.js'
17 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
18
19 const moduleName = 'OCPP20IncomingRequestService'
20
21 export class OCPP20IncomingRequestService extends OCPPIncomingRequestService {
22 protected payloadValidateFunctions: Map<OCPP20IncomingRequestCommand, ValidateFunction<JsonType>>
23
24 private readonly incomingRequestHandlers: Map<
25 OCPP20IncomingRequestCommand,
26 IncomingRequestHandler
27 >
28
29 public constructor () {
30 // if (new.target.name === moduleName) {
31 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
32 // }
33 super(OCPPVersion.VERSION_20)
34 this.incomingRequestHandlers = new Map<OCPP20IncomingRequestCommand, IncomingRequestHandler>([
35 [OCPP20IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)]
36 ])
37 this.payloadValidateFunctions = new Map<
38 OCPP20IncomingRequestCommand,
39 ValidateFunction<JsonType>
40 >([
41 [
42 OCPP20IncomingRequestCommand.CLEAR_CACHE,
43 this.ajv
44 .compile(
45 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheRequest>(
46 'assets/json-schemas/ocpp/2.0/ClearCacheRequest.json',
47 moduleName,
48 'constructor'
49 )
50 )
51 .bind(this)
52 ]
53 ])
54 this.validatePayload = this.validatePayload.bind(this)
55 }
56
57 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
58 chargingStation: ChargingStation,
59 messageId: string,
60 commandName: OCPP20IncomingRequestCommand,
61 commandPayload: ReqType
62 ): Promise<void> {
63 let response: ResType
64 if (
65 chargingStation.stationInfo?.ocppStrictCompliance === true &&
66 chargingStation.inPendingState() &&
67 (commandName === OCPP20IncomingRequestCommand.REQUEST_START_TRANSACTION ||
68 commandName === OCPP20IncomingRequestCommand.REQUEST_STOP_TRANSACTION)
69 ) {
70 throw new OCPPError(
71 ErrorType.SECURITY_ERROR,
72 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
73 commandPayload,
74 undefined,
75 2
76 )} while the charging station is in pending state on the central server`,
77 commandName,
78 commandPayload
79 )
80 }
81 if (
82 chargingStation.isRegistered() ||
83 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
84 chargingStation.inUnknownState())
85 ) {
86 if (
87 this.incomingRequestHandlers.has(commandName) &&
88 OCPP20ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
89 ) {
90 try {
91 this.validatePayload(chargingStation, commandName, commandPayload)
92 // Call the method to build the response
93 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
94 const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
95 if (isAsyncFunction(incomingRequestHandler)) {
96 response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
97 } else {
98 response = incomingRequestHandler(chargingStation, commandPayload) as ResType
99 }
100 } catch (error) {
101 // Log
102 logger.error(
103 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
104 error
105 )
106 throw error
107 }
108 } else {
109 // Throw exception
110 throw new OCPPError(
111 ErrorType.NOT_IMPLEMENTED,
112 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
113 commandPayload,
114 undefined,
115 2
116 )}`,
117 commandName,
118 commandPayload
119 )
120 }
121 } else {
122 throw new OCPPError(
123 ErrorType.SECURITY_ERROR,
124 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
125 commandPayload,
126 undefined,
127 2
128 )} while the charging station is not registered on the central server.`,
129 commandName,
130 commandPayload
131 )
132 }
133 // Send the built response
134 await chargingStation.ocppRequestService.sendResponse(
135 chargingStation,
136 messageId,
137 response,
138 commandName
139 )
140 // Emit command name event to allow delayed handling
141 this.emit(commandName, chargingStation, commandPayload, response)
142 }
143
144 private validatePayload (
145 chargingStation: ChargingStation,
146 commandName: OCPP20IncomingRequestCommand,
147 commandPayload: JsonType
148 ): boolean {
149 if (this.payloadValidateFunctions.has(commandName)) {
150 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
151 }
152 logger.warn(
153 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
154 )
155 return false
156 }
157 }