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