1 // Partial Copyright Jerome Benoit. 2021. All Rights Reserved.
3 import { ACElectricUtils
, DCElectricUtils
} from
'../../../utils/ElectricUtils';
4 import { AuthorizeRequest
, OCPP16AuthorizeResponse
, OCPP16StartTransactionResponse
, OCPP16StopTransactionReason
, OCPP16StopTransactionResponse
, StartTransactionRequest
, StopTransactionRequest
} from
'../../../types/ocpp/1.6/Transaction';
5 import { CurrentType
, Voltage
} from
'../../../types/ChargingStationTemplate';
6 import { DiagnosticsStatusNotificationRequest
, HeartbeatRequest
, OCPP16BootNotificationRequest
, OCPP16IncomingRequestCommand
, OCPP16RequestCommand
, StatusNotificationRequest
} from
'../../../types/ocpp/1.6/Requests';
7 import { MeterValueUnit
, MeterValuesRequest
, OCPP16MeterValue
, OCPP16MeterValueMeasurand
, OCPP16MeterValuePhase
} from
'../../../types/ocpp/1.6/MeterValues';
9 import ChargingStation from
'../../ChargingStation';
10 import Constants from
'../../../utils/Constants';
11 import { ErrorType
} from
'../../../types/ocpp/ErrorType';
12 import { JsonType
} from
'../../../types/JsonType';
13 import MeasurandPerPhaseSampledValueTemplates from
'../../../types/MeasurandPerPhaseSampledValueTemplates';
14 import MeasurandValues from
'../../../types/MeasurandValues';
15 import { MessageType
} from
'../../../types/ocpp/MessageType';
16 import { OCPP16BootNotificationResponse
} from
'../../../types/ocpp/1.6/Responses';
17 import { OCPP16ChargePointErrorCode
} from
'../../../types/ocpp/1.6/ChargePointErrorCode';
18 import { OCPP16ChargePointStatus
} from
'../../../types/ocpp/1.6/ChargePointStatus';
19 import { OCPP16DiagnosticsStatus
} from
'../../../types/ocpp/1.6/DiagnosticsStatus';
20 import { OCPP16ServiceUtils
} from
'./OCPP16ServiceUtils';
21 import OCPPError from
'../../../exception/OCPPError';
22 import OCPPRequestService from
'../OCPPRequestService';
23 import OCPPResponseService from
'../OCPPResponseService';
24 import { SendParams
} from
'../../../types/ocpp/Requests';
25 import Utils from
'../../../utils/Utils';
26 import logger from
'../../../utils/Logger';
28 export default class OCPP16RequestService
extends OCPPRequestService
{
29 public constructor(chargingStation
: ChargingStation
, ocppResponseService
: OCPPResponseService
) {
30 if (new.target
?.name
=== 'OCPP16RequestService') {
31 throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
33 super(chargingStation
, ocppResponseService
);
36 public async sendHeartbeat(params
?: SendParams
): Promise
<void> {
38 const payload
: HeartbeatRequest
= {};
39 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.HEARTBEAT
, params
);
41 this.handleRequestError(OCPP16RequestCommand
.HEARTBEAT
, error
as Error);
45 public async sendBootNotification(chargePointModel
: string, chargePointVendor
: string, chargeBoxSerialNumber
?: string, firmwareVersion
?: string,
46 chargePointSerialNumber
?: string, iccid
?: string, imsi
?: string, meterSerialNumber
?: string, meterType
?: string,
47 params
?: SendParams
): Promise
<OCPP16BootNotificationResponse
> {
49 const payload
: OCPP16BootNotificationRequest
= {
52 ...!Utils
.isUndefined(chargeBoxSerialNumber
) && { chargeBoxSerialNumber
},
53 ...!Utils
.isUndefined(chargePointSerialNumber
) && { chargePointSerialNumber
},
54 ...!Utils
.isUndefined(firmwareVersion
) && { firmwareVersion
},
55 ...!Utils
.isUndefined(iccid
) && { iccid
},
56 ...!Utils
.isUndefined(imsi
) && { imsi
},
57 ...!Utils
.isUndefined(meterSerialNumber
) && { meterSerialNumber
},
58 ...!Utils
.isUndefined(meterType
) && { meterType
}
60 return await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
,
61 OCPP16RequestCommand
.BOOT_NOTIFICATION
, { ...params
, skipBufferingOnError
: true }) as OCPP16BootNotificationResponse
;
63 this.handleRequestError(OCPP16RequestCommand
.BOOT_NOTIFICATION
, error
as Error);
67 public async sendStatusNotification(connectorId
: number, status: OCPP16ChargePointStatus
,
68 errorCode
: OCPP16ChargePointErrorCode
= OCPP16ChargePointErrorCode
.NO_ERROR
): Promise
<void> {
70 const payload
: StatusNotificationRequest
= {
75 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.STATUS_NOTIFICATION
);
77 this.handleRequestError(OCPP16RequestCommand
.STATUS_NOTIFICATION
, error
as Error);
81 public async sendAuthorize(connectorId
: number, idTag
?: string): Promise
<OCPP16AuthorizeResponse
> {
83 const payload
: AuthorizeRequest
= {
84 ...!Utils
.isUndefined(idTag
) ? { idTag
} : { idTag
: Constants
.TRANSACTION_DEFAULT_IDTAG
},
86 this.chargingStation
.getConnectorStatus(connectorId
).authorizeIdTag
= idTag
;
87 return await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.AUTHORIZE
) as OCPP16AuthorizeResponse
;
89 this.handleRequestError(OCPP16RequestCommand
.AUTHORIZE
, error
as Error);
93 public async sendStartTransaction(connectorId
: number, idTag
?: string): Promise
<OCPP16StartTransactionResponse
> {
95 const payload
: StartTransactionRequest
= {
97 ...!Utils
.isUndefined(idTag
) ? { idTag
} : { idTag
: Constants
.TRANSACTION_DEFAULT_IDTAG
},
98 meterStart
: this.chargingStation
.getEnergyActiveImportRegisterByConnectorId(connectorId
),
99 timestamp
: new Date().toISOString(),
101 return await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.START_TRANSACTION
) as OCPP16StartTransactionResponse
;
103 this.handleRequestError(OCPP16RequestCommand
.START_TRANSACTION
, error
as Error);
107 public async sendStopTransaction(transactionId
: number, meterStop
: number, idTag
?: string,
108 reason
: OCPP16StopTransactionReason
= OCPP16StopTransactionReason
.NONE
): Promise
<OCPP16StopTransactionResponse
> {
110 let connectorId
: number;
111 for (const id
of this.chargingStation
.connectors
.keys()) {
112 if (id
> 0 && this.chargingStation
.getConnectorStatus(id
)?.transactionId
=== transactionId
) {
117 const transactionEndMeterValue
= OCPP16ServiceUtils
.buildTransactionEndMeterValue(this.chargingStation
, connectorId
, meterStop
);
118 // FIXME: should be a callback, each OCPP commands implementation must do only one job
119 (this.chargingStation
.getBeginEndMeterValues() && this.chargingStation
.getOcppStrictCompliance() && !this.chargingStation
.getOutOfOrderEndMeterValues())
120 && await this.sendTransactionEndMeterValues(connectorId
, transactionId
, transactionEndMeterValue
);
121 const payload
: StopTransactionRequest
= {
123 ...!Utils
.isUndefined(idTag
) && { idTag
},
125 timestamp
: new Date().toISOString(),
126 ...reason
&& { reason
},
127 ...this.chargingStation
.getTransactionDataMeterValues() && { transactionData
: OCPP16ServiceUtils
.buildTransactionDataMeterValues(this.chargingStation
.getConnectorStatus(connectorId
).transactionBeginMeterValue
, transactionEndMeterValue
) },
129 return await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.STOP_TRANSACTION
) as OCPP16StartTransactionResponse
;
131 this.handleRequestError(OCPP16RequestCommand
.STOP_TRANSACTION
, error
as Error);
135 public async sendMeterValues(connectorId
: number, transactionId
: number, interval
: number, debug
= false): Promise
<void> {
137 const meterValue
: OCPP16MeterValue
= {
138 timestamp
: new Date().toISOString(),
141 const connector
= this.chargingStation
.getConnectorStatus(connectorId
);
143 const socSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.STATE_OF_CHARGE
);
144 if (socSampledValueTemplate
) {
145 const socSampledValueTemplateValue
= socSampledValueTemplate
.value
146 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(socSampledValueTemplate
.value
), socSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
147 : Utils
.getRandomInteger(100);
148 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(socSampledValueTemplate
, socSampledValueTemplateValue
));
149 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
150 if (Utils
.convertToInt(meterValue
.sampledValue
[sampledValuesIndex
].value
) > 100 || debug
) {
151 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/100`);
155 const voltageSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.VOLTAGE
);
156 if (voltageSampledValueTemplate
) {
157 const voltageSampledValueTemplateValue
= voltageSampledValueTemplate
.value
? parseInt(voltageSampledValueTemplate
.value
) : this.chargingStation
.getVoltageOut();
158 const fluctuationPercent
= voltageSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
159 const voltageMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(voltageSampledValueTemplateValue
, fluctuationPercent
);
160 if (this.chargingStation
.getNumberOfPhases() !== 3 || (this.chargingStation
.getNumberOfPhases() === 3 && this.chargingStation
.getMainVoltageMeterValues())) {
161 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(voltageSampledValueTemplate
, voltageMeasurandValue
));
163 for (let phase
= 1; this.chargingStation
.getNumberOfPhases() === 3 && phase
<= this.chargingStation
.getNumberOfPhases(); phase
++) {
164 const phaseLineToNeutralValue
= `L${phase}-N`;
165 const voltagePhaseLineToNeutralSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.VOLTAGE
,
166 phaseLineToNeutralValue
as OCPP16MeterValuePhase
);
167 let voltagePhaseLineToNeutralMeasurandValue
: number;
168 if (voltagePhaseLineToNeutralSampledValueTemplate
) {
169 const voltagePhaseLineToNeutralSampledValueTemplateValue
= voltagePhaseLineToNeutralSampledValueTemplate
.value
? parseInt(voltagePhaseLineToNeutralSampledValueTemplate
.value
) : this.chargingStation
.getVoltageOut();
170 const fluctuationPhaseToNeutralPercent
= voltagePhaseLineToNeutralSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
171 voltagePhaseLineToNeutralMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(voltagePhaseLineToNeutralSampledValueTemplateValue
, fluctuationPhaseToNeutralPercent
);
173 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(voltagePhaseLineToNeutralSampledValueTemplate
?? voltageSampledValueTemplate
,
174 voltagePhaseLineToNeutralMeasurandValue
?? voltageMeasurandValue
, null, phaseLineToNeutralValue
as OCPP16MeterValuePhase
));
175 if (this.chargingStation
.getPhaseLineToLineVoltageMeterValues()) {
176 const phaseLineToLineValue
= `L${phase}-L${(phase + 1) % this.chargingStation.getNumberOfPhases() !== 0 ? (phase + 1) % this.chargingStation.getNumberOfPhases() : this.chargingStation.getNumberOfPhases()}`;
177 const voltagePhaseLineToLineSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.VOLTAGE
, phaseLineToLineValue
as OCPP16MeterValuePhase
);
178 let voltagePhaseLineToLineMeasurandValue
: number;
179 if (voltagePhaseLineToLineSampledValueTemplate
) {
180 const voltagePhaseLineToLineSampledValueTemplateValue
= voltagePhaseLineToLineSampledValueTemplate
.value
? parseInt(voltagePhaseLineToLineSampledValueTemplate
.value
) : Voltage
.VOLTAGE_400
;
181 const fluctuationPhaseLineToLinePercent
= voltagePhaseLineToLineSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
;
182 voltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(voltagePhaseLineToLineSampledValueTemplateValue
, fluctuationPhaseLineToLinePercent
);
184 const defaultVoltagePhaseLineToLineMeasurandValue
= Utils
.getRandomFloatFluctuatedRounded(Voltage
.VOLTAGE_400
, fluctuationPercent
);
185 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(voltagePhaseLineToLineSampledValueTemplate
?? voltageSampledValueTemplate
,
186 voltagePhaseLineToLineMeasurandValue
?? defaultVoltagePhaseLineToLineMeasurandValue
, null, phaseLineToLineValue
as OCPP16MeterValuePhase
));
190 // Power.Active.Import measurand
191 const powerSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
);
192 let powerPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
193 if (this.chargingStation
.getNumberOfPhases() === 3) {
194 powerPerPhaseSampledValueTemplates
= {
195 L1
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
, OCPP16MeterValuePhase
.L1_N
),
196 L2
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
, OCPP16MeterValuePhase
.L2_N
),
197 L3
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.POWER_ACTIVE_IMPORT
, OCPP16MeterValuePhase
.L3_N
),
200 if (powerSampledValueTemplate
) {
201 OCPP16ServiceUtils
.checkMeasurandPowerDivider(this.chargingStation
, powerSampledValueTemplate
.measurand
);
202 const errMsg
= `${this.chargingStation.logPrefix()} MeterValues measurand ${powerSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${this.chargingStation.getCurrentOutType()} currentOutType in template file ${this.chargingStation.stationTemplateFile}, cannot calculate ${powerSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
203 const powerMeasurandValues
= {} as MeasurandValues
;
204 const unitDivider
= powerSampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT
? 1000 : 1;
205 const maxPower
= Math.round(this.chargingStation
.stationInfo
.maxPower
/ this.chargingStation
.stationInfo
.powerDivider
);
206 const maxPowerPerPhase
= Math.round((this.chargingStation
.stationInfo
.maxPower
/ this.chargingStation
.stationInfo
.powerDivider
) / this.chargingStation
.getNumberOfPhases());
207 switch (this.chargingStation
.getCurrentOutType()) {
209 if (this.chargingStation
.getNumberOfPhases() === 3) {
210 const defaultFluctuatedPowerPerPhase
= powerSampledValueTemplate
.value
211 && Utils
.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate
.value
) / this.chargingStation
.getNumberOfPhases(), powerSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
212 const phase1FluctuatedValue
= powerPerPhaseSampledValueTemplates
?.L1
?.value
213 && Utils
.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates
.L1
.value
), powerPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
214 const phase2FluctuatedValue
= powerPerPhaseSampledValueTemplates
?.L2
?.value
215 && Utils
.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates
.L2
.value
), powerPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
216 const phase3FluctuatedValue
= powerPerPhaseSampledValueTemplates
?.L3
?.value
217 && Utils
.getRandomFloatFluctuatedRounded(parseInt(powerPerPhaseSampledValueTemplates
.L3
.value
), powerPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
218 powerMeasurandValues
.L1
= (phase1FluctuatedValue
?? defaultFluctuatedPowerPerPhase
) ?? Utils
.getRandomFloatRounded(maxPowerPerPhase
/ unitDivider
);
219 powerMeasurandValues
.L2
= (phase2FluctuatedValue
?? defaultFluctuatedPowerPerPhase
) ?? Utils
.getRandomFloatRounded(maxPowerPerPhase
/ unitDivider
);
220 powerMeasurandValues
.L3
= (phase3FluctuatedValue
?? defaultFluctuatedPowerPerPhase
) ?? Utils
.getRandomFloatRounded(maxPowerPerPhase
/ unitDivider
);
222 powerMeasurandValues
.L1
= powerSampledValueTemplate
.value
223 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate
.value
), powerSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
224 : Utils
.getRandomFloatRounded(maxPower
/ unitDivider
);
225 powerMeasurandValues
.L2
= 0;
226 powerMeasurandValues
.L3
= 0;
228 powerMeasurandValues
.allPhases
= Utils
.roundTo(powerMeasurandValues
.L1
+ powerMeasurandValues
.L2
+ powerMeasurandValues
.L3
, 2);
231 powerMeasurandValues
.allPhases
= powerSampledValueTemplate
.value
232 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(powerSampledValueTemplate
.value
), powerSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
233 : Utils
.getRandomFloatRounded(maxPower
/ unitDivider
);
236 logger
.error(errMsg
);
237 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
239 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(powerSampledValueTemplate
, powerMeasurandValues
.allPhases
));
240 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
241 const maxPowerRounded
= Utils
.roundTo(maxPower
/ unitDivider
, 2);
242 if (Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) > maxPowerRounded
|| debug
) {
243 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxPowerRounded}`);
245 for (let phase
= 1; this.chargingStation
.getNumberOfPhases() === 3 && phase
<= this.chargingStation
.getNumberOfPhases(); phase
++) {
246 const phaseValue
= `L${phase}-N`;
247 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(powerPerPhaseSampledValueTemplates
[`L${phase}`] ?? powerSampledValueTemplate
, powerMeasurandValues
[`L${phase}`], null,
248 phaseValue
as OCPP16MeterValuePhase
));
249 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
250 const maxPowerPerPhaseRounded
= Utils
.roundTo(maxPowerPerPhase
/ unitDivider
, 2);
251 if (Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) > maxPowerPerPhaseRounded
|| debug
) {
252 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: phase ${meterValue.sampledValue[sampledValuesPerPhaseIndex].phase}, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].value}/${maxPowerPerPhaseRounded}`);
256 // Current.Import measurand
257 const currentSampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.CURRENT_IMPORT
);
258 let currentPerPhaseSampledValueTemplates
: MeasurandPerPhaseSampledValueTemplates
= {};
259 if (this.chargingStation
.getNumberOfPhases() === 3) {
260 currentPerPhaseSampledValueTemplates
= {
261 L1
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.CURRENT_IMPORT
, OCPP16MeterValuePhase
.L1
),
262 L2
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.CURRENT_IMPORT
, OCPP16MeterValuePhase
.L2
),
263 L3
: this.chargingStation
.getSampledValueTemplate(connectorId
, OCPP16MeterValueMeasurand
.CURRENT_IMPORT
, OCPP16MeterValuePhase
.L3
),
266 if (currentSampledValueTemplate
) {
267 OCPP16ServiceUtils
.checkMeasurandPowerDivider(this.chargingStation
, currentSampledValueTemplate
.measurand
);
268 const errMsg
= `${this.chargingStation.logPrefix()} MeterValues measurand ${currentSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: Unknown ${this.chargingStation.getCurrentOutType()} currentOutType in template file ${this.chargingStation.stationTemplateFile}, cannot calculate ${currentSampledValueTemplate.measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER} measurand value`;
269 const currentMeasurandValues
: MeasurandValues
= {} as MeasurandValues
;
270 let maxAmperage
: number;
271 switch (this.chargingStation
.getCurrentOutType()) {
273 maxAmperage
= ACElectricUtils
.amperagePerPhaseFromPower(this.chargingStation
.getNumberOfPhases(), this.chargingStation
.stationInfo
.maxPower
/ this.chargingStation
.stationInfo
.powerDivider
, this.chargingStation
.getVoltageOut());
274 if (this.chargingStation
.getNumberOfPhases() === 3) {
275 const defaultFluctuatedAmperagePerPhase
= currentSampledValueTemplate
.value
276 && Utils
.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate
.value
), currentSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
277 const phase1FluctuatedValue
= currentPerPhaseSampledValueTemplates
?.L1
?.value
278 && Utils
.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates
.L1
.value
), currentPerPhaseSampledValueTemplates
.L1
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
279 const phase2FluctuatedValue
= currentPerPhaseSampledValueTemplates
?.L2
?.value
280 && Utils
.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates
.L2
.value
), currentPerPhaseSampledValueTemplates
.L2
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
281 const phase3FluctuatedValue
= currentPerPhaseSampledValueTemplates
?.L3
?.value
282 && Utils
.getRandomFloatFluctuatedRounded(parseInt(currentPerPhaseSampledValueTemplates
.L3
.value
), currentPerPhaseSampledValueTemplates
.L3
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
);
283 currentMeasurandValues
.L1
= (phase1FluctuatedValue
?? defaultFluctuatedAmperagePerPhase
) ?? Utils
.getRandomFloatRounded(maxAmperage
);
284 currentMeasurandValues
.L2
= (phase2FluctuatedValue
?? defaultFluctuatedAmperagePerPhase
) ?? Utils
.getRandomFloatRounded(maxAmperage
);
285 currentMeasurandValues
.L3
= (phase3FluctuatedValue
?? defaultFluctuatedAmperagePerPhase
) ?? Utils
.getRandomFloatRounded(maxAmperage
);
287 currentMeasurandValues
.L1
= currentSampledValueTemplate
.value
288 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate
.value
), currentSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
289 : Utils
.getRandomFloatRounded(maxAmperage
);
290 currentMeasurandValues
.L2
= 0;
291 currentMeasurandValues
.L3
= 0;
293 currentMeasurandValues
.allPhases
= Utils
.roundTo((currentMeasurandValues
.L1
+ currentMeasurandValues
.L2
+ currentMeasurandValues
.L3
) / this.chargingStation
.getNumberOfPhases(), 2);
296 maxAmperage
= DCElectricUtils
.amperage(this.chargingStation
.stationInfo
.maxPower
/ this.chargingStation
.stationInfo
.powerDivider
, this.chargingStation
.getVoltageOut());
297 currentMeasurandValues
.allPhases
= currentSampledValueTemplate
.value
298 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(currentSampledValueTemplate
.value
), currentSampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
299 : Utils
.getRandomFloatRounded(maxAmperage
);
302 logger
.error(errMsg
);
303 throw new OCPPError(ErrorType
.INTERNAL_ERROR
, errMsg
, OCPP16RequestCommand
.METER_VALUES
);
305 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(currentSampledValueTemplate
, currentMeasurandValues
.allPhases
));
306 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
307 if (Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesIndex
].value
) > maxAmperage
|| debug
) {
308 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesIndex].value}/${maxAmperage}`);
310 for (let phase
= 1; this.chargingStation
.getNumberOfPhases() === 3 && phase
<= this.chargingStation
.getNumberOfPhases(); phase
++) {
311 const phaseValue
= `L${phase}`;
312 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(currentPerPhaseSampledValueTemplates
[phaseValue
] ?? currentSampledValueTemplate
,
313 currentMeasurandValues
[phaseValue
], null, phaseValue
as OCPP16MeterValuePhase
));
314 const sampledValuesPerPhaseIndex
= meterValue
.sampledValue
.length
- 1;
315 if (Utils
.convertToFloat(meterValue
.sampledValue
[sampledValuesPerPhaseIndex
].value
) > maxAmperage
|| debug
) {
316 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesPerPhaseIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: phase ${meterValue.sampledValue[sampledValuesPerPhaseIndex].phase}, connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${meterValue.sampledValue[sampledValuesPerPhaseIndex].value}/${maxAmperage}`);
320 // Energy.Active.Import.Register measurand (default)
321 const energySampledValueTemplate
= this.chargingStation
.getSampledValueTemplate(connectorId
);
322 if (energySampledValueTemplate
) {
323 OCPP16ServiceUtils
.checkMeasurandPowerDivider(this.chargingStation
, energySampledValueTemplate
.measurand
);
324 const unitDivider
= energySampledValueTemplate
?.unit
=== MeterValueUnit
.KILO_WATT_HOUR
? 1000 : 1;
325 const maxEnergyRounded
= Utils
.roundTo(((this.chargingStation
.stationInfo
.maxPower
/ this.chargingStation
.stationInfo
.powerDivider
) * interval
) / (3600 * 1000), 2);
326 const energyValueRounded
= energySampledValueTemplate
.value
327 // Cumulate the fluctuated value around the static one
328 ? Utils
.getRandomFloatFluctuatedRounded(parseInt(energySampledValueTemplate
.value
), energySampledValueTemplate
.fluctuationPercent
?? Constants
.DEFAULT_FLUCTUATION_PERCENT
)
329 : Utils
.getRandomFloatRounded(maxEnergyRounded
);
330 // Persist previous value on connector
331 if (connector
&& !Utils
.isNullOrUndefined(connector
.energyActiveImportRegisterValue
) && connector
.energyActiveImportRegisterValue
>= 0 &&
332 !Utils
.isNullOrUndefined(connector
.transactionEnergyActiveImportRegisterValue
) && connector
.transactionEnergyActiveImportRegisterValue
>= 0) {
333 connector
.energyActiveImportRegisterValue
+= energyValueRounded
;
334 connector
.transactionEnergyActiveImportRegisterValue
+= energyValueRounded
;
336 connector
.energyActiveImportRegisterValue
= 0;
337 connector
.transactionEnergyActiveImportRegisterValue
= 0;
339 meterValue
.sampledValue
.push(OCPP16ServiceUtils
.buildSampledValue(energySampledValueTemplate
,
340 Utils
.roundTo(this.chargingStation
.getEnergyActiveImportRegisterByTransactionId(transactionId
) / unitDivider
, 2)));
341 const sampledValuesIndex
= meterValue
.sampledValue
.length
- 1;
342 if (energyValueRounded
> maxEnergyRounded
|| debug
) {
343 logger
.error(`${this.chargingStation.logPrefix()} MeterValues measurand ${meterValue.sampledValue[sampledValuesIndex].measurand ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: connectorId ${connectorId}, transaction ${connector.transactionId}, value: ${energyValueRounded}/${maxEnergyRounded}, duration: ${Utils.roundTo(interval / (3600 * 1000), 4)}h`);
346 const payload
: MeterValuesRequest
= {
349 meterValue
: [meterValue
],
351 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.METER_VALUES
);
353 this.handleRequestError(OCPP16RequestCommand
.METER_VALUES
, error
as Error);
357 public async sendTransactionBeginMeterValues(connectorId
: number, transactionId
: number, beginMeterValue
: OCPP16MeterValue
): Promise
<void> {
359 const payload
: MeterValuesRequest
= {
362 meterValue
: [beginMeterValue
],
364 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.METER_VALUES
);
366 this.handleRequestError(OCPP16RequestCommand
.METER_VALUES
, error
as Error);
370 public async sendTransactionEndMeterValues(connectorId
: number, transactionId
: number, endMeterValue
: OCPP16MeterValue
): Promise
<void> {
372 const payload
: MeterValuesRequest
= {
375 meterValue
: [endMeterValue
],
377 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.METER_VALUES
);
379 this.handleRequestError(OCPP16RequestCommand
.METER_VALUES
, error
as Error);
383 public async sendDiagnosticsStatusNotification(diagnosticsStatus
: OCPP16DiagnosticsStatus
): Promise
<void> {
385 const payload
: DiagnosticsStatusNotificationRequest
= {
386 status: diagnosticsStatus
388 await this.sendMessage(Utils
.generateUUID(), payload
, MessageType
.CALL_MESSAGE
, OCPP16RequestCommand
.DIAGNOSTICS_STATUS_NOTIFICATION
);
390 this.handleRequestError(OCPP16RequestCommand
.METER_VALUES
, error
as Error);
394 public async sendResult(messageId
: string, resultMessageData
: JsonType
, commandName
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
): Promise
<JsonType
> {
397 return await this.sendMessage(messageId
, resultMessageData
, MessageType
.CALL_RESULT_MESSAGE
, commandName
) as JsonType
;
399 this.handleRequestError(commandName
as OCPP16RequestCommand
, error
as Error);
403 public async sendError(messageId
: string, ocppError
: OCPPError
, commandName
: OCPP16RequestCommand
| OCPP16IncomingRequestCommand
): Promise
<JsonType
> {
406 return await this.sendMessage(messageId
, ocppError
, MessageType
.CALL_ERROR_MESSAGE
, commandName
) as JsonType
;
408 this.handleRequestError(commandName
as OCPP16RequestCommand
, error
as Error);