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