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