// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
-import path from 'node:path';
-import { fileURLToPath } from 'node:url';
-
import type { JSONSchemaType } from 'ajv';
-import type { ChargingStation } from '../../../charging-station';
+import { type ChargingStation, ChargingStationUtils } from '../../../charging-station';
import { OCPPError } from '../../../exception';
import {
CurrentType,
MeterValueContext,
MeterValueLocation,
MeterValueUnit,
+ OCPP16AuthorizationStatus,
+ type OCPP16AuthorizeRequest,
+ type OCPP16AuthorizeResponse,
type OCPP16ChargingProfile,
type OCPP16IncomingRequestCommand,
type OCPP16MeterValue,
Voltage,
} from '../../../types';
import { ACElectricUtils, Constants, DCElectricUtils, Utils, logger } from '../../../utils';
-import { OCPPServiceUtils } from '../internal';
+import { OCPPServiceUtils } from '../OCPPServiceUtils';
export class OCPP16ServiceUtils extends OCPPServiceUtils {
public static checkFeatureProfile(
OCPP16MeterValueMeasurand.STATE_OF_CHARGE
);
if (socSampledValueTemplate) {
+ const socMaximumValue = 100;
+ const socMinimumValue = socSampledValueTemplate.minimumValue ?? 0;
const socSampledValueTemplateValue = socSampledValueTemplate.value
? Utils.getRandomFloatFluctuatedRounded(
parseInt(socSampledValueTemplate.value),
socSampledValueTemplate.fluctuationPercent ?? Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomInteger(100);
+ : Utils.getRandomInteger(socMaximumValue, socMinimumValue);
meterValue.sampledValue.push(
OCPP16ServiceUtils.buildSampledValue(socSampledValueTemplate, socSampledValueTemplateValue)
);
const sampledValuesIndex = meterValue.sampledValue.length - 1;
- if (Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > 100 || debug) {
+ if (
+ Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) > socMaximumValue ||
+ Utils.convertToInt(meterValue.sampledValue[sampledValuesIndex].value) < socMinimumValue ||
+ debug
+ ) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
+ }: connector id ${connectorId}, transaction id ${
+ connector?.transactionId
+ }, value: ${socMinimumValue}/${
meterValue.sampledValue[sampledValuesIndex].value
- }/100`
+ }/${socMaximumValue}}`
);
}
}
connectorId,
OCPP16MeterValueMeasurand.POWER_ACTIVE_IMPORT
);
- let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates =
- Constants.EMPTY_OBJECT;
+ let powerPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
if (chargingStation.getNumberOfPhases() === 3) {
powerPerPhaseSampledValueTemplates = {
L1: OCPP16ServiceUtils.getSampledValueTemplate(
powerSampledValueTemplate.measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
} measurand value`;
- const powerMeasurandValues = Constants.EMPTY_OBJECT as MeasurandValues;
+ const powerMeasurandValues = {} as MeasurandValues;
const unitDivider = powerSampledValueTemplate?.unit === MeterValueUnit.KILO_WATT ? 1000 : 1;
const connectorMaximumAvailablePower =
chargingStation.getConnectorMaximumAvailablePower(connectorId);
const connectorMaximumPowerPerPhase = Math.round(
connectorMaximumAvailablePower / chargingStation.getNumberOfPhases()
);
+ const connectorMinimumPower = Math.round(powerSampledValueTemplate.minimumValue) ?? 0;
+ const connectorMinimumPowerPerPhase = Math.round(
+ connectorMinimumPower / chargingStation.getNumberOfPhases()
+ );
switch (chargingStation.getCurrentOutType()) {
case CurrentType.AC:
if (chargingStation.getNumberOfPhases() === 3) {
powerMeasurandValues.L1 =
phase1FluctuatedValue ??
defaultFluctuatedPowerPerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / unitDivider
+ );
powerMeasurandValues.L2 =
phase2FluctuatedValue ??
defaultFluctuatedPowerPerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / unitDivider
+ );
powerMeasurandValues.L3 =
phase3FluctuatedValue ??
defaultFluctuatedPowerPerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumPowerPerPhase / unitDivider);
+ Utils.getRandomFloatRounded(
+ connectorMaximumPowerPerPhase / unitDivider,
+ connectorMinimumPowerPerPhase / unitDivider
+ );
} else {
powerMeasurandValues.L1 = powerSampledValueTemplate.value
? Utils.getRandomFloatFluctuatedRounded(
powerSampledValueTemplate.fluctuationPercent ??
Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
+ : Utils.getRandomFloatRounded(
+ connectorMaximumPower / unitDivider,
+ connectorMinimumPower / unitDivider
+ );
powerMeasurandValues.L2 = 0;
powerMeasurandValues.L3 = 0;
}
powerSampledValueTemplate.fluctuationPercent ??
Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomFloatRounded(connectorMaximumPower / unitDivider);
+ : Utils.getRandomFloatRounded(
+ connectorMaximumPower / unitDivider,
+ connectorMinimumPower / unitDivider
+ );
break;
default:
logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
);
const sampledValuesIndex = meterValue.sampledValue.length - 1;
const connectorMaximumPowerRounded = Utils.roundTo(connectorMaximumPower / unitDivider, 2);
+ const connectorMinimumPowerRounded = Utils.roundTo(connectorMinimumPower / unitDivider, 2);
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
connectorMaximumPowerRounded ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+ connectorMinimumPowerRounded ||
debug
) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
+ }: connector id ${connectorId}, transaction id ${
+ connector?.transactionId
+ }, value: ${connectorMinimumPowerRounded}/${
meterValue.sampledValue[sampledValuesIndex].value
}/${connectorMaximumPowerRounded}`
);
connectorMaximumPowerPerPhase / unitDivider,
2
);
+ const connectorMinimumPowerPerPhaseRounded = Utils.roundTo(
+ connectorMinimumPowerPerPhase / unitDivider,
+ 2
+ );
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
connectorMaximumPowerPerPhaseRounded ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+ connectorMinimumPowerPerPhaseRounded ||
debug
) {
logger.error(
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: phase ${
meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
- }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
+ }, connector id ${connectorId}, transaction id ${
+ connector?.transactionId
+ }, value: ${connectorMinimumPowerPerPhaseRounded}/${
meterValue.sampledValue[sampledValuesPerPhaseIndex].value
}/${connectorMaximumPowerPerPhaseRounded}`
);
connectorId,
OCPP16MeterValueMeasurand.CURRENT_IMPORT
);
- let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates =
- Constants.EMPTY_OBJECT;
+ let currentPerPhaseSampledValueTemplates: MeasurandPerPhaseSampledValueTemplates = {};
if (chargingStation.getNumberOfPhases() === 3) {
currentPerPhaseSampledValueTemplates = {
L1: OCPP16ServiceUtils.getSampledValueTemplate(
currentSampledValueTemplate.measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
} measurand value`;
- const currentMeasurandValues: MeasurandValues = Constants.EMPTY_OBJECT as MeasurandValues;
+ const currentMeasurandValues: MeasurandValues = {} as MeasurandValues;
const connectorMaximumAvailablePower =
chargingStation.getConnectorMaximumAvailablePower(connectorId);
+ const connectorMinimumAmperage = currentSampledValueTemplate.minimumValue ?? 0;
let connectorMaximumAmperage: number;
switch (chargingStation.getCurrentOutType()) {
case CurrentType.AC:
currentMeasurandValues.L1 =
phase1FluctuatedValue ??
defaultFluctuatedAmperagePerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L2 =
phase2FluctuatedValue ??
defaultFluctuatedAmperagePerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L3 =
phase3FluctuatedValue ??
defaultFluctuatedAmperagePerPhase ??
- Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
} else {
currentMeasurandValues.L1 = currentSampledValueTemplate.value
? Utils.getRandomFloatFluctuatedRounded(
currentSampledValueTemplate.fluctuationPercent ??
Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ : Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
currentMeasurandValues.L2 = 0;
currentMeasurandValues.L3 = 0;
}
currentSampledValueTemplate.fluctuationPercent ??
Constants.DEFAULT_FLUCTUATION_PERCENT
)
- : Utils.getRandomFloatRounded(connectorMaximumAmperage);
+ : Utils.getRandomFloatRounded(connectorMaximumAmperage, connectorMinimumAmperage);
break;
default:
logger.error(`${chargingStation.logPrefix()} ${errMsg}`);
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) >
connectorMaximumAmperage ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesIndex].value) <
+ connectorMinimumAmperage ||
debug
) {
logger.error(
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
+ }: connector id ${connectorId}, transaction id ${
+ connector?.transactionId
+ }, value: ${connectorMinimumAmperage}/${
meterValue.sampledValue[sampledValuesIndex].value
}/${connectorMaximumAmperage}`
);
if (
Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) >
connectorMaximumAmperage ||
+ Utils.convertToFloat(meterValue.sampledValue[sampledValuesPerPhaseIndex].value) <
+ connectorMinimumAmperage ||
debug
) {
logger.error(
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
}: phase ${
meterValue.sampledValue[sampledValuesPerPhaseIndex].phase
- }, connectorId ${connectorId}, transaction ${connector?.transactionId}, value: ${
+ }, connector id ${connectorId}, transaction id ${
+ connector?.transactionId
+ }, value: ${connectorMinimumAmperage}/${
meterValue.sampledValue[sampledValuesPerPhaseIndex].value
}/${connectorMaximumAmperage}`
);
`${chargingStation.logPrefix()} MeterValues measurand ${
meterValue.sampledValue[sampledValuesIndex].measurand ??
OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER
- }: connectorId ${connectorId}, transaction ${
+ }: connector id ${connectorId}, transaction id ${
connector?.transactionId
}, value: ${energyValueRounded}/${connectorMaximumEnergyRounded}, duration: ${Utils.roundTo(
interval / (3600 * 1000),
Utils.isNullOrUndefined(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles)
) {
logger.error(
- `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
+ `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an uninitialized charging profiles array attribute, applying deferred initialization`
);
chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
}
Array.isArray(chargingStation.getConnectorStatus(connectorId)?.chargingProfiles) === false
) {
logger.error(
- `${chargingStation.logPrefix()} Trying to set a charging profile on connectorId ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
+ `${chargingStation.logPrefix()} Trying to set a charging profile on connector id ${connectorId} with an improper attribute type for the charging profiles array, applying proper type initialization`
);
chargingStation.getConnectorStatus(connectorId).chargingProfiles = [];
}
methodName?: string
): JSONSchemaType<T> {
return super.parseJsonSchemaFile<T>(
- path.resolve(path.dirname(fileURLToPath(import.meta.url)), relativePath),
+ relativePath,
OCPPVersion.VERSION_16,
moduleName,
methodName
);
}
+ public static async isIdTagAuthorized(
+ chargingStation: ChargingStation,
+ connectorId: number,
+ idTag: string,
+ parentIdTag?: string
+ ): Promise<boolean> {
+ let authorized = false;
+ const connectorStatus = chargingStation.getConnectorStatus(connectorId);
+ if (OCPP16ServiceUtils.isIdTagLocalAuthorized(chargingStation, idTag)) {
+ connectorStatus.localAuthorizeIdTag = idTag;
+ connectorStatus.idTagLocalAuthorized = true;
+ authorized = true;
+ } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
+ connectorStatus.authorizeIdTag = idTag;
+ authorized = await OCPP16ServiceUtils.isIdTagRemoteAuthorized(chargingStation, idTag);
+ } else {
+ logger.warn(
+ `${chargingStation.logPrefix()} The charging station configuration expects authorize at
+ remote start transaction but local authorization or authorize isn't enabled`
+ );
+ }
+ return authorized;
+ }
+
private static buildSampledValue(
sampledValueTemplate: SampledValueTemplate,
value: number,
return MeterValueUnit.VOLT;
}
}
+
+ private static isIdTagLocalAuthorized(chargingStation: ChargingStation, idTag: string): boolean {
+ return (
+ chargingStation.getLocalAuthListEnabled() === true &&
+ chargingStation.hasIdTags() === true &&
+ Utils.isNotEmptyString(
+ chargingStation.idTagsCache
+ .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo))
+ ?.find((tag) => tag === idTag)
+ )
+ );
+ }
+
+ private static async isIdTagRemoteAuthorized(
+ chargingStation: ChargingStation,
+ idTag: string
+ ): Promise<boolean> {
+ const authorizeResponse: OCPP16AuthorizeResponse =
+ await chargingStation.ocppRequestService.requestHandler<
+ OCPP16AuthorizeRequest,
+ OCPP16AuthorizeResponse
+ >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
+ idTag: idTag,
+ });
+ return authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED;
+ }
}