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