Consistent naming for CS template enums
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCCP16IncomingRequestService.ts
CommitLineData
c0560973 1import { ChangeAvailabilityRequest, ChangeConfigurationRequest, ClearChargingProfileRequest, GetConfigurationRequest, OCPP16AvailabilityType, OCPP16IncomingRequestCommand, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, SetChargingProfileRequest, UnlockConnectorRequest } from '../../../types/ocpp/1.6/Requests';
9ccca265 2import { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, GetConfigurationResponse, SetChargingProfileResponse, UnlockConnectorResponse } from '../../../types/ocpp/1.6/Responses';
c0560973
JB
3import { ChargingProfilePurposeType, OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile';
4import { OCPP16AuthorizationStatus, OCPP16StopTransactionReason } from '../../../types/ocpp/1.6/Transaction';
5
6import Constants from '../../../utils/Constants';
9ccca265 7import { DefaultResponse } from '../../../types/ocpp/Responses';
c0560973
JB
8import { ErrorType } from '../../../types/ocpp/ErrorType';
9import { MessageType } from '../../../types/ocpp/MessageType';
10import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
11import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
12import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
13import OCPPError from '../../OcppError';
14import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
15import Utils from '../../../utils/Utils';
16import logger from '../../../utils/Logger';
17
18export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
19 public async handleRequest(messageId: string, commandName: OCPP16IncomingRequestCommand, commandPayload: Record<string, unknown>): Promise<void> {
20 let response;
35ff6ec4 21 const methodName = `handleRequest${commandName}`;
c0560973 22 // Call
35ff6ec4 23 if (typeof this[methodName] === 'function') {
c0560973
JB
24 try {
25 // Call the method to build the response
35ff6ec4 26 response = await this[methodName](commandPayload);
c0560973
JB
27 } catch (error) {
28 // Log
29 logger.error(this.chargingStation.logPrefix() + ' Handle request error: %j', error);
a4cc42ea 30 // Send back an error response to inform backend
c0560973
JB
31 await this.chargingStation.ocppRequestService.sendError(messageId, error, commandName);
32 throw error;
33 }
34 } else {
35 // Throw exception
36 await this.chargingStation.ocppRequestService.sendError(messageId, new OCPPError(ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented`, {}), commandName);
37 throw new Error(`${commandName} is not implemented to handle payload ${JSON.stringify(commandPayload)}`);
38 }
39 // Send the built response
40 await this.chargingStation.ocppRequestService.sendMessage(messageId, response, MessageType.CALL_RESULT_MESSAGE, commandName);
41 }
42
43 // Simulate charging station restart
44 private handleRequestReset(commandPayload: ResetRequest): DefaultResponse {
71623267
JB
45 // eslint-disable-next-line @typescript-eslint/no-misused-promises
46 setImmediate(async (): Promise<void> => {
c0560973
JB
47 await this.chargingStation.stop(commandPayload.type + 'Reset' as OCPP16StopTransactionReason);
48 await Utils.sleep(this.chargingStation.stationInfo.resetTime);
71623267 49 this.chargingStation.start();
c0560973
JB
50 });
51 logger.info(`${this.chargingStation.logPrefix()} ${commandPayload.type} reset command received, simulating it. The station will be back online in ${Utils.milliSecondsToHHMMSS(this.chargingStation.stationInfo.resetTime)}`);
52 return Constants.OCPP_RESPONSE_ACCEPTED;
53 }
54
55 private handleRequestClearCache(): DefaultResponse {
56 return Constants.OCPP_RESPONSE_ACCEPTED;
57 }
58
59 private async handleRequestUnlockConnector(commandPayload: UnlockConnectorRequest): Promise<UnlockConnectorResponse> {
60 const connectorId = commandPayload.connectorId;
61 if (connectorId === 0) {
62 logger.error(this.chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString());
63 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
64 }
65 if (this.chargingStation.getConnector(connectorId)?.transactionStarted) {
66 const transactionId = this.chargingStation.getConnector(connectorId).transactionId;
6ed92bc1
JB
67 const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
68 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
69 this.chargingStation.getTransactionIdTag(transactionId),
70 OCPP16StopTransactionReason.UNLOCK_COMMAND);
c0560973
JB
71 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
72 return Constants.OCPP_RESPONSE_UNLOCKED;
73 }
74 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
75 }
76 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.AVAILABLE);
77 this.chargingStation.getConnector(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
78 return Constants.OCPP_RESPONSE_UNLOCKED;
79 }
80
81 private handleRequestGetConfiguration(commandPayload: GetConfigurationRequest): GetConfigurationResponse {
82 const configurationKey: OCPPConfigurationKey[] = [];
83 const unknownKey: string[] = [];
84 if (Utils.isEmptyArray(commandPayload.key)) {
85 for (const configuration of this.chargingStation.configuration.configurationKey) {
86 if (Utils.isUndefined(configuration.visible)) {
87 configuration.visible = true;
88 }
89 if (!configuration.visible) {
90 continue;
91 }
92 configurationKey.push({
93 key: configuration.key,
94 readonly: configuration.readonly,
95 value: configuration.value,
96 });
97 }
98 } else {
99 for (const key of commandPayload.key) {
100 const keyFound = this.chargingStation.getConfigurationKey(key);
101 if (keyFound) {
102 if (Utils.isUndefined(keyFound.visible)) {
103 keyFound.visible = true;
104 }
105 if (!keyFound.visible) {
106 continue;
107 }
108 configurationKey.push({
109 key: keyFound.key,
110 readonly: keyFound.readonly,
111 value: keyFound.value,
112 });
113 } else {
114 unknownKey.push(key);
115 }
116 }
117 }
118 return {
119 configurationKey,
120 unknownKey,
121 };
122 }
123
124 private handleRequestChangeConfiguration(commandPayload: ChangeConfigurationRequest): ChangeConfigurationResponse {
125 // JSON request fields type sanity check
126 if (!Utils.isString(commandPayload.key)) {
127 logger.error(`${this.chargingStation.logPrefix()} ChangeConfiguration request key field is not a string:`, commandPayload);
128 }
129 if (!Utils.isString(commandPayload.value)) {
130 logger.error(`${this.chargingStation.logPrefix()} ChangeConfiguration request value field is not a string:`, commandPayload);
131 }
132 const keyToChange = this.chargingStation.getConfigurationKey(commandPayload.key, true);
133 if (!keyToChange) {
134 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
135 } else if (keyToChange && keyToChange.readonly) {
136 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
137 } else if (keyToChange && !keyToChange.readonly) {
138 const keyIndex = this.chargingStation.configuration.configurationKey.indexOf(keyToChange);
139 let valueChanged = false;
140 if (this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value) {
141 this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
142 valueChanged = true;
143 }
144 let triggerHeartbeatRestart = false;
145 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
146 this.chargingStation.setConfigurationKeyValue(OCPP16StandardParametersKey.HeartbeatInterval, commandPayload.value);
147 triggerHeartbeatRestart = true;
148 }
149 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
150 this.chargingStation.setConfigurationKeyValue(OCPP16StandardParametersKey.HeartBeatInterval, commandPayload.value);
151 triggerHeartbeatRestart = true;
152 }
153 if (triggerHeartbeatRestart) {
154 this.chargingStation.restartHeartbeat();
155 }
156 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
157 this.chargingStation.restartWebSocketPing();
158 }
159 if (keyToChange.reboot) {
160 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
161 }
162 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
163 }
164 }
165
166 private handleRequestSetChargingProfile(commandPayload: SetChargingProfileRequest): SetChargingProfileResponse {
167 if (!this.chargingStation.getConnector(commandPayload.connectorId)) {
a4cc42ea 168 logger.error(`${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${commandPayload.connectorId}`);
c0560973
JB
169 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
170 }
171 if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE && commandPayload.connectorId !== 0) {
172 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
173 }
174 if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE && (commandPayload.connectorId === 0 || !this.chargingStation.getConnector(commandPayload.connectorId)?.transactionStarted)) {
175 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
176 }
177 this.chargingStation.setChargingProfile(commandPayload.connectorId, commandPayload.csChargingProfiles);
a4cc42ea 178 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`, this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles);
c0560973
JB
179 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
180 }
181
182 private handleRequestClearChargingProfile(commandPayload: ClearChargingProfileRequest): ClearChargingProfileResponse {
183 if (!this.chargingStation.getConnector(commandPayload.connectorId)) {
a4cc42ea 184 logger.error(`${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${commandPayload.connectorId}`);
c0560973
JB
185 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
186 }
187 if (commandPayload.connectorId && !Utils.isEmptyArray(this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles)) {
188 this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles = [];
a4cc42ea 189 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles);
c0560973
JB
190 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
191 }
192 if (!commandPayload.connectorId) {
193 let clearedCP = false;
194 for (const connector in this.chargingStation.connectors) {
195 if (!Utils.isEmptyArray(this.chargingStation.getConnector(Utils.convertToInt(connector)).chargingProfiles)) {
6e0964c8 196 this.chargingStation.getConnector(Utils.convertToInt(connector)).chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
c0560973
JB
197 let clearCurrentCP = false;
198 if (chargingProfile.chargingProfileId === commandPayload.id) {
199 clearCurrentCP = true;
200 }
201 if (!commandPayload.chargingProfilePurpose && chargingProfile.stackLevel === commandPayload.stackLevel) {
202 clearCurrentCP = true;
203 }
204 if (!chargingProfile.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
205 clearCurrentCP = true;
206 }
207 if (chargingProfile.stackLevel === commandPayload.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
208 clearCurrentCP = true;
209 }
210 if (clearCurrentCP) {
211 this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles[index] = {} as OCPP16ChargingProfile;
a4cc42ea 212 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles);
c0560973
JB
213 clearedCP = true;
214 }
215 });
216 }
217 }
218 if (clearedCP) {
219 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
220 }
221 }
222 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
223 }
224
e268356b 225 private async handleRequestChangeAvailability(commandPayload: ChangeAvailabilityRequest): Promise<ChangeAvailabilityResponse> {
c0560973
JB
226 const connectorId: number = commandPayload.connectorId;
227 if (!this.chargingStation.getConnector(connectorId)) {
228 logger.error(`${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`);
229 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
230 }
231 const chargePointStatus: OCPP16ChargePointStatus = commandPayload.type === OCPP16AvailabilityType.OPERATIVE ? OCPP16ChargePointStatus.AVAILABLE : OCPP16ChargePointStatus.UNAVAILABLE;
232 if (connectorId === 0) {
233 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
234 for (const connector in this.chargingStation.connectors) {
235 if (this.chargingStation.getConnector(Utils.convertToInt(connector)).transactionStarted) {
236 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
237 }
238 this.chargingStation.getConnector(Utils.convertToInt(connector)).availability = commandPayload.type;
239 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
e268356b 240 await this.chargingStation.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), chargePointStatus);
c0560973
JB
241 this.chargingStation.getConnector(Utils.convertToInt(connector)).status = chargePointStatus;
242 }
243 }
244 return response;
245 } else if (connectorId > 0 && (this.chargingStation.getConnector(0).availability === OCPP16AvailabilityType.OPERATIVE || (this.chargingStation.getConnector(0).availability === OCPP16AvailabilityType.INOPERATIVE && commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))) {
246 if (this.chargingStation.getConnector(connectorId)?.transactionStarted) {
247 this.chargingStation.getConnector(connectorId).availability = commandPayload.type;
248 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
249 }
250 this.chargingStation.getConnector(connectorId).availability = commandPayload.type;
e268356b 251 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, chargePointStatus);
c0560973
JB
252 this.chargingStation.getConnector(connectorId).status = chargePointStatus;
253 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
254 }
255 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
256 }
257
258 private async handleRequestRemoteStartTransaction(commandPayload: RemoteStartTransactionRequest): Promise<DefaultResponse> {
5a20b4fd
JB
259 const transactionConnectorId: number = commandPayload.connectorId ?? 1;
260 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.PREPARING);
261 this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.PREPARING;
262 if (this.chargingStation.isChargingStationAvailable() && this.chargingStation.isConnectorAvailable(transactionConnectorId)) {
36f6a92e
JB
263 if (this.chargingStation.getAuthorizeRemoteTxRequests()) {
264 let authorized = false;
c0560973 265 // Check if authorized
36f6a92e
JB
266 if (this.chargingStation.getLocalAuthListEnabled() && this.chargingStation.hasAuthorizedTags()
267 && this.chargingStation.authorizedTags.find((value) => value === commandPayload.idTag)) {
268 authorized = true;
c0560973 269 if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
5a20b4fd
JB
270 this.chargingStation.setChargingProfile(transactionConnectorId, commandPayload.chargingProfile);
271 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`, this.chargingStation.getConnector(transactionConnectorId).chargingProfiles);
c0560973 272 } else if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
5a20b4fd
JB
273 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.AVAILABLE);
274 this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.AVAILABLE;
36f6a92e 275 logger.warn(`${this.chargingStation.logPrefix()} Not allowed to set ${commandPayload.chargingProfile.chargingProfilePurpose} charging profile(s) at remote start transaction`);
c0560973
JB
276 return Constants.OCPP_RESPONSE_REJECTED;
277 }
36f6a92e
JB
278 }
279 if (!authorized) {
5a20b4fd 280 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(transactionConnectorId, commandPayload.idTag);
36f6a92e
JB
281 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
282 authorized = true;
283 }
284 }
285 if (authorized) {
286 // Authorization successful, start transaction
5a20b4fd
JB
287 await this.chargingStation.ocppRequestService.sendStartTransaction(transactionConnectorId, commandPayload.idTag);
288 logger.debug(this.chargingStation.logPrefix() + ' Transaction remotely STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + transactionConnectorId.toString() + ' for idTag ' + commandPayload.idTag);
c0560973
JB
289 return Constants.OCPP_RESPONSE_ACCEPTED;
290 }
5a20b4fd
JB
291 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.AVAILABLE);
292 this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.AVAILABLE;
293 logger.warn(this.chargingStation.logPrefix() + ' Remote starting transaction REJECTED on connector Id ' + transactionConnectorId.toString() + ', idTag ' + commandPayload.idTag);
c0560973
JB
294 return Constants.OCPP_RESPONSE_REJECTED;
295 }
c0560973 296 if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
5a20b4fd 297 this.chargingStation.setChargingProfile(transactionConnectorId, commandPayload.chargingProfile);
36f6a92e 298 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`, this.chargingStation.getConnector(commandPayload.connectorId).chargingProfiles);
c0560973 299 } else if (commandPayload.chargingProfile && commandPayload.chargingProfile.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
5a20b4fd
JB
300 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.AVAILABLE);
301 this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.AVAILABLE;
36f6a92e 302 logger.warn(`${this.chargingStation.logPrefix()} Not allowed to set ${commandPayload.chargingProfile.chargingProfilePurpose} charging profile(s) at remote start transaction`);
c0560973
JB
303 return Constants.OCPP_RESPONSE_REJECTED;
304 }
36f6a92e 305 // No authorization check required, start transaction
5a20b4fd
JB
306 await this.chargingStation.ocppRequestService.sendStartTransaction(transactionConnectorId, commandPayload.idTag);
307 logger.debug(this.chargingStation.logPrefix() + ' Transaction remotely STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + transactionConnectorId.toString() + ' for idTag ' + commandPayload.idTag);
c0560973
JB
308 return Constants.OCPP_RESPONSE_ACCEPTED;
309 }
5a20b4fd
JB
310 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.AVAILABLE);
311 this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.AVAILABLE;
312 logger.warn(this.chargingStation.logPrefix() + ' Remote starting transaction REJECTED on unavailable connector Id ' + transactionConnectorId.toString() + ', idTag ' + commandPayload.idTag);
c0560973
JB
313 return Constants.OCPP_RESPONSE_REJECTED;
314 }
315
316 private async handleRequestRemoteStopTransaction(commandPayload: RemoteStopTransactionRequest): Promise<DefaultResponse> {
317 const transactionId = commandPayload.transactionId;
318 for (const connector in this.chargingStation.connectors) {
319 if (Utils.convertToInt(connector) > 0 && this.chargingStation.getConnector(Utils.convertToInt(connector))?.transactionId === transactionId) {
320 await this.chargingStation.ocppRequestService.sendStatusNotification(Utils.convertToInt(connector), OCPP16ChargePointStatus.FINISHING);
321 this.chargingStation.getConnector(Utils.convertToInt(connector)).status = OCPP16ChargePointStatus.FINISHING;
6ed92bc1 322 await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
035742f7 323 this.chargingStation.getTransactionIdTag(transactionId));
c0560973
JB
324 return Constants.OCPP_RESPONSE_ACCEPTED;
325 }
326 }
327 logger.info(this.chargingStation.logPrefix() + ' Trying to remote stop a non existing transaction ' + transactionId.toString());
328 return Constants.OCPP_RESPONSE_REJECTED;
329 }
330}