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