Merge pull request #574 from JulianHBuecher/reservation-feature
[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';
2896e06d 6import { type ChargingStation, ChargingStationConfigurationUtils } 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>([
43 [OCPP20RequestCommand.BOOT_NOTIFICATION, this.handleResponseBootNotification.bind(this)],
81533a20 44 [OCPP20RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this)],
6e939d9e 45 [OCPP20RequestCommand.STATUS_NOTIFICATION, this.emptyResponseHandler.bind(this)],
d270cc87
JB
46 ]);
47 this.jsonSchemas = new Map<OCPP20RequestCommand, JSONSchemaType<JsonObject>>([
48 [
49 OCPP20RequestCommand.BOOT_NOTIFICATION,
130783a7 50 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20BootNotificationResponse>(
51022aa0 51 'assets/json-schemas/ocpp/2.0/BootNotificationResponse.json',
1b271a54
JB
52 moduleName,
53 'constructor'
e9a4164c 54 ),
d270cc87 55 ],
81533a20
JB
56 [
57 OCPP20RequestCommand.HEARTBEAT,
130783a7 58 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20HeartbeatResponse>(
51022aa0 59 'assets/json-schemas/ocpp/2.0/HeartbeatResponse.json',
1b271a54
JB
60 moduleName,
61 'constructor'
e9a4164c 62 ),
81533a20 63 ],
6e939d9e
JB
64 [
65 OCPP20RequestCommand.STATUS_NOTIFICATION,
130783a7 66 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20StatusNotificationResponse>(
51022aa0 67 'assets/json-schemas/ocpp/2.0/StatusNotificationResponse.json',
1b271a54
JB
68 moduleName,
69 'constructor'
e9a4164c 70 ),
6e939d9e 71 ],
d270cc87 72 ]);
02887891
JB
73 this.jsonIncomingRequestResponseSchemas = new Map([
74 [
75 OCPP20IncomingRequestCommand.CLEAR_CACHE,
130783a7 76 OCPP20ServiceUtils.parseJsonSchemaFile<OCPP20ClearCacheResponse>(
51022aa0 77 'assets/json-schemas/ocpp/2.0/ClearCacheResponse.json',
1b271a54
JB
78 moduleName,
79 'constructor'
e9a4164c 80 ),
02887891
JB
81 ],
82 ]);
31f59c6d
JB
83 this.validatePayload = this.validatePayload.bind(this) as (
84 chargingStation: ChargingStation,
85 commandName: OCPP20RequestCommand,
86 payload: JsonType
87 ) => boolean;
953d6b02
JB
88 }
89
90 public async responseHandler(
91 chargingStation: ChargingStation,
92 commandName: OCPP20RequestCommand,
93 payload: JsonType,
94 requestPayload: JsonType
95 ): Promise<void> {
96 if (
d270cc87
JB
97 chargingStation.isRegistered() === true ||
98 commandName === OCPP20RequestCommand.BOOT_NOTIFICATION
953d6b02
JB
99 ) {
100 if (
101 this.responseHandlers.has(commandName) === true &&
102 OCPP20ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
103 ) {
104 try {
105 this.validatePayload(chargingStation, commandName, payload);
106 await this.responseHandlers.get(commandName)(chargingStation, payload, requestPayload);
107 } catch (error) {
108 logger.error(
109 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
110 error
111 );
112 throw error;
113 }
114 } else {
115 // Throw exception
116 throw new OCPPError(
117 ErrorType.NOT_IMPLEMENTED,
118 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
119 payload,
120 null,
121 2
122 )}`,
123 commandName,
124 payload
125 );
126 }
127 } else {
128 throw new OCPPError(
129 ErrorType.SECURITY_ERROR,
130 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
131 payload,
132 null,
133 2
439fc71b 134 )} while the charging station is not registered on the central server.`,
953d6b02
JB
135 commandName,
136 payload
137 );
138 }
139 }
140
141 private validatePayload(
142 chargingStation: ChargingStation,
143 commandName: OCPP20RequestCommand,
144 payload: JsonType
145 ): boolean {
45988780 146 if (this.jsonSchemas.has(commandName) === true) {
953d6b02
JB
147 return this.validateResponsePayload(
148 chargingStation,
149 commandName,
150 this.jsonSchemas.get(commandName),
151 payload
152 );
153 }
154 logger.warn(
b3fc3ff5 155 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
953d6b02
JB
156 );
157 return false;
158 }
d270cc87
JB
159
160 private handleResponseBootNotification(
161 chargingStation: ChargingStation,
162 payload: OCPP20BootNotificationResponse
163 ): void {
164 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
268a74bb
JB
165 ChargingStationConfigurationUtils.addConfigurationKey(
166 chargingStation,
857d8dd9 167 OCPP20OptionalVariableName.HeartbeatInterval,
268a74bb 168 payload.interval.toString(),
abe9e9dd 169 {},
268a74bb
JB
170 { overwrite: true, save: true }
171 );
8f953431 172 OCPP20ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval);
d270cc87
JB
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 }
953d6b02 188}