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