build(deps-dev): apply updates
[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 52 moduleName,
5edd8ba0 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 60 moduleName,
5edd8ba0 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 68 moduleName,
5edd8ba0 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 78 moduleName,
5edd8ba0 79 'constructor',
e9a4164c 80 ),
02887891
JB
81 ],
82 ]);
31f59c6d
JB
83 this.validatePayload = this.validatePayload.bind(this) as (
84 chargingStation: ChargingStation,
85 commandName: OCPP20RequestCommand,
5edd8ba0 86 payload: JsonType,
31f59c6d 87 ) => boolean;
953d6b02
JB
88 }
89
90 public async responseHandler(
91 chargingStation: ChargingStation,
92 commandName: OCPP20RequestCommand,
93 payload: JsonType,
5edd8ba0 94 requestPayload: JsonType,
953d6b02
JB
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:`,
5edd8ba0 110 error,
953d6b02
JB
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,
5edd8ba0 121 2,
953d6b02
JB
122 )}`,
123 commandName,
5edd8ba0 124 payload,
953d6b02
JB
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,
5edd8ba0 133 2,
439fc71b 134 )} while the charging station is not registered on the central server.`,
953d6b02 135 commandName,
5edd8ba0 136 payload,
953d6b02
JB
137 );
138 }
139 }
140
141 private validatePayload(
142 chargingStation: ChargingStation,
143 commandName: OCPP20RequestCommand,
5edd8ba0 144 payload: JsonType,
953d6b02 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),
5edd8ba0 151 payload,
953d6b02
JB
152 );
153 }
154 logger.warn(
5edd8ba0 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,
5edd8ba0 162 payload: OCPP20BootNotificationResponse,
d270cc87
JB
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 {},
5edd8ba0 170 { overwrite: true, save: true },
268a74bb 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`,
5edd8ba0 184 payload,
d270cc87
JB
185 );
186 }
187 }
953d6b02 188}