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