refactor: switch eslint configuration to strict type checking
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import type { JSONSchemaType } from 'ajv'
4
5 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
6 import { type ChargingStation, addConfigurationKey } from '../../../charging-station/index.js'
7 import { OCPPError } from '../../../exception/index.js'
8 import {
9 ErrorType,
10 type JsonType,
11 type OCPP20BootNotificationResponse,
12 type OCPP20ClearCacheResponse,
13 type OCPP20HeartbeatResponse,
14 OCPP20IncomingRequestCommand,
15 OCPP20OptionalVariableName,
16 OCPP20RequestCommand,
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
20 type ResponseHandler
21 } from '../../../types/index.js'
22 import { logger } from '../../../utils/index.js'
23 import { OCPPResponseService } from '../OCPPResponseService.js'
24
25 const moduleName = 'OCPP20ResponseService'
26
27 export class OCPP20ResponseService extends OCPPResponseService {
28 public jsonIncomingRequestResponseSchemas: Map<
29 OCPP20IncomingRequestCommand,
30 JSONSchemaType<JsonType>
31 >
32
33 private readonly responseHandlers: Map<OCPP20RequestCommand, ResponseHandler>
34 private readonly jsonSchemas: Map<OCPP20RequestCommand, JSONSchemaType<JsonType>>
35
36 public constructor () {
37 // if (new.target.name === moduleName) {
38 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
39 // }
40 super(OCPPVersion.VERSION_20)
41 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
42 [
43 OCPP20RequestCommand.BOOT_NOTIFICATION,
44 this.handleResponseBootNotification.bind(this) as ResponseHandler
45 ],
46 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this) as ResponseHandler],
47 [
48 OCPP20RequestCommand.STATUS_NOTIFICATION,
49 this.emptyResponseHandler.bind(this) as ResponseHandler
50 ]
51 ])
52 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonType>>([
53 [
54 OCPP20RequestCommand.BOOT_NOTIFICATION,
55 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
56 'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
57 moduleName,
58 'constructor'
59 )
60 ],
61 [
62 OCPP20RequestCommand.HEARTBEAT,
63 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
64 'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
65 moduleName,
66 'constructor'
67 )
68 ],
69 [
70 OCPP20RequestCommand.STATUS_NOTIFICATION,
71 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
72 'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
73 moduleName,
74 'constructor'
75 )
76 ]
77 ])
78 this.jsonIncomingRequestResponseSchemas = new Map([
79 [
80 OCPP20IncomingRequestCommand.CLEAR_CACHE,
81 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
82 'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
83 moduleName,
84 'constructor'
85 )
86 ]
87 ])
88 this.validatePayload = this.validatePayload.bind(this) as (
89 chargingStation: ChargingStation,
90 commandName: OCPP20RequestCommand,
91 payload: JsonType
92 ) => boolean
93 }
94
95 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
96 chargingStation: ChargingStation,
97 commandName: OCPP20RequestCommand,
98 payload: ResType,
99 requestPayload: ReqType
100 ): Promise<void> {
101 if (chargingStation.isRegistered() || commandName === OCPP20RequestCommand.BOOT_NOTIFICATION) {
102 if (
103 this.responseHandlers.has(commandName) &&
104 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
105 ) {
106 try {
107 this.validatePayload(chargingStation, commandName, payload)
108 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
110 } catch (error) {
111 logger.error(
112 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
113 error
114 )
115 throw error
116 }
117 } else {
118 // Throw exception
119 throw new OCPPError(
120 ErrorType.NOT_IMPLEMENTED,
121 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
122 payload,
123 undefined,
124 2
125 )}`,
126 commandName,
127 payload
128 )
129 }
130 } else {
131 throw new OCPPError(
132 ErrorType.SECURITY_ERROR,
133 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
134 payload,
135 undefined,
136 2
137 )} while the charging station is not registered on the central server.`,
138 commandName,
139 payload
140 )
141 }
142 }
143
144 private validatePayload (
145 chargingStation: ChargingStation,
146 commandName: OCPP20RequestCommand,
147 payload: JsonType
148 ): boolean {
149 if (this.jsonSchemas.has(commandName)) {
150 return this.validateResponsePayload(
151 chargingStation,
152 commandName,
153 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154 this.jsonSchemas.get(commandName)!,
155 payload
156 )
157 }
158 logger.warn(
159 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
160 )
161 return false
162 }
163
164 private handleResponseBootNotification (
165 chargingStation: ChargingStation,
166 payload: OCPP20BootNotificationResponse
167 ): void {
168 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
169 addConfigurationKey(
170 chargingStation,
171 OCPP20OptionalVariableName.HeartbeatInterval,
172 payload.interval.toString(),
173 {},
174 { overwrite: true, save: true }
175 )
176 OCPP20ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
177 }
178 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
179 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
180 payload.status
181 }' state on the central server`
182 payload.status === RegistrationStatusEnumType.REJECTED
183 ? logger.warn(logMsg)
184 : logger.info(logMsg)
185 } else {
186 logger.error(
187 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
188 payload
189 )
190 }
191 }
192 }