refactor(simulator): switch to named exports
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 2.0 / OCPP20ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
2
3 import type { JSONSchemaType } from 'ajv';
4
5 import { OCPP20ServiceUtils } from './OCPP20ServiceUtils';
6 import { OCPPError } from '../../../exception';
7 import {
8 ErrorType,
9 type JsonObject,
10 type JsonType,
11 OCPP16StandardParametersKey,
12 type OCPP20BootNotificationResponse,
13 type OCPP20ClearCacheResponse,
14 type OCPP20HeartbeatResponse,
15 OCPP20IncomingRequestCommand,
16 OCPP20RequestCommand,
17 type OCPP20StatusNotificationResponse,
18 OCPPVersion,
19 RegistrationStatusEnumType,
20 type ResponseHandler,
21 } from '../../../types';
22 import { logger } from '../../../utils/Logger';
23 import type { ChargingStation } from '../../ChargingStation';
24 import { ChargingStationConfigurationUtils } from '../../ChargingStationConfigurationUtils';
25 import { OCPPResponseService } from '../OCPPResponseService';
26
27 const moduleName = 'OCPP20ResponseService';
28
29 export class OCPP20ResponseService extends OCPPResponseService {
30 public jsonIncomingRequestResponseSchemas: Map<
31 OCPP20IncomingRequestCommand,
32 JSONSchemaType<JsonObject>
33 >;
34
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 }
42 super(OCPPVersion.VERSION_20);
43 this.responseHandlers = new Map<OCPP20RequestCommand, ResponseHandler>([
44 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
45 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
46 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
47 ]);
48 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
49 [
50 OCPP20RequestCommand.BOOT_NOTIFICATION,
51 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
52 '../../../assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
53 moduleName,
54 'constructor'
55 ),
56 ],
57 [
58 OCPP20RequestCommand.HEARTBEAT,
59 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
60 '../../../assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
61 moduleName,
62 'constructor'
63 ),
64 ],
65 [
66 OCPP20RequestCommand.STATUS_NOTIFICATION,
67 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
68 '../../../assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
69 moduleName,
70 'constructor'
71 ),
72 ],
73 ]);
74 this.jsonIncomingRequestResponseSchemas = new Map([
75 [
76 OCPP20IncomingRequestCommand.CLEAR_CACHE,
77 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
78 '../../../assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
79 moduleName,
80 'constructor'
81 ),
82 ],
83 ]);
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 (
94 chargingStation.isRegistered() === true ||
95 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
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
131 )} while the charging station is not registered on the central server.`,
132 commandName,
133 payload
134 );
135 }
136 }
137
138 private validatePayload(
139 chargingStation: ChargingStation,
140 commandName: OCPP20RequestCommand,
141 payload: JsonType
142 ): boolean {
143 if (this.jsonSchemas.has(commandName) === true) {
144 return this.validateResponsePayload(
145 chargingStation,
146 commandName,
147 this.jsonSchemas.get(commandName),
148 payload
149 );
150 }
151 logger.warn(
152 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
153 );
154 return false;
155 }
156
157 private handleResponseBootNotification(
158 chargingStation: ChargingStation,
159 payload: OCPP20BootNotificationResponse
160 ): void {
161 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
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 );
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(
189 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
190 payload
191 );
192 }
193 }
194 }