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