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