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