3c01d452f5bb7cee69b836b3d10e837619769327
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16RequestService.ts
1 import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
2 import { AuthorizeRequest, OCPP16AuthorizeResponse, OCPP16StartTransactionResponse, OCPP16StopTransactionReason, OCPP16StopTransactionResponse, StartTransactionRequest, StopTransactionRequest } from '../../../types/ocpp/1.6/Transaction';
3 import { HeartbeatRequest, OCPP16BootNotificationRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests';
4 import { MeterValue, MeterValueContext, MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
5
6 import Constants from '../../../utils/Constants';
7 import { CurrentOutType } from '../../../types/ChargingStationTemplate';
8 import MeasurandValues from '../../../types/MeasurandValues';
9 import { MessageType } from '../../../types/ocpp/MessageType';
10 import { OCPP16BootNotificationResponse } from '../../../types/ocpp/1.6/Responses';
11 import { OCPP16ChargePointErrorCode } from '../../../types/ocpp/1.6/ChargePointErrorCode';
12 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
13 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
14 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
15 import OCPPError from '../../OcppError';
16 import OCPPRequestService from '../OCPPRequestService';
17 import Utils from '../../../utils/Utils';
18 import logger from '../../../utils/Logger';
19
20 export default class OCPP16RequestService extends OCPPRequestService {
21 public async sendHeartbeat(): Promise<void> {
22 try {
23 const payload: HeartbeatRequest = {};
24 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.HEARTBEAT);
25 } catch (error) {
26 this.handleRequestError(OCPP16RequestCommand.HEARTBEAT, error);
27 }
28 }
29
30 public async sendBootNotification(chargePointModel: string, chargePointVendor: string, chargeBoxSerialNumber?: string, firmwareVersion?: string, chargePointSerialNumber?: string, iccid?: string, imsi?: string, meterSerialNumber?: string, meterType?: string): Promise<OCPP16BootNotificationResponse> {
31 try {
32 const payload: OCPP16BootNotificationRequest = {
33 chargePointModel,
34 chargePointVendor,
35 ...!Utils.isUndefined(chargeBoxSerialNumber) && { chargeBoxSerialNumber },
36 ...!Utils.isUndefined(chargePointSerialNumber) && { chargePointSerialNumber },
37 ...!Utils.isUndefined(firmwareVersion) && { firmwareVersion },
38 ...!Utils.isUndefined(iccid) && { iccid },
39 ...!Utils.isUndefined(imsi) && { imsi },
40 ...!Utils.isUndefined(meterSerialNumber) && { meterSerialNumber },
41 ...!Utils.isUndefined(meterType) && { meterType }
42 };
43 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.BOOT_NOTIFICATION) as OCPP16BootNotificationResponse;
44 } catch (error) {
45 this.handleRequestError(OCPP16RequestCommand.BOOT_NOTIFICATION, error);
46 }
47 }
48
49 public async sendStatusNotification(connectorId: number, status: OCPP16ChargePointStatus,
50 errorCode: OCPP16ChargePointErrorCode = OCPP16ChargePointErrorCode.NO_ERROR): Promise<void> {
51 try {
52 const payload: StatusNotificationRequest = {
53 connectorId,
54 errorCode,
55 status,
56 };
57 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.STATUS_NOTIFICATION);
58 } catch (error) {
59 this.handleRequestError(OCPP16RequestCommand.STATUS_NOTIFICATION, error);
60 }
61 }
62
63 public async sendAuthorize(idTag?: string): Promise<OCPP16AuthorizeResponse> {
64 try {
65 const payload: AuthorizeRequest = {
66 ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.TRANSACTION_DEFAULT_IDTAG },
67 };
68 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.AUTHORIZE) as OCPP16AuthorizeResponse;
69 } catch (error) {
70 this.handleRequestError(OCPP16RequestCommand.AUTHORIZE, error);
71 }
72 }
73
74 public async sendStartTransaction(connectorId: number, idTag?: string): Promise<OCPP16StartTransactionResponse> {
75 try {
76 const payload: StartTransactionRequest = {
77 connectorId,
78 ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.TRANSACTION_DEFAULT_IDTAG },
79 meterStart: this.chargingStation.getEnergyActiveImportRegisterByConnectorId(connectorId),
80 timestamp: new Date().toISOString(),
81 };
82 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.START_TRANSACTION) as OCPP16StartTransactionResponse;
83 } catch (error) {
84 this.handleRequestError(OCPP16RequestCommand.START_TRANSACTION, error);
85 }
86 }
87
88 public async sendStopTransaction(transactionId: number, meterStop: number, idTag?: string,
89 reason: OCPP16StopTransactionReason = OCPP16StopTransactionReason.NONE): Promise<OCPP16StopTransactionResponse> {
90 try {
91 const payload: StopTransactionRequest = {
92 transactionId,
93 ...!Utils.isUndefined(idTag) && { idTag },
94 meterStop: meterStop,
95 timestamp: new Date().toISOString(),
96 ...reason && { reason },
97 };
98 let connectorId: number;
99 for (const connector in this.chargingStation.connectors) {
100 if (Utils.convertToInt(connector) > 0 && this.chargingStation.getConnector(Utils.convertToInt(connector))?.transactionId === transactionId) {
101 connectorId = Utils.convertToInt(connector);
102 }
103 }
104 (this.chargingStation.getBeginEndMeterValues() && !this.chargingStation.getOutOfOrderEndMeterValues())
105 && await this.sendTransactionEndMeterValues(connectorId, transactionId, meterStop);
106 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.STOP_TRANSACTION) as OCPP16StartTransactionResponse;
107 } catch (error) {
108 this.handleRequestError(OCPP16RequestCommand.STOP_TRANSACTION, error);
109 }
110 }
111
112 // eslint-disable-next-line consistent-this
113 public async sendMeterValues(connectorId: number, transactionId: number, interval: number, self: OCPPRequestService, debug = false): Promise<void> {
114 try {
115 const meterValue: MeterValue = {
116 timestamp: new Date().toISOString(),
117 sampledValue: [],
118 };
119 const meterValuesTemplate: OCPP16SampledValue[] = self.chargingStation.getConnector(connectorId).MeterValues;
120 for (let index = 0; index < meterValuesTemplate.length; index++) {
121 const connector = self.chargingStation.getConnector(connectorId);
122 // SoC measurand
123 if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.STATE_OF_CHARGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.STATE_OF_CHARGE)) {
124 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.getRandomInt(100)));
125 const sampledValuesIndex = meterValue.sampledValue.length - 1;
126 if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
127 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`);
128 }
129 // Voltage measurand
130 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.VOLTAGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.VOLTAGE)) {
131 const voltageMeasurandValue = Utils.getRandomFloatRounded(self.chargingStation.getVoltageOut() + self.chargingStation.getVoltageOut() * 0.1, self.chargingStation.getVoltageOut() - self.chargingStation.getVoltageOut() * 0.1);
132 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue));
133 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
134 let phaseValue: string;
135 if (self.chargingStation.getVoltageOut() >= 0 && self.chargingStation.getVoltageOut() <= 250) {
136 phaseValue = `L${phase}-N`;
137 } else if (self.chargingStation.getVoltageOut() > 250) {
138 phaseValue = `L${phase}-L${(phase + 1) % self.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % self.chargingStation.getNumberOfPhases() : self.chargingStation.getNumberOfPhases()}`;
139 }
140 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], voltageMeasurandValue, null,
141 phaseValue as MeterValuePhase));
142 }
143 // Power.Active.Import measurand
144 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT)) {
145 OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
146 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
147 const powerMeasurandValues = {} as MeasurandValues;
148 const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
149 const maxPower = Math.round(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider);
150 const maxPowerPerPhase = Math.round((self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider) / self.chargingStation.getNumberOfPhases());
151 switch (self.chargingStation.getCurrentOutType()) {
152 case CurrentOutType.AC:
153 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
154 powerMeasurandValues.L1 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
155 powerMeasurandValues.L2 = 0;
156 powerMeasurandValues.L3 = 0;
157 if (self.chargingStation.getNumberOfPhases() === 3) {
158 powerMeasurandValues.L2 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
159 powerMeasurandValues.L3 = Utils.getRandomFloatRounded(maxPowerPerPhase / unitDivider);
160 }
161 powerMeasurandValues.allPhases = Utils.roundTo(powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, 2);
162 }
163 break;
164 case CurrentOutType.DC:
165 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
166 powerMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxPower / unitDivider);
167 }
168 break;
169 default:
170 logger.error(errMsg);
171 throw Error(errMsg);
172 }
173 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues.allPhases));
174 const sampledValuesIndex = meterValue.sampledValue.length - 1;
175 const maxPowerRounded = Utils.roundTo(maxPower / unitDivider, 2);
176 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPowerRounded || debug) {
177 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPowerRounded}`);
178 }
179 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
180 const phaseValue = `L${phase}-N`;
181 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], powerMeasurandValues[`L${phase}`], null,
182 phaseValue as MeterValuePhase));
183 }
184 // Current.Import measurand
185 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.CURRENT_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.CURRENT_IMPORT)) {
186 OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
187 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${self.chargingStation.getCurrentOutType()} currentOutType in template file ${self.chargingStation.stationTemplateFile}, cannot calculate ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
188 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
189 let maxAmperage: number;
190 switch (self.chargingStation.getCurrentOutType()) {
191 case CurrentOutType.AC:
192 maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(self.chargingStation.getNumberOfPhases(), self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut());
193 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
194 currentMeasurandValues.L1 = Utils.getRandomFloatRounded(maxAmperage);
195 currentMeasurandValues.L2 = 0;
196 currentMeasurandValues.L3 = 0;
197 if (self.chargingStation.getNumberOfPhases() === 3) {
198 currentMeasurandValues.L2 = Utils.getRandomFloatRounded(maxAmperage);
199 currentMeasurandValues.L3 = Utils.getRandomFloatRounded(maxAmperage);
200 }
201 currentMeasurandValues.allPhases = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / self.chargingStation.getNumberOfPhases(), 2);
202 }
203 break;
204 case CurrentOutType.DC:
205 maxAmperage = DCElectricUtils.amperage(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut());
206 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
207 currentMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxAmperage);
208 }
209 break;
210 default:
211 logger.error(errMsg);
212 throw Error(errMsg);
213 }
214 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues.allPhases));
215 const sampledValuesIndex = meterValue.sampledValue.length - 1;
216 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || debug) {
217 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
218 }
219 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
220 const phaseValue = `L${phase}`;
221 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], currentMeasurandValues[phaseValue], null,
222 phaseValue as MeterValuePhase));
223 }
224 // Energy.Active.Import.Register measurand (default)
225 } else if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
226 OCPP16ServiceUtils.checkMeasurandPowerDivider(self.chargingStation, meterValuesTemplate[index].measurand);
227 const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
228 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
229 const energyMeasurandValue = Utils.getRandomInt(self.chargingStation.stationInfo.maxPower / (self.chargingStation.stationInfo.powerDivider * 3600000) * interval);
230 // Persist previous value in connector
231 if (connector && !Utils.isNullOrUndefined(connector.energyActiveImportRegisterValue) && connector.energyActiveImportRegisterValue >= 0 &&
232 !Utils.isNullOrUndefined(connector.transactionEnergyActiveImportRegisterValue) && connector.transactionEnergyActiveImportRegisterValue >= 0) {
233 connector.energyActiveImportRegisterValue += energyMeasurandValue;
234 connector.transactionEnergyActiveImportRegisterValue += energyMeasurandValue;
235 } else {
236 connector.energyActiveImportRegisterValue = 0;
237 connector.transactionEnergyActiveImportRegisterValue = 0;
238 }
239 }
240 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index],
241 Utils.roundTo(self.chargingStation.getEnergyActiveImportRegisterByTransactionId(transactionId) / unitDivider, 4)));
242 const sampledValuesIndex = meterValue.sampledValue.length - 1;
243 const maxEnergy = Math.round(self.chargingStation.stationInfo.maxPower * 3600 / (self.chargingStation.stationInfo.powerDivider * interval));
244 const maxEnergyRounded = Utils.roundTo(maxEnergy / unitDivider, 4);
245 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxEnergyRounded || debug) {
246 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxEnergyRounded}`);
247 }
248 // Unsupported measurand
249 } else {
250 logger.info(`${self.chargingStation.logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} on connectorId ${connectorId}`);
251 }
252 }
253 const payload: MeterValuesRequest = {
254 connectorId,
255 transactionId,
256 meterValue,
257 };
258 await self.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
259 } catch (error) {
260 self.handleRequestError(OCPP16RequestCommand.METER_VALUES, error);
261 }
262 }
263
264 public async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number): Promise<void> {
265 const meterValue: MeterValue = {
266 timestamp: new Date().toISOString(),
267 sampledValue: [],
268 };
269 const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
270 for (let index = 0; index < meterValuesTemplate.length; index++) {
271 // Energy.Active.Import.Register measurand (default)
272 if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
273 const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
274 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index],
275 Utils.roundTo(meterBegin / unitDivider, 4), MeterValueContext.TRANSACTION_BEGIN));
276 }
277 }
278 const payload: MeterValuesRequest = {
279 connectorId,
280 transactionId,
281 meterValue,
282 };
283 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
284 }
285
286 public async sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number): Promise<void> {
287 const meterValue: MeterValue = {
288 timestamp: new Date().toISOString(),
289 sampledValue: [],
290 };
291 const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
292 for (let index = 0; index < meterValuesTemplate.length; index++) {
293 // Energy.Active.Import.Register measurand (default)
294 if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
295 const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
296 meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.roundTo(meterEnd / unitDivider, 4), MeterValueContext.TRANSACTION_END));
297 }
298 }
299 const payload: MeterValuesRequest = {
300 connectorId,
301 transactionId,
302 meterValue,
303 };
304 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
305 }
306
307 public async sendError(messageId: string, error: OCPPError, commandName: OCPP16RequestCommand | OCPP16IncomingRequestCommand): Promise<unknown> {
308 // Send error
309 return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
310 }
311 }