build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
953d6b02 2
24d15716 3import type { ValidateFunction } from 'ajv'
953d6b02 4
4c3f6c20 5import { addConfigurationKey, type ChargingStation } from '../../../charging-station/index.js'
66a7748d 6import { OCPPError } from '../../../exception/index.js'
b3fc3ff5 7import {
50539084 8 ChargingStationEvents,
268a74bb 9 ErrorType,
268a74bb 10 type JsonType,
268a74bb
JB
11 type OCPP20BootNotificationResponse,
12 type OCPP20ClearCacheResponse,
13 type OCPP20HeartbeatResponse,
b3fc3ff5 14 OCPP20IncomingRequestCommand,
857d8dd9 15 OCPP20OptionalVariableName,
b3fc3ff5 16 OCPP20RequestCommand,
268a74bb
JB
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
66a7748d
JB
20 type ResponseHandler
21} from '../../../types/index.js'
bcf95df1 22import { isAsyncFunction, logger } from '../../../utils/index.js'
66a7748d 23import { OCPPResponseService } from '../OCPPResponseService.js'
4c3f6c20 24import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
953d6b02 25
66a7748d 26const moduleName = 'OCPP20ResponseService'
953d6b02 27
268a74bb 28export class OCPP20ResponseService extends OCPPResponseService {
d5490a13 29 public incomingRequestResponsePayloadValidateFunctions: Map<
66a7748d 30 OCPP20IncomingRequestCommand,
24d15716 31 ValidateFunction<JsonType>
66a7748d 32 >
b3fc3ff5 33
d5490a13 34 protected payloadValidateFunctions: Map<OCPP20RequestCommand, ValidateFunction<JsonType>>
66a7748d 35 private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
953d6b02 36
66a7748d 37 public constructor () {
5199f9fd
JB
38 // if (new.target.name === moduleName) {
39 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
b768993d 40 // }
1feac591 41 super(OCPPVersion.VERSION_201)
d270cc87 42 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
a37fc6dc
JB
43 [
44 OCPP20RequestCommand.BOOT_NOTIFICATION,
66a7748d 45 this.handleResponseBootNotification.bind(this) as ResponseHandler
a37fc6dc 46 ],
65b5177e
JB
47 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler],
48 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler]
66a7748d 49 ])
d5490a13 50 this.payloadValidateFunctions = new Map<OCPP20RequestCommand, ValidateFunction<JsonType>>([
d270cc87
JB
51 [
52 OCPP20RequestCommand.BOOT_NOTIFICATION,
24d15716
JB
53 this.ajv
54 .compile(
55 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
56 'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
57 moduleName,
58 'constructor'
59 )
60 )
61 .bind(this)
d270cc87 62 ],
81533a20
JB
63 [
64 OCPP20RequestCommand.HEARTBEAT,
24d15716
JB
65 this.ajv
66 .compile(
67 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
68 'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
69 moduleName,
70 'constructor'
71 )
72 )
73 .bind(this)
81533a20 74 ],
6e939d9e
JB
75 [
76 OCPP20RequestCommand.STATUS_NOTIFICATION,
24d15716
JB
77 this.ajv
78 .compile(
79 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
80 'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
81 moduleName,
82 'constructor'
83 )
84 )
85 .bind(this)
66a7748d
JB
86 ]
87 ])
d5490a13 88 this.incomingRequestResponsePayloadValidateFunctions = new Map<
24d15716
JB
89 OCPP20IncomingRequestCommand,
90 ValidateFunction<JsonType>
91 >([
02887891
JB
92 [
93 OCPP20IncomingRequestCommand.CLEAR_CACHE,
298be10c 94 this.ajvIncomingRequest
24d15716
JB
95 .compile(
96 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
97 'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
98 moduleName,
99 'constructor'
100 )
101 )
102 .bind(this)
66a7748d
JB
103 ]
104 ])
ba9a56a6 105 this.validatePayload = this.validatePayload.bind(this)
953d6b02
JB
106 }
107
9429aa42 108 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
953d6b02
JB
109 chargingStation: ChargingStation,
110 commandName: OCPP20RequestCommand,
9429aa42 111 payload: ResType,
66a7748d 112 requestPayload: ReqType
953d6b02 113 ): Promise<void> {
66a7748d 114 if (chargingStation.isRegistered() || commandName === OCPP20RequestCommand.BOOT_NOTIFICATION) {
953d6b02 115 if (
66a7748d
JB
116 this.responseHandlers.has(commandName) &&
117 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
953d6b02
JB
118 ) {
119 try {
66a7748d
JB
120 this.validatePayload(chargingStation, commandName, payload)
121 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
bcf95df1
JB
122 const responseHandler = this.responseHandlers.get(commandName)!
123 if (isAsyncFunction(responseHandler)) {
124 await responseHandler(chargingStation, payload, requestPayload)
125 } else {
126 (
127 responseHandler as (
128 chargingStation: ChargingStation,
129 payload: JsonType,
130 requestPayload?: JsonType
131 ) => void
132 )(chargingStation, payload, requestPayload)
133 }
953d6b02
JB
134 } catch (error) {
135 logger.error(
136 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
137 error
138 )
139 throw error
953d6b02
JB
140 }
141 } else {
142 // Throw exception
143 throw new OCPPError(
144 ErrorType.NOT_IMPLEMENTED,
3024d5b2 145 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
953d6b02 146 payload,
4ed03b6e 147 undefined,
66a7748d 148 2
953d6b02
JB
149 )}`,
150 commandName,
66a7748d
JB
151 payload
152 )
953d6b02
JB
153 }
154 } else {
155 throw new OCPPError(
156 ErrorType.SECURITY_ERROR,
157 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
158 payload,
4ed03b6e 159 undefined,
66a7748d 160 2
412cece8 161 )} while the charging station is not registered on the central server`,
953d6b02 162 commandName,
66a7748d
JB
163 payload
164 )
953d6b02
JB
165 }
166 }
167
66a7748d 168 private validatePayload (
953d6b02
JB
169 chargingStation: ChargingStation,
170 commandName: OCPP20RequestCommand,
66a7748d 171 payload: JsonType
953d6b02 172 ): boolean {
d5490a13 173 if (this.payloadValidateFunctions.has(commandName)) {
24d15716 174 return this.validateResponsePayload(chargingStation, commandName, payload)
953d6b02
JB
175 }
176 logger.warn(
24d15716 177 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
178 )
179 return false
953d6b02 180 }
d270cc87 181
66a7748d 182 private handleResponseBootNotification (
d270cc87 183 chargingStation: ChargingStation,
66a7748d 184 payload: OCPP20BootNotificationResponse
d270cc87 185 ): void {
d270cc87 186 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
0320e2bb 187 chargingStation.bootNotificationResponse = payload
50539084
JB
188 if (chargingStation.isRegistered()) {
189 chargingStation.emit(ChargingStationEvents.registered)
190 if (chargingStation.inAcceptedState()) {
d627f8ef
JB
191 addConfigurationKey(
192 chargingStation,
193 OCPP20OptionalVariableName.HeartbeatInterval,
194 payload.interval.toString(),
195 {},
196 { overwrite: true, save: true }
197 )
99100f9c 198 chargingStation.emit(ChargingStationEvents.accepted)
50539084
JB
199 }
200 } else if (chargingStation.inRejectedState()) {
201 chargingStation.emit(ChargingStationEvents.rejected)
202 }
d270cc87
JB
203 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
204 payload.status
66a7748d 205 }' state on the central server`
d270cc87
JB
206 payload.status === RegistrationStatusEnumType.REJECTED
207 ? logger.warn(logMsg)
66a7748d 208 : logger.info(logMsg)
d270cc87 209 } else {
18c6df2b 210 delete chargingStation.bootNotificationResponse
d270cc87 211 logger.error(
44eb6026 212 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
213 payload
214 )
d270cc87
JB
215 }
216 }
953d6b02 217}