package-lock.json: update
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
CommitLineData
c8eeb62b
JB
1// Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
2
58144adb 3import { ChangeAvailabilityRequest, ChangeConfigurationRequest, ClearChargingProfileRequest, GetConfigurationRequest, GetDiagnosticsRequest, MessageTrigger, OCPP16AvailabilityType, OCPP16IncomingRequestCommand, OCPP16RequestCommand, OCPP16TriggerMessageRequest, RemoteStartTransactionRequest, RemoteStopTransactionRequest, ResetRequest, SetChargingProfileRequest, UnlockConnectorRequest } from '../../../types/ocpp/1.6/Requests';
802cfa13 4import { ChangeAvailabilityResponse, ChangeConfigurationResponse, ClearChargingProfileResponse, GetConfigurationResponse, GetDiagnosticsResponse, OCPP16TriggerMessageResponse, SetChargingProfileResponse, UnlockConnectorResponse } from '../../../types/ocpp/1.6/Responses';
c0560973 5import { ChargingProfilePurposeType, OCPP16ChargingProfile } from '../../../types/ocpp/1.6/ChargingProfile';
47e22477 6import { Client, FTPResponse } from 'basic-ftp';
c0560973
JB
7import { OCPP16AuthorizationStatus, OCPP16StopTransactionReason } from '../../../types/ocpp/1.6/Transaction';
8
58144adb 9import ChargingStation from '../../ChargingStation';
c0560973 10import Constants from '../../../utils/Constants';
9ccca265 11import { DefaultResponse } from '../../../types/ocpp/Responses';
c0560973 12import { ErrorType } from '../../../types/ocpp/ErrorType';
58144adb 13import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
c0560973
JB
14import { MessageType } from '../../../types/ocpp/MessageType';
15import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
47e22477 16import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
c0560973
JB
17import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
18import { OCPPConfigurationKey } from '../../../types/ocpp/Configuration';
14763b46 19import OCPPError from '../OCPPError';
c0560973 20import OCPPIncomingRequestService from '../OCPPIncomingRequestService';
a3868ec4 21import { URL } from 'url';
c0560973 22import Utils from '../../../utils/Utils';
47e22477 23import fs from 'fs';
c0560973 24import logger from '../../../utils/Logger';
47e22477
JB
25import path from 'path';
26import tar from 'tar';
c0560973
JB
27
28export default class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
58144adb
JB
29 private incomingRequestHandlers: Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>;
30
31 constructor(chargingStation: ChargingStation) {
32 super(chargingStation);
33 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
34 [OCPP16IncomingRequestCommand.RESET, this.handleRequestReset.bind(this)],
35 [OCPP16IncomingRequestCommand.CLEAR_CACHE, this.handleRequestClearCache.bind(this)],
36 [OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR, this.handleRequestUnlockConnector.bind(this)],
37 [OCPP16IncomingRequestCommand.GET_CONFIGURATION, this.handleRequestGetConfiguration.bind(this)],
38 [OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION, this.handleRequestChangeConfiguration.bind(this)],
39 [OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE, this.handleRequestSetChargingProfile.bind(this)],
40 [OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE, this.handleRequestClearChargingProfile.bind(this)],
41 [OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY, this.handleRequestChangeAvailability.bind(this)],
42 [OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION, this.handleRequestRemoteStartTransaction.bind(this)],
43 [OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION, this.handleRequestRemoteStopTransaction.bind(this)],
734d790d 44 [OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, this.handleRequestGetDiagnostics.bind(this)],
58144adb
JB
45 [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)]
46 ]);
47 }
48
c0560973 49 public async handleRequest(messageId: string, commandName: OCPP16IncomingRequestCommand, commandPayload: Record<string, unknown>): Promise<void> {
6198eef3 50 let response: Record<string, unknown>;
58144adb 51 if (this.incomingRequestHandlers.has(commandName)) {
c0560973
JB
52 try {
53 // Call the method to build the response
58144adb 54 response = await this.incomingRequestHandlers.get(commandName)(commandPayload);
c0560973
JB
55 } catch (error) {
56 // Log
57 logger.error(this.chargingStation.logPrefix() + ' Handle request error: %j', error);
c0560973
JB
58 throw error;
59 }
60 } else {
61 // Throw exception
887fef76 62 throw new OCPPError(ErrorType.NOT_IMPLEMENTED, `${commandName} is not implemented to handle request payload ${JSON.stringify(commandPayload, null, 2)}`, commandName);
c0560973
JB
63 }
64 // Send the built response
65 await this.chargingStation.ocppRequestService.sendMessage(messageId, response, MessageType.CALL_RESULT_MESSAGE, commandName);
66 }
67
68 // Simulate charging station restart
69 private handleRequestReset(commandPayload: ResetRequest): DefaultResponse {
71623267
JB
70 // eslint-disable-next-line @typescript-eslint/no-misused-promises
71 setImmediate(async (): Promise<void> => {
c0560973
JB
72 await this.chargingStation.stop(commandPayload.type + 'Reset' as OCPP16StopTransactionReason);
73 await Utils.sleep(this.chargingStation.stationInfo.resetTime);
71623267 74 this.chargingStation.start();
c0560973 75 });
d7d1db72 76 logger.info(`${this.chargingStation.logPrefix()} ${commandPayload.type} reset command received, simulating it. The station will be back online in ${Utils.formatDurationMilliSeconds(this.chargingStation.stationInfo.resetTime)}`);
c0560973
JB
77 return Constants.OCPP_RESPONSE_ACCEPTED;
78 }
79
80 private handleRequestClearCache(): DefaultResponse {
81 return Constants.OCPP_RESPONSE_ACCEPTED;
82 }
83
84 private async handleRequestUnlockConnector(commandPayload: UnlockConnectorRequest): Promise<UnlockConnectorResponse> {
85 const connectorId = commandPayload.connectorId;
86 if (connectorId === 0) {
87 logger.error(this.chargingStation.logPrefix() + ' Trying to unlock connector ' + connectorId.toString());
88 return Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED;
89 }
734d790d
JB
90 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
91 const transactionId = this.chargingStation.getConnectorStatus(connectorId).transactionId;
6ed92bc1
JB
92 const stopResponse = await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId,
93 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
94 this.chargingStation.getTransactionIdTag(transactionId),
95 OCPP16StopTransactionReason.UNLOCK_COMMAND);
c0560973
JB
96 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
97 return Constants.OCPP_RESPONSE_UNLOCKED;
98 }
99 return Constants.OCPP_RESPONSE_UNLOCK_FAILED;
100 }
101 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.AVAILABLE);
734d790d 102 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
c0560973
JB
103 return Constants.OCPP_RESPONSE_UNLOCKED;
104 }
105
106 private handleRequestGetConfiguration(commandPayload: GetConfigurationRequest): GetConfigurationResponse {
107 const configurationKey: OCPPConfigurationKey[] = [];
108 const unknownKey: string[] = [];
109 if (Utils.isEmptyArray(commandPayload.key)) {
110 for (const configuration of this.chargingStation.configuration.configurationKey) {
111 if (Utils.isUndefined(configuration.visible)) {
112 configuration.visible = true;
113 }
114 if (!configuration.visible) {
115 continue;
116 }
117 configurationKey.push({
118 key: configuration.key,
119 readonly: configuration.readonly,
120 value: configuration.value,
121 });
122 }
123 } else {
124 for (const key of commandPayload.key) {
125 const keyFound = this.chargingStation.getConfigurationKey(key);
126 if (keyFound) {
127 if (Utils.isUndefined(keyFound.visible)) {
128 keyFound.visible = true;
129 }
130 if (!keyFound.visible) {
131 continue;
132 }
133 configurationKey.push({
134 key: keyFound.key,
135 readonly: keyFound.readonly,
136 value: keyFound.value,
137 });
138 } else {
139 unknownKey.push(key);
140 }
141 }
142 }
143 return {
144 configurationKey,
145 unknownKey,
146 };
147 }
148
149 private handleRequestChangeConfiguration(commandPayload: ChangeConfigurationRequest): ChangeConfigurationResponse {
150 // JSON request fields type sanity check
151 if (!Utils.isString(commandPayload.key)) {
58144adb 152 logger.error(`${this.chargingStation.logPrefix()} ${OCPP16RequestCommand.CHANGE_CONFIGURATION} request key field is not a string:`, commandPayload);
c0560973
JB
153 }
154 if (!Utils.isString(commandPayload.value)) {
58144adb 155 logger.error(`${this.chargingStation.logPrefix()} ${OCPP16RequestCommand.CHANGE_CONFIGURATION} request value field is not a string:`, commandPayload);
c0560973
JB
156 }
157 const keyToChange = this.chargingStation.getConfigurationKey(commandPayload.key, true);
158 if (!keyToChange) {
159 return Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED;
160 } else if (keyToChange && keyToChange.readonly) {
161 return Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED;
162 } else if (keyToChange && !keyToChange.readonly) {
163 const keyIndex = this.chargingStation.configuration.configurationKey.indexOf(keyToChange);
164 let valueChanged = false;
165 if (this.chargingStation.configuration.configurationKey[keyIndex].value !== commandPayload.value) {
166 this.chargingStation.configuration.configurationKey[keyIndex].value = commandPayload.value;
167 valueChanged = true;
168 }
169 let triggerHeartbeatRestart = false;
170 if (keyToChange.key === OCPP16StandardParametersKey.HeartBeatInterval && valueChanged) {
171 this.chargingStation.setConfigurationKeyValue(OCPP16StandardParametersKey.HeartbeatInterval, commandPayload.value);
172 triggerHeartbeatRestart = true;
173 }
174 if (keyToChange.key === OCPP16StandardParametersKey.HeartbeatInterval && valueChanged) {
175 this.chargingStation.setConfigurationKeyValue(OCPP16StandardParametersKey.HeartBeatInterval, commandPayload.value);
176 triggerHeartbeatRestart = true;
177 }
178 if (triggerHeartbeatRestart) {
179 this.chargingStation.restartHeartbeat();
180 }
181 if (keyToChange.key === OCPP16StandardParametersKey.WebSocketPingInterval && valueChanged) {
182 this.chargingStation.restartWebSocketPing();
183 }
184 if (keyToChange.reboot) {
185 return Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED;
186 }
187 return Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED;
188 }
189 }
190
191 private handleRequestSetChargingProfile(commandPayload: SetChargingProfileRequest): SetChargingProfileResponse {
734d790d 192 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
a4cc42ea 193 logger.error(`${this.chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector Id ${commandPayload.connectorId}`);
c0560973
JB
194 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
195 }
196 if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE && commandPayload.connectorId !== 0) {
197 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
198 }
734d790d 199 if (commandPayload.csChargingProfiles.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE && (commandPayload.connectorId === 0 || !this.chargingStation.getConnectorStatus(commandPayload.connectorId)?.transactionStarted)) {
c0560973
JB
200 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED;
201 }
202 this.chargingStation.setChargingProfile(commandPayload.connectorId, commandPayload.csChargingProfiles);
734d790d 203 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) set, dump their stack: %j`, this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles);
c0560973
JB
204 return Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED;
205 }
206
207 private handleRequestClearChargingProfile(commandPayload: ClearChargingProfileRequest): ClearChargingProfileResponse {
734d790d 208 if (!this.chargingStation.getConnectorStatus(commandPayload.connectorId)) {
a4cc42ea 209 logger.error(`${this.chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector Id ${commandPayload.connectorId}`);
c0560973
JB
210 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
211 }
734d790d
JB
212 if (commandPayload.connectorId && !Utils.isEmptyArray(this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles)) {
213 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles = [];
214 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles);
c0560973
JB
215 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
216 }
217 if (!commandPayload.connectorId) {
218 let clearedCP = false;
734d790d
JB
219 for (const connectorId of this.chargingStation.connectors.keys()) {
220 if (!Utils.isEmptyArray(this.chargingStation.getConnectorStatus(connectorId).chargingProfiles)) {
221 this.chargingStation.getConnectorStatus(connectorId).chargingProfiles?.forEach((chargingProfile: OCPP16ChargingProfile, index: number) => {
c0560973
JB
222 let clearCurrentCP = false;
223 if (chargingProfile.chargingProfileId === commandPayload.id) {
224 clearCurrentCP = true;
225 }
226 if (!commandPayload.chargingProfilePurpose && chargingProfile.stackLevel === commandPayload.stackLevel) {
227 clearCurrentCP = true;
228 }
229 if (!chargingProfile.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
230 clearCurrentCP = true;
231 }
232 if (chargingProfile.stackLevel === commandPayload.stackLevel && chargingProfile.chargingProfilePurpose === commandPayload.chargingProfilePurpose) {
233 clearCurrentCP = true;
234 }
235 if (clearCurrentCP) {
734d790d
JB
236 this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles[index] = {} as OCPP16ChargingProfile;
237 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) cleared, dump their stack: %j`, this.chargingStation.getConnectorStatus(commandPayload.connectorId).chargingProfiles);
c0560973
JB
238 clearedCP = true;
239 }
240 });
241 }
242 }
243 if (clearedCP) {
244 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED;
245 }
246 }
247 return Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN;
248 }
249
e268356b 250 private async handleRequestChangeAvailability(commandPayload: ChangeAvailabilityRequest): Promise<ChangeAvailabilityResponse> {
c0560973 251 const connectorId: number = commandPayload.connectorId;
734d790d 252 if (!this.chargingStation.getConnectorStatus(connectorId)) {
c0560973
JB
253 logger.error(`${this.chargingStation.logPrefix()} Trying to change the availability of a non existing connector Id ${connectorId.toString()}`);
254 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
255 }
734d790d
JB
256 const chargePointStatus: OCPP16ChargePointStatus = commandPayload.type === OCPP16AvailabilityType.OPERATIVE
257 ? OCPP16ChargePointStatus.AVAILABLE
258 : OCPP16ChargePointStatus.UNAVAILABLE;
c0560973
JB
259 if (connectorId === 0) {
260 let response: ChangeAvailabilityResponse = Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
734d790d
JB
261 for (const id of this.chargingStation.connectors.keys()) {
262 if (this.chargingStation.getConnectorStatus(id)?.transactionStarted) {
c0560973
JB
263 response = Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
264 }
734d790d 265 this.chargingStation.getConnectorStatus(id).availability = commandPayload.type;
c0560973 266 if (response === Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED) {
734d790d
JB
267 await this.chargingStation.ocppRequestService.sendStatusNotification(id, chargePointStatus);
268 this.chargingStation.getConnectorStatus(id).status = chargePointStatus;
c0560973
JB
269 }
270 }
271 return response;
734d790d
JB
272 } else if (connectorId > 0 && (this.chargingStation.getConnectorStatus(0).availability === OCPP16AvailabilityType.OPERATIVE || (this.chargingStation.getConnectorStatus(0).availability === OCPP16AvailabilityType.INOPERATIVE && commandPayload.type === OCPP16AvailabilityType.INOPERATIVE))) {
273 if (this.chargingStation.getConnectorStatus(connectorId)?.transactionStarted) {
274 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
c0560973
JB
275 return Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED;
276 }
734d790d 277 this.chargingStation.getConnectorStatus(connectorId).availability = commandPayload.type;
e268356b 278 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, chargePointStatus);
734d790d 279 this.chargingStation.getConnectorStatus(connectorId).status = chargePointStatus;
c0560973
JB
280 return Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED;
281 }
282 return Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED;
283 }
284
285 private async handleRequestRemoteStartTransaction(commandPayload: RemoteStartTransactionRequest): Promise<DefaultResponse> {
a7fc8211
JB
286 const transactionConnectorId: number = commandPayload.connectorId;
287 if (transactionConnectorId) {
288 await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.PREPARING);
734d790d 289 this.chargingStation.getConnectorStatus(transactionConnectorId).status = OCPP16ChargePointStatus.PREPARING;
a7fc8211 290 if (this.chargingStation.isChargingStationAvailable() && this.chargingStation.isConnectorAvailable(transactionConnectorId)) {
e060fe58 291 // Check if authorized
a7fc8211
JB
292 if (this.chargingStation.getAuthorizeRemoteTxRequests()) {
293 let authorized = false;
a7fc8211
JB
294 if (this.chargingStation.getLocalAuthListEnabled() && this.chargingStation.hasAuthorizedTags()
295 && this.chargingStation.authorizedTags.find((value) => value === commandPayload.idTag)) {
a2653482
JB
296 this.chargingStation.getConnectorStatus(transactionConnectorId).localAuthorizeIdTag = commandPayload.idTag;
297 this.chargingStation.getConnectorStatus(transactionConnectorId).idTagLocalAuthorized = true;
36f6a92e 298 authorized = true;
71068fb9 299 } else if (this.chargingStation.getMayAuthorizeAtRemoteStart()) {
a7fc8211
JB
300 const authorizeResponse = await this.chargingStation.ocppRequestService.sendAuthorize(transactionConnectorId, commandPayload.idTag);
301 if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
302 authorized = true;
a7fc8211 303 }
71068fb9 304 } else {
b4d1b412 305 logger.warn(`${this.chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`);
a7fc8211
JB
306 }
307 if (authorized) {
308 // Authorization successful, start transaction
e060fe58 309 if (this.setRemoteStartTransactionChargingProfile(transactionConnectorId, commandPayload.chargingProfile)) {
a2653482 310 this.chargingStation.getConnectorStatus(transactionConnectorId).transactionRemoteStarted = true;
e060fe58
JB
311 if ((await this.chargingStation.ocppRequestService.sendStartTransaction(transactionConnectorId, commandPayload.idTag)).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
312 logger.debug(this.chargingStation.logPrefix() + ' Transaction remotely STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + transactionConnectorId.toString() + ' for idTag ' + commandPayload.idTag);
313 return Constants.OCPP_RESPONSE_ACCEPTED;
314 }
57939a9d 315 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
e060fe58 316 }
57939a9d 317 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
a7fc8211 318 }
57939a9d 319 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
36f6a92e 320 }
a7fc8211 321 // No authorization check required, start transaction
e060fe58 322 if (this.setRemoteStartTransactionChargingProfile(transactionConnectorId, commandPayload.chargingProfile)) {
a2653482 323 this.chargingStation.getConnectorStatus(transactionConnectorId).transactionRemoteStarted = true;
e060fe58
JB
324 if ((await this.chargingStation.ocppRequestService.sendStartTransaction(transactionConnectorId, commandPayload.idTag)).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
325 logger.debug(this.chargingStation.logPrefix() + ' Transaction remotely STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + transactionConnectorId.toString() + ' for idTag ' + commandPayload.idTag);
326 return Constants.OCPP_RESPONSE_ACCEPTED;
327 }
57939a9d 328 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
e060fe58 329 }
57939a9d 330 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
c0560973 331 }
57939a9d 332 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
c0560973 333 }
57939a9d 334 return this.notifyRemoteStartTransactionRejected(transactionConnectorId, commandPayload.idTag);
a7fc8211
JB
335 }
336
337 private async notifyRemoteStartTransactionRejected(connectorId: number, idTag: string): Promise<DefaultResponse> {
734d790d 338 if (this.chargingStation.getConnectorStatus(connectorId).status !== OCPP16ChargePointStatus.AVAILABLE) {
e060fe58 339 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.AVAILABLE);
734d790d 340 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.AVAILABLE;
e060fe58 341 }
734d790d 342 logger.warn(this.chargingStation.logPrefix() + ' Remote starting transaction REJECTED on connector Id ' + connectorId.toString() + ', idTag ' + idTag + ', availability ' + this.chargingStation.getConnectorStatus(connectorId).availability + ', status ' + this.chargingStation.getConnectorStatus(connectorId).status);
c0560973
JB
343 return Constants.OCPP_RESPONSE_REJECTED;
344 }
345
e060fe58 346 private setRemoteStartTransactionChargingProfile(connectorId: number, cp: OCPP16ChargingProfile): boolean {
a7fc8211
JB
347 if (cp && cp.chargingProfilePurpose === ChargingProfilePurposeType.TX_PROFILE) {
348 this.chargingStation.setChargingProfile(connectorId, cp);
734d790d 349 logger.debug(`${this.chargingStation.logPrefix()} Charging profile(s) set at remote start transaction, dump their stack: %j`, this.chargingStation.getConnectorStatus(connectorId).chargingProfiles);
a7fc8211
JB
350 return true;
351 } else if (cp && cp.chargingProfilePurpose !== ChargingProfilePurposeType.TX_PROFILE) {
a7fc8211
JB
352 logger.warn(`${this.chargingStation.logPrefix()} Not allowed to set ${cp.chargingProfilePurpose} charging profile(s) at remote start transaction`);
353 return false;
e060fe58
JB
354 } else if (!cp) {
355 return true;
a7fc8211
JB
356 }
357 }
358
c0560973
JB
359 private async handleRequestRemoteStopTransaction(commandPayload: RemoteStopTransactionRequest): Promise<DefaultResponse> {
360 const transactionId = commandPayload.transactionId;
734d790d
JB
361 for (const connectorId of this.chargingStation.connectors.keys()) {
362 if (connectorId > 0 && this.chargingStation.getConnectorStatus(connectorId)?.transactionId === transactionId) {
363 await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.FINISHING);
364 this.chargingStation.getConnectorStatus(connectorId).status = OCPP16ChargePointStatus.FINISHING;
6ed92bc1 365 await this.chargingStation.ocppRequestService.sendStopTransaction(transactionId, this.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId),
035742f7 366 this.chargingStation.getTransactionIdTag(transactionId));
c0560973
JB
367 return Constants.OCPP_RESPONSE_ACCEPTED;
368 }
369 }
370 logger.info(this.chargingStation.logPrefix() + ' Trying to remote stop a non existing transaction ' + transactionId.toString());
371 return Constants.OCPP_RESPONSE_REJECTED;
372 }
47e22477
JB
373
374 private async handleRequestGetDiagnostics(commandPayload: GetDiagnosticsRequest): Promise<GetDiagnosticsResponse> {
58144adb 375 logger.debug(this.chargingStation.logPrefix() + ' ' + OCPP16IncomingRequestCommand.GET_DIAGNOSTICS + ' request received: %j', commandPayload);
a3868ec4 376 const uri = new URL(commandPayload.location);
47e22477
JB
377 if (uri.protocol.startsWith('ftp:')) {
378 let ftpClient: Client;
379 try {
380 const logFiles = fs.readdirSync(path.resolve(__dirname, '../../../../')).filter((file) => file.endsWith('.log')).map((file) => path.join('./', file));
381 const diagnosticsArchive = this.chargingStation.stationInfo.chargingStationId + '_logs.tar.gz';
382 tar.create({ gzip: true }, logFiles).pipe(fs.createWriteStream(diagnosticsArchive));
383 ftpClient = new Client();
384 const accessResponse = await ftpClient.access({
385 host: uri.host,
386 ...(uri.port !== '') && { port: Utils.convertToInt(uri.port) },
387 ...(uri.username !== '') && { user: uri.username },
388 ...(uri.password !== '') && { password: uri.password },
389 });
390 let uploadResponse: FTPResponse;
391 if (accessResponse.code === 220) {
392 // eslint-disable-next-line @typescript-eslint/no-misused-promises
393 ftpClient.trackProgress(async (info) => {
394 logger.info(`${this.chargingStation.logPrefix()} ${info.bytes / 1024} bytes transferred from diagnostics archive ${info.name}`);
395 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(OCPP16DiagnosticsStatus.Uploading);
396 });
397 uploadResponse = await ftpClient.uploadFrom(path.join(path.resolve(__dirname, '../../../../'), diagnosticsArchive), uri.pathname + diagnosticsArchive);
398 if (uploadResponse.code === 226) {
399 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(OCPP16DiagnosticsStatus.Uploaded);
400 if (ftpClient) {
401 ftpClient.close();
402 }
403 return { fileName: diagnosticsArchive };
404 }
58144adb 405 throw new OCPPError(ErrorType.GENERIC_ERROR, `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS);
47e22477 406 }
58144adb 407 throw new OCPPError(ErrorType.GENERIC_ERROR, `Diagnostics transfer failed with error code ${accessResponse.code.toString()}${uploadResponse?.code && '|' + uploadResponse?.code.toString()}`, OCPP16IncomingRequestCommand.GET_DIAGNOSTICS);
47e22477
JB
408 } catch (error) {
409 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(OCPP16DiagnosticsStatus.UploadFailed);
47e22477
JB
410 if (ftpClient) {
411 ftpClient.close();
412 }
88184022 413 return this.handleIncomingRequestError(OCPP16IncomingRequestCommand.GET_DIAGNOSTICS, error as Error, Constants.OCPP_RESPONSE_EMPTY);
47e22477
JB
414 }
415 } else {
416 logger.error(`${this.chargingStation.logPrefix()} Unsupported protocol ${uri.protocol} to transfer the diagnostic logs archive`);
417 await this.chargingStation.ocppRequestService.sendDiagnosticsStatusNotification(OCPP16DiagnosticsStatus.UploadFailed);
418 return Constants.OCPP_RESPONSE_EMPTY;
419 }
420 }
802cfa13
JB
421
422 private handleRequestTriggerMessage(commandPayload: OCPP16TriggerMessageRequest): OCPP16TriggerMessageResponse {
423 try {
424 switch (commandPayload.requestedMessage) {
425 case MessageTrigger.BootNotification:
426 setTimeout(() => {
427 this.chargingStation.ocppRequestService.sendBootNotification(this.chargingStation.getBootNotificationRequest().chargePointModel,
428 this.chargingStation.getBootNotificationRequest().chargePointVendor, this.chargingStation.getBootNotificationRequest().chargeBoxSerialNumber,
0dad4bda 429 this.chargingStation.getBootNotificationRequest().firmwareVersion).catch(() => { /* This is intentional */ });
802cfa13
JB
430 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
431 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
432 case MessageTrigger.Heartbeat:
433 setTimeout(() => {
0dad4bda 434 this.chargingStation.ocppRequestService.sendHeartbeat().catch(() => { /* This is intentional */ });
802cfa13
JB
435 }, Constants.OCPP_TRIGGER_MESSAGE_DELAY);
436 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED;
437 default:
438 return Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED;
439 }
440 } catch (error) {
88184022 441 return this.handleIncomingRequestError(OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, error as Error, Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED);
802cfa13
JB
442 }
443 }
c0560973 444}