a5721325e025c51b5819c294f434d2ccd71c8c32
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16RequestService.ts
1 import { AuthorizeRequest, OCPP16AuthorizeResponse, OCPP16StartTransactionResponse, OCPP16StopTransactionReason, OCPP16StopTransactionResponse, StartTransactionRequest, StopTransactionRequest } from '../../../types/ocpp/1.6/Transaction';
2 import { HeartbeatRequest, OCPP16BootNotificationRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests';
3 import { MeterValue, MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
4
5 import { ACElectricUtils } from '../../../utils/ElectricUtils';
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 { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
14 import OCPPError from '../../OcppError';
15 import OCPPRequestService from '../OCPPRequestService';
16 import Utils from '../../../utils/Utils';
17 import logger from '../../../utils/Logger';
18
19 export default class OCPP16RequestService extends OCPPRequestService {
20 public async sendHeartbeat(): Promise<void> {
21 try {
22 const payload: HeartbeatRequest = {};
23 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.HEARTBEAT);
24 } catch (error) {
25 this.handleRequestError(OCPP16RequestCommand.HEARTBEAT, error);
26 }
27 }
28
29 public async sendBootNotification(chargePointModel: string, chargePointVendor: string, chargeBoxSerialNumber?: string, firmwareVersion?: string, chargePointSerialNumber?: string, iccid?: string, imsi?: string, meterSerialNumber?: string, meterType?: string): Promise<OCPP16BootNotificationResponse> {
30 try {
31 const payload: OCPP16BootNotificationRequest = {
32 chargePointModel,
33 chargePointVendor,
34 ...!Utils.isUndefined(chargeBoxSerialNumber) && { chargeBoxSerialNumber },
35 ...!Utils.isUndefined(chargePointSerialNumber) && { chargePointSerialNumber },
36 ...!Utils.isUndefined(firmwareVersion) && { firmwareVersion },
37 ...!Utils.isUndefined(iccid) && { iccid },
38 ...!Utils.isUndefined(imsi) && { imsi },
39 ...!Utils.isUndefined(meterSerialNumber) && { meterSerialNumber },
40 ...!Utils.isUndefined(meterType) && { meterType }
41 };
42 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.BOOT_NOTIFICATION) as OCPP16BootNotificationResponse;
43 } catch (error) {
44 this.handleRequestError(OCPP16RequestCommand.BOOT_NOTIFICATION, error);
45 }
46 }
47
48 public async sendStatusNotification(connectorId: number, status: OCPP16ChargePointStatus,
49 errorCode: OCPP16ChargePointErrorCode = OCPP16ChargePointErrorCode.NO_ERROR): Promise<void> {
50 try {
51 const payload: StatusNotificationRequest = {
52 connectorId,
53 errorCode,
54 status,
55 };
56 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.STATUS_NOTIFICATION);
57 } catch (error) {
58 this.handleRequestError(OCPP16RequestCommand.STATUS_NOTIFICATION, error);
59 }
60 }
61
62 public async sendAuthorize(idTag?: string): Promise<OCPP16AuthorizeResponse> {
63 try {
64 const payload: AuthorizeRequest = {
65 ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.TRANSACTION_DEFAULT_IDTAG },
66 };
67 return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.AUTHORIZE) as OCPP16AuthorizeResponse;
68 } catch (error) {
69 this.handleRequestError(OCPP16RequestCommand.AUTHORIZE, error);
70 }
71 }
72
73 public async sendStartTransaction(connectorId: number, idTag?: string): Promise<OCPP16StartTransactionResponse> {
74 try {
75 const payload: StartTransactionRequest = {
76 connectorId,
77 ...!Utils.isUndefined(idTag) ? { idTag } : { idTag: Constants.TRANSACTION_DEFAULT_IDTAG },
78 meterStart: 0,
79 timestamp: new Date().toISOString(),
80 };
81 const response = await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.START_TRANSACTION) as OCPP16StartTransactionResponse;
82 await this.sendTransactionBeginMeterValues(connectorId, response.transactionId, 0);
83 return response;
84 } catch (error) {
85 this.handleRequestError(OCPP16RequestCommand.START_TRANSACTION, error);
86 }
87 }
88
89 public async sendStopTransaction(transactionId: number, meterStop: number, idTag?: string,
90 reason: OCPP16StopTransactionReason = OCPP16StopTransactionReason.NONE): Promise<OCPP16StopTransactionResponse> {
91 try {
92 const payload: StopTransactionRequest = {
93 transactionId,
94 ...!Utils.isUndefined(idTag) && { idTag },
95 meterStop: meterStop,
96 timestamp: new Date().toISOString(),
97 ...reason && { reason },
98 };
99 let connectorId: number;
100 for (const connector in this.chargingStation.connectors) {
101 if (Utils.convertToInt(connector) > 0 && this.chargingStation.getConnector(Utils.convertToInt(connector))?.transactionId === transactionId) {
102 connectorId = Utils.convertToInt(connector);
103 }
104 }
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({
125 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.PERCENT },
126 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
127 measurand: meterValuesTemplate[index].measurand,
128 ...!Utils.isUndefined(meterValuesTemplate[index].location) ? { location: meterValuesTemplate[index].location } : { location: MeterValueLocation.EV },
129 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: Utils.getRandomInt(100).toString() },
130 });
131 const sampledValuesIndex = meterValue.sampledValue.length - 1;
132 if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
133 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`);
134 }
135 // Voltage measurand
136 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.VOLTAGE && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.VOLTAGE)) {
137 const voltageMeasurandValue = Utils.getRandomFloatRounded(self.chargingStation.getVoltageOut() + self.chargingStation.getVoltageOut() * 0.1, self.chargingStation.getVoltageOut() - self.chargingStation.getVoltageOut() * 0.1);
138 meterValue.sampledValue.push({
139 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.VOLT },
140 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
141 measurand: meterValuesTemplate[index].measurand,
142 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
143 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue.toString() },
144 });
145 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
146 let phaseValue: string;
147 if (self.chargingStation.getVoltageOut() >= 0 && self.chargingStation.getVoltageOut() <= 250) {
148 phaseValue = `L${phase}-N`;
149 } else if (self.chargingStation.getVoltageOut() > 250) {
150 phaseValue = `L${phase}-L${(phase + 1) % self.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % self.chargingStation.getNumberOfPhases() : self.chargingStation.getNumberOfPhases()}`;
151 }
152 meterValue.sampledValue.push({
153 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.VOLT },
154 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
155 measurand: meterValuesTemplate[index].measurand,
156 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
157 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: voltageMeasurandValue.toString() },
158 phase: phaseValue as MeterValuePhase,
159 });
160 }
161 // Power.Active.Import measurand
162 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT)) {
163 // FIXME: factor out powerDivider checks
164 if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
165 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
166 logger.error(errMsg);
167 throw Error(errMsg);
168 } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
169 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
170 logger.error(errMsg);
171 throw Error(errMsg);
172 }
173 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].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 ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
174 const powerMeasurandValues = {} as MeasurandValues;
175 const maxPower = Math.round(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider);
176 const maxPowerPerPhase = Math.round((self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider) / self.chargingStation.getNumberOfPhases());
177 switch (self.chargingStation.getCurrentOutType()) {
178 case CurrentOutType.AC:
179 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
180 powerMeasurandValues.L1 = Utils.getRandomFloatRounded(maxPowerPerPhase);
181 powerMeasurandValues.L2 = 0;
182 powerMeasurandValues.L3 = 0;
183 if (self.chargingStation.getNumberOfPhases() === 3) {
184 powerMeasurandValues.L2 = Utils.getRandomFloatRounded(maxPowerPerPhase);
185 powerMeasurandValues.L3 = Utils.getRandomFloatRounded(maxPowerPerPhase);
186 }
187 powerMeasurandValues.allPhases = Utils.roundTo(powerMeasurandValues.L1 + powerMeasurandValues.L2 + powerMeasurandValues.L3, 2);
188 }
189 break;
190 case CurrentOutType.DC:
191 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
192 powerMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxPower);
193 }
194 break;
195 default:
196 logger.error(errMsg);
197 throw Error(errMsg);
198 }
199 meterValue.sampledValue.push({
200 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT },
201 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
202 measurand: meterValuesTemplate[index].measurand,
203 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
204 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: powerMeasurandValues.allPhases.toString() },
205 });
206 const sampledValuesIndex = meterValue.sampledValue.length - 1;
207 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxPower || debug) {
208 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPower}`);
209 }
210 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
211 const phaseValue = `L${phase}-N`;
212 meterValue.sampledValue.push({
213 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT },
214 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
215 ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
216 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
217 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: powerMeasurandValues[`L${phase}`] as string },
218 phase: phaseValue as MeterValuePhase,
219 });
220 }
221 // Current.Import measurand
222 } else if (meterValuesTemplate[index].measurand && meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.CURRENT_IMPORT && self.chargingStation.getConfigurationKey(OCPP16StandardParametersKey.MeterValuesSampledData).value.includes(OCPP16MeterValueMeasurand.CURRENT_IMPORT)) {
223 // FIXME: factor out powerDivider checks
224 if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
225 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
226 logger.error(errMsg);
227 throw Error(errMsg);
228 } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
229 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
230 logger.error(errMsg);
231 throw Error(errMsg);
232 }
233 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].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 ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
234 const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
235 let maxAmperage: number;
236 switch (self.chargingStation.getCurrentOutType()) {
237 case CurrentOutType.AC:
238 maxAmperage = ACElectricUtils.amperagePerPhaseFromPower(self.chargingStation.getNumberOfPhases(), self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut());
239 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
240 currentMeasurandValues.L1 = Utils.getRandomFloatRounded(maxAmperage);
241 currentMeasurandValues.L2 = 0;
242 currentMeasurandValues.L3 = 0;
243 if (self.chargingStation.getNumberOfPhases() === 3) {
244 currentMeasurandValues.L2 = Utils.getRandomFloatRounded(maxAmperage);
245 currentMeasurandValues.L3 = Utils.getRandomFloatRounded(maxAmperage);
246 }
247 currentMeasurandValues.allPhases = Utils.roundTo((currentMeasurandValues.L1 + currentMeasurandValues.L2 + currentMeasurandValues.L3) / self.chargingStation.getNumberOfPhases(), 2);
248 }
249 break;
250 case CurrentOutType.DC:
251 maxAmperage = ACElectricUtils.amperageTotalFromPower(self.chargingStation.stationInfo.maxPower / self.chargingStation.stationInfo.powerDivider, self.chargingStation.getVoltageOut());
252 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
253 currentMeasurandValues.allPhases = Utils.getRandomFloatRounded(maxAmperage);
254 }
255 break;
256 default:
257 logger.error(errMsg);
258 throw Error(errMsg);
259 }
260 meterValue.sampledValue.push({
261 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.AMP },
262 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
263 measurand: meterValuesTemplate[index].measurand,
264 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
265 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: currentMeasurandValues.allPhases.toString() },
266 });
267 const sampledValuesIndex = meterValue.sampledValue.length - 1;
268 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxAmperage || debug) {
269 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
270 }
271 for (let phase = 1; self.chargingStation.getNumberOfPhases() === 3 && phase <= self.chargingStation.getNumberOfPhases(); phase++) {
272 const phaseValue = `L${phase}`;
273 meterValue.sampledValue.push({
274 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.AMP },
275 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
276 ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
277 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
278 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } : { value: currentMeasurandValues[phaseValue] as string },
279 phase: phaseValue as MeterValuePhase,
280 });
281 }
282 // Energy.Active.Import.Register measurand (default)
283 } else if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
284 // FIXME: factor out powerDivider checks
285 if (Utils.isUndefined(self.chargingStation.stationInfo.powerDivider)) {
286 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
287 logger.error(errMsg);
288 throw Error(errMsg);
289 } else if (self.chargingStation.stationInfo.powerDivider && self.chargingStation.stationInfo.powerDivider <= 0) {
290 const errMsg = `${self.chargingStation.logPrefix()} MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${self.chargingStation.stationInfo.powerDivider}`;
291 logger.error(errMsg);
292 throw Error(errMsg);
293 }
294 if (Utils.isUndefined(meterValuesTemplate[index].value)) {
295 const measurandValue = Utils.getRandomInt(self.chargingStation.stationInfo.maxPower / (self.chargingStation.stationInfo.powerDivider * 3600000) * interval);
296 // Persist previous value in connector
297 if (connector && !Utils.isNullOrUndefined(connector.lastEnergyActiveImportRegisterValue) && connector.lastEnergyActiveImportRegisterValue >= 0) {
298 connector.lastEnergyActiveImportRegisterValue += measurandValue;
299 } else {
300 connector.lastEnergyActiveImportRegisterValue = 0;
301 }
302 }
303 meterValue.sampledValue.push({
304 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
305 ...!Utils.isUndefined(meterValuesTemplate[index].context) && { context: meterValuesTemplate[index].context },
306 ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
307 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
308 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
309 { value: connector.lastEnergyActiveImportRegisterValue.toString() },
310 });
311 const sampledValuesIndex = meterValue.sampledValue.length - 1;
312 const maxConsumption = Math.round(self.chargingStation.stationInfo.maxPower * 3600 / (self.chargingStation.stationInfo.powerDivider * interval));
313 if (Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) > maxConsumption || debug) {
314 logger.error(`${self.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ? meterValue.sampledValue[sampledValuesIndex].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxConsumption}`);
315 }
316 // Unsupported measurand
317 } else {
318 logger.info(`${self.chargingStation.logPrefix()} Unsupported MeterValues measurand ${meterValuesTemplate[index].measurand ? meterValuesTemplate[index].measurand : OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} on connectorId ${connectorId}`);
319 }
320 }
321 const payload: MeterValuesRequest = {
322 connectorId,
323 transactionId,
324 meterValue,
325 };
326 await self.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
327 } catch (error) {
328 self.handleRequestError(OCPP16RequestCommand.METER_VALUES, error);
329 }
330 }
331
332 public async sendError(messageId: string, error: OCPPError, commandName: OCPP16RequestCommand | OCPP16IncomingRequestCommand): Promise<unknown> {
333 // Send error
334 return this.sendMessage(messageId, error, MessageType.CALL_ERROR_MESSAGE, commandName);
335 }
336
337 private async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number) {
338 const meterValue: MeterValue = {
339 timestamp: new Date().toISOString(),
340 sampledValue: [],
341 };
342 const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
343 for (let index = 0; index < meterValuesTemplate.length; index++) {
344 // Energy.Active.Import.Register measurand (default)
345 if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
346 meterValue.sampledValue.push({
347 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
348 context: MeterValueContext.TRANSACTION_BEGIN,
349 ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
350 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
351 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
352 { value: meterBegin.toString() },
353 });
354 }
355 }
356 const payload: MeterValuesRequest = {
357 connectorId,
358 transactionId,
359 meterValue,
360 };
361 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
362 }
363
364 private async sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number) {
365 const meterValue: MeterValue = {
366 timestamp: new Date().toISOString(),
367 sampledValue: [],
368 };
369 const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
370 for (let index = 0; index < meterValuesTemplate.length; index++) {
371 // Energy.Active.Import.Register measurand (default)
372 if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
373 meterValue.sampledValue.push({
374 ...!Utils.isUndefined(meterValuesTemplate[index].unit) ? { unit: meterValuesTemplate[index].unit } : { unit: MeterValueUnit.WATT_HOUR },
375 context: MeterValueContext.TRANSACTION_END,
376 ...!Utils.isUndefined(meterValuesTemplate[index].measurand) && { measurand: meterValuesTemplate[index].measurand },
377 ...!Utils.isUndefined(meterValuesTemplate[index].location) && { location: meterValuesTemplate[index].location },
378 ...!Utils.isUndefined(meterValuesTemplate[index].value) ? { value: meterValuesTemplate[index].value } :
379 { value: meterEnd.toString() },
380 });
381 }
382 }
383 const payload: MeterValuesRequest = {
384 connectorId,
385 transactionId,
386 meterValue,
387 };
388 await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
389 }
390 }