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