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