Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

Commit

Permalink
Add support for transaction data MeterValues
Browse files Browse the repository at this point in the history
Signed-off-by: Jérôme Benoit <[email protected]>
  • Loading branch information
Jérôme Benoit committed Jun 13, 2021
1 parent 6f9e2fc commit fd0c36f
Show file tree
Hide file tree
Showing 12 changed files with 98 additions and 60 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ enableStatistics | true/false | true | boolean | enable charging stations statis
beginEndMeterValues | true/false | false | boolean | enable Transaction.{Begin,End} MeterValues
outOfOrderEndMeterValues | true/false | false | boolean | send Transaction.End MeterValues out of order
meteringPerTransaction | true/false | true | boolean | disable metering on a per transaction basis
transactionDataMeterValues | true/false | false | boolean | enable transaction data MeterValues at stop transaction
Configuration | | | ChargingStationConfiguration | charging stations OCPP configuration parameters
AutomaticTransactionGenerator | | | AutomaticTransactionGenerator | charging stations ATG configuration
Connectors | | | Connectors | charging stations connectors configuration
Expand Down
15 changes: 11 additions & 4 deletions src/charging-station/ChargingStation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ export default class ChargingStation {
return this.stationInfo.meteringPerTransaction ?? true;
}

public getTransactionDataMeterValues(): boolean {
return this.stationInfo.transactionDataMeterValues ?? false;
}

public getEnergyActiveImportRegisterByTransactionId(transactionId: number): number | undefined {
if (this.getMeteringPerTransaction()) {
for (const connector in this.connectors) {
Expand Down Expand Up @@ -349,6 +353,7 @@ export default class ChargingStation {
delete this.getConnector(connectorId).transactionId;
delete this.getConnector(connectorId).idTag;
this.getConnector(connectorId).transactionEnergyActiveImportRegisterValue = 0;
delete this.getConnector(connectorId).transactionBeginMeterValue;
this.stopMeterValues(connectorId);
}

Expand Down Expand Up @@ -405,9 +410,11 @@ export default class ChargingStation {
} else {
stationTemplateFromFile.power = stationTemplateFromFile.power as number;
stationInfo.maxPower = stationTemplateFromFile.powerUnit === PowerUnits.KILO_WATT
? (stationTemplateFromFile.power) * 1000
? stationTemplateFromFile.power * 1000
: stationTemplateFromFile.power;
}
delete stationInfo.power;
delete stationInfo.powerUnit;
stationInfo.chargingStationId = this.getChargingStationId(stationTemplateFromFile);
stationInfo.resetTime = stationTemplateFromFile.resetTime ? stationTemplateFromFile.resetTime * 1000 : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
return stationInfo;
Expand Down Expand Up @@ -882,7 +889,7 @@ export default class ChargingStation {
const authorizationFile = this.getAuthorizationFile();
if (authorizationFile) {
try {
fs.watch(authorizationFile).on('change', (e) => {
fs.watch(authorizationFile).on('change', () => {
try {
logger.debug(this.logPrefix() + ' Authorization file ' + authorizationFile + ' have changed, reload');
// Initialize authorizedTags
Expand All @@ -901,8 +908,8 @@ export default class ChargingStation {

private startStationTemplateFileMonitoring(): void {
try {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
fs.watch(this.stationTemplateFile).on('change', async (e): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
fs.watch(this.stationTemplateFile).on('change', async (): Promise<void> => {
try {
logger.debug(this.logPrefix() + ' Template file ' + this.stationTemplateFile + ' have changed, reload');
// Initialize
Expand Down
59 changes: 19 additions & 40 deletions src/charging-station/ocpp/1.6/OCPP16RequestService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ACElectricUtils, DCElectricUtils } from '../../../utils/ElectricUtils';
import { AuthorizeRequest, OCPP16AuthorizeResponse, OCPP16StartTransactionResponse, OCPP16StopTransactionReason, OCPP16StopTransactionResponse, StartTransactionRequest, StopTransactionRequest } from '../../../types/ocpp/1.6/Transaction';
import { HeartbeatRequest, OCPP16BootNotificationRequest, OCPP16IncomingRequestCommand, OCPP16RequestCommand, StatusNotificationRequest } from '../../../types/ocpp/1.6/Requests';
import { MeterValue, MeterValueContext, MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
import { MeterValuePhase, MeterValueUnit, MeterValuesRequest, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';

import Constants from '../../../utils/Constants';
import { CurrentOutType } from '../../../types/ChargingStationTemplate';
Expand All @@ -27,7 +27,8 @@ export default class OCPP16RequestService extends OCPPRequestService {
}
}

public async sendBootNotification(chargePointModel: string, chargePointVendor: string, chargeBoxSerialNumber?: string, firmwareVersion?: string, chargePointSerialNumber?: string, iccid?: string, imsi?: string, meterSerialNumber?: string, meterType?: string): Promise<OCPP16BootNotificationResponse> {
public async sendBootNotification(chargePointModel: string, chargePointVendor: string, chargeBoxSerialNumber?: string, firmwareVersion?: string,
chargePointSerialNumber?: string, iccid?: string, imsi?: string, meterSerialNumber?: string, meterType?: string): Promise<OCPP16BootNotificationResponse> {
try {
const payload: OCPP16BootNotificationRequest = {
chargePointModel,
Expand Down Expand Up @@ -88,21 +89,24 @@ export default class OCPP16RequestService extends OCPPRequestService {
public async sendStopTransaction(transactionId: number, meterStop: number, idTag?: string,
reason: OCPP16StopTransactionReason = OCPP16StopTransactionReason.NONE): Promise<OCPP16StopTransactionResponse> {
try {
const payload: StopTransactionRequest = {
transactionId,
...!Utils.isUndefined(idTag) && { idTag },
meterStop: meterStop,
timestamp: new Date().toISOString(),
...reason && { reason },
};
let connectorId: number;
for (const connector in this.chargingStation.connectors) {
if (Utils.convertToInt(connector) > 0 && this.chargingStation.getConnector(Utils.convertToInt(connector))?.transactionId === transactionId) {
connectorId = Utils.convertToInt(connector);
}
}
const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(this.chargingStation, connectorId, meterStop);
// FIXME: should be a callback, each OCPP commands implementation must do only one job
(this.chargingStation.getBeginEndMeterValues() && !this.chargingStation.getOutOfOrderEndMeterValues())
&& await this.sendTransactionEndMeterValues(connectorId, transactionId, meterStop);
&& await this.sendTransactionEndMeterValues(connectorId, transactionId, transactionEndMeterValue);
const payload: StopTransactionRequest = {
transactionId,
...!Utils.isUndefined(idTag) && { idTag },
meterStop,
timestamp: new Date().toISOString(),
...reason && { reason },
...this.chargingStation.getTransactionDataMeterValues() && { transactionData: OCPP16ServiceUtils.buildTransactionDataMeterValues(this.chargingStation.getConnector(connectorId).transactionBeginMeterValue, transactionEndMeterValue) },
};
return await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.STOP_TRANSACTION) as OCPP16StartTransactionResponse;
} catch (error) {
this.handleRequestError(OCPP16RequestCommand.STOP_TRANSACTION, error);
Expand All @@ -112,7 +116,7 @@ export default class OCPP16RequestService extends OCPPRequestService {
// eslint-disable-next-line consistent-this
public async sendMeterValues(connectorId: number, transactionId: number, interval: number, self: OCPPRequestService, debug = false): Promise<void> {
try {
const meterValue: MeterValue = {
const meterValue: OCPP16MeterValue = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
Expand Down Expand Up @@ -261,45 +265,20 @@ export default class OCPP16RequestService extends OCPPRequestService {
}
}

public async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number): Promise<void> {
const meterValue: MeterValue = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
for (let index = 0; index < meterValuesTemplate.length; index++) {
// Energy.Active.Import.Register measurand (default)
if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index],
Utils.roundTo(meterBegin / unitDivider, 4), MeterValueContext.TRANSACTION_BEGIN));
}
}
public async sendTransactionBeginMeterValues(connectorId: number, transactionId: number, beginMeterValue: OCPP16MeterValue): Promise<void> {
const payload: MeterValuesRequest = {
connectorId,
transactionId,
meterValue,
meterValue: beginMeterValue,
};
await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
}

public async sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number): Promise<void> {
const meterValue: MeterValue = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
const meterValuesTemplate: OCPP16SampledValue[] = this.chargingStation.getConnector(connectorId).MeterValues;
for (let index = 0; index < meterValuesTemplate.length; index++) {
// Energy.Active.Import.Register measurand (default)
if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.roundTo(meterEnd / unitDivider, 4), MeterValueContext.TRANSACTION_END));
}
}
public async sendTransactionEndMeterValues(connectorId: number, transactionId: number, endMeterValue: OCPP16MeterValue): Promise<void> {
const payload: MeterValuesRequest = {
connectorId,
transactionId,
meterValue,
meterValue: endMeterValue,
};
await this.sendMessage(Utils.generateUUID(), payload, MessageType.CALL_MESSAGE, OCPP16RequestCommand.METER_VALUES);
}
Expand Down
8 changes: 6 additions & 2 deletions src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HeartbeatResponse, OCPP16BootNotificationResponse, OCPP16RegistrationSt
import { MeterValuesRequest, MeterValuesResponse } from '../../../types/ocpp/1.6/MeterValues';

import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
import OCPPResponseService from '../OCPPResponseService';
import Utils from '../../../utils/Utils';
Expand Down Expand Up @@ -55,8 +56,10 @@ export default class OCPP16ResponseService extends OCPPResponseService {
this.chargingStation.getConnector(connectorId).transactionId = payload.transactionId;
this.chargingStation.getConnector(connectorId).idTag = requestPayload.idTag;
this.chargingStation.getConnector(connectorId).transactionEnergyActiveImportRegisterValue = 0;
this.chargingStation.getConnector(connectorId).transactionBeginMeterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(this.chargingStation, connectorId,
requestPayload.meterStart);
this.chargingStation.getBeginEndMeterValues() && await this.chargingStation.ocppRequestService.sendTransactionBeginMeterValues(connectorId, payload.transactionId,
this.chargingStation.getEnergyActiveImportRegisterByTransactionId(payload.transactionId));
this.chargingStation.getConnector(connectorId).transactionBeginMeterValue);
await this.chargingStation.ocppRequestService.sendStatusNotification(connectorId, OCPP16ChargePointStatus.CHARGING);
this.chargingStation.getConnector(connectorId).status = OCPP16ChargePointStatus.CHARGING;
logger.info(this.chargingStation.logPrefix() + ' Transaction ' + payload.transactionId.toString() + ' STARTED on ' + this.chargingStation.stationInfo.chargingStationId + '#' + connectorId.toString() + ' for idTag ' + requestPayload.idTag);
Expand Down Expand Up @@ -87,7 +90,8 @@ export default class OCPP16ResponseService extends OCPPResponseService {
}
if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
(this.chargingStation.getBeginEndMeterValues() && this.chargingStation.getOutOfOrderEndMeterValues())
&& await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(transactionConnectorId, requestPayload.transactionId, requestPayload.meterStop);
&& await this.chargingStation.ocppRequestService.sendTransactionEndMeterValues(transactionConnectorId, requestPayload.transactionId,
OCPP16ServiceUtils.buildTransactionEndMeterValue(this.chargingStation, transactionConnectorId, requestPayload.meterStop));
if (!this.chargingStation.isChargingStationAvailable() || !this.chargingStation.isConnectorAvailable(transactionConnectorId)) {
await this.chargingStation.ocppRequestService.sendStatusNotification(transactionConnectorId, OCPP16ChargePointStatus.UNAVAILABLE);
this.chargingStation.getConnector(transactionConnectorId).status = OCPP16ChargePointStatus.UNAVAILABLE;
Expand Down
44 changes: 42 additions & 2 deletions src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';
import { MeterValueContext, MeterValueLocation, MeterValuePhase, MeterValueUnit, OCPP16MeterValue, OCPP16MeterValueMeasurand, OCPP16SampledValue } from '../../../types/ocpp/1.6/MeterValues';

import ChargingStation from '../../ChargingStation';
import Utils from '../../../utils/Utils';
Expand All @@ -10,7 +10,7 @@ export class OCPP16ServiceUtils {
const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider is undefined`;
logger.error(errMsg);
throw Error(errMsg);
} else if (chargingStation.stationInfo.powerDivider && chargingStation.stationInfo.powerDivider <= 0) {
} else if (chargingStation.stationInfo?.powerDivider <= 0) {
const errMsg = `${chargingStation.logPrefix()} MeterValues measurand ${measurandType ?? OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER}: powerDivider have zero or below value ${chargingStation.stationInfo.powerDivider}`;
logger.error(errMsg);
throw Error(errMsg);
Expand Down Expand Up @@ -59,4 +59,44 @@ export class OCPP16ServiceUtils {
return MeterValueLocation.EV;
}
}

public static buildTransactionBeginMeterValue(chargingStation: ChargingStation, connectorId: number, meterBegin: number): OCPP16MeterValue {
const meterValue: OCPP16MeterValue = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
const meterValuesTemplate: OCPP16SampledValue[] = chargingStation.getConnector(connectorId).MeterValues;
for (let index = 0; index < meterValuesTemplate.length; index++) {
// Energy.Active.Import.Register measurand (default)
if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index],
Utils.roundTo(meterBegin / unitDivider, 4), MeterValueContext.TRANSACTION_BEGIN));
}
}
return meterValue;
}

public static buildTransactionEndMeterValue(chargingStation: ChargingStation, connectorId: number, meterEnd: number): OCPP16MeterValue {
const meterValue: OCPP16MeterValue = {
timestamp: new Date().toISOString(),
sampledValue: [],
};
const meterValuesTemplate: OCPP16SampledValue[] = chargingStation.getConnector(connectorId).MeterValues;
for (let index = 0; index < meterValuesTemplate.length; index++) {
// Energy.Active.Import.Register measurand (default)
if (!meterValuesTemplate[index].measurand || meterValuesTemplate[index].measurand === OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER) {
const unitDivider = meterValuesTemplate[index]?.unit === MeterValueUnit.KILO_WATT_HOUR ? 1000 : 1;
meterValue.sampledValue.push(OCPP16ServiceUtils.buildSampledValue(meterValuesTemplate[index], Utils.roundTo(meterEnd / unitDivider, 4), MeterValueContext.TRANSACTION_END));
}
}
return meterValue;
}

public static buildTransactionDataMeterValues(transactionBeginMeterValue: OCPP16MeterValue, transactionEndMeterValue: OCPP16MeterValue): OCPP16MeterValue[] {
const meterValues: OCPP16MeterValue[] = [];
meterValues.push(transactionBeginMeterValue);
meterValues.push(transactionEndMeterValue);
return meterValues;
}
}
5 changes: 3 additions & 2 deletions src/charging-station/ocpp/OCPPRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ChargingStation from '../ChargingStation';
import Constants from '../../utils/Constants';
import { ErrorType } from '../../types/ocpp/ErrorType';
import { MessageType } from '../../types/ocpp/MessageType';
import { MeterValue } from '../../types/ocpp/MeterValues';
import OCPPError from '../OcppError';
import OCPPResponseService from './OCPPResponseService';
import logger from '../../utils/Logger';
Expand Down Expand Up @@ -114,7 +115,7 @@ export default abstract class OCPPRequestService {
public abstract sendStartTransaction(connectorId: number, idTag?: string): Promise<StartTransactionResponse>;
public abstract sendStopTransaction(transactionId: number, meterStop: number, idTag?: string, reason?: StopTransactionReason): Promise<StopTransactionResponse>;
public abstract sendMeterValues(connectorId: number, transactionId: number, interval: number, self: OCPPRequestService): Promise<void>;
public abstract sendTransactionBeginMeterValues(connectorId: number, transactionId: number, meterBegin: number): Promise<void>;
public abstract sendTransactionEndMeterValues(connectorId: number, transactionId: number, meterEnd: number): Promise<void>;
public abstract sendTransactionBeginMeterValues(connectorId: number, transactionId: number, beginMeterValue: MeterValue): Promise<void>;
public abstract sendTransactionEndMeterValues(connectorId: number, transactionId: number, endMeterValue: MeterValue): Promise<void>;
public abstract sendError(messageId: string, error: OCPPError, commandName: RequestCommand | IncomingRequestCommand): Promise<unknown>;
}
4 changes: 3 additions & 1 deletion src/types/ChargingStationTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export enum PowerUnits {
export enum VoltageOut {
VOLTAGE_110 = 110,
VOLTAGE_230 = 230,
VOLTAGE_400 = 400
VOLTAGE_400 = 400,
VOLTAGE_800 = 800
}

export interface AutomaticTransactionGenerator {
Expand Down Expand Up @@ -61,6 +62,7 @@ export default interface ChargingStationTemplate {
beginEndMeterValues?: boolean;
outOfOrderEndMeterValues?: boolean;
meteringPerTransaction?: boolean;
transactionDataMeterValues?: boolean;
Configuration?: ChargingStationConfiguration;
AutomaticTransactionGenerator: AutomaticTransactionGenerator;
Connectors: Connectors;
Expand Down
6 changes: 4 additions & 2 deletions src/types/Connectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MeterValue, SampledValue } from './ocpp/MeterValues';

import { AvailabilityType } from './ocpp/Requests';
import { ChargePointStatus } from './ocpp/ChargePointStatus';
import { ChargingProfile } from './ocpp/ChargingProfile';
import { SampledValue } from './ocpp/MeterValues';

export interface Connector {
availability: AvailabilityType;
Expand All @@ -14,7 +15,8 @@ export interface Connector {
idTag?: string;
energyActiveImportRegisterValue?: number; // In Wh
transactionEnergyActiveImportRegisterValue?: number; // In Wh
chargingProfiles?: ChargingProfile[]
transactionBeginMeterValue?: MeterValue;
chargingProfiles?: ChargingProfile[];
}

export default interface Connectors {
Expand Down
Loading

0 comments on commit fd0c36f

Please sign in to comment.