Handle OCPI Auth ID with User
Check Auth ID in OCPI Session (first call)
Add missing Consumption props
Put in common connector runtime data changes
MODIFY
src/client/ocpi/EmspOCPIClient.ts
src/client/ocpi/EmspOCPIClient.ts
@@ -228,7 +228,7 @@ export default class EmspOCPIClient extends OCPIClient {
if (!Utils.isEmptyArray(sessions)) {
await Promise.map(sessions, async (session: OCPISession) => {
try {
- await OCPIUtilsService.updateTransaction(this.tenant, session);
+ await OCPIUtilsService.updateTransaction(this.tenant, session, ServerAction.OCPI_PULL_SESSIONS);
result.success++;
} catch (error) {
result.failure++;

MODIFY
src/server/ocpi/OCPIUtils.ts
src/server/ocpi/OCPIUtils.ts
@@ -27,6 +27,7 @@ import SiteArea from '../../types/SiteArea';
import Tenant from '../../types/Tenant';
import Utils from '../../utils/Utils';
import moment from 'moment';
+import { OCPISessionStatus } from '../../types/ocpi/OCPISession';
const MODULE_NAME = 'OCPIUtils';
@@ -345,6 +346,19 @@ export default class OCPIUtils {
}
}
+ public static convertOCPISessionStatus2ConnectorStatus(status: OCPISessionStatus): ChargePointStatus {
+ switch (status) {
+ case OCPISessionStatus.PENDING:
+ return ChargePointStatus.PREPARING;
+ case OCPISessionStatus.COMPLETED:
+ return ChargePointStatus.AVAILABLE;
+ case OCPISessionStatus.INVALID:
+ return ChargePointStatus.FAULTED;
+ case OCPISessionStatus.ACTIVE:
+ return ChargePointStatus.CHARGING;
+ }
+ }
+
public static convertStatus2OCPIStatus(status: ChargePointStatus): OCPIEvseStatus {
switch (status) {
case ChargePointStatus.AVAILABLE:

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
import * as CountriesList from 'countries-list';
-
-import ChargingStation, { ChargePoint, Connector, ConnectorType, CurrentType, Voltage } from '../../../../types/ChargingStation';
-import { OCPIAvailableEndpoints, OCPIEndpointVersions } from '../../../../types/ocpi/OCPIEndpoint';
-import { OCPICapability, OCPIEvse, OCPIEvseStatus } from '../../../../types/ocpi/OCPIEvse';
-import { OCPIConnector, OCPIConnectorFormat, OCPIConnectorType, OCPIPowerType, OCPIVoltage } from '../../../../types/ocpi/OCPIConnector';
-import { OCPILocation, OCPILocationOptions, OCPILocationType, OCPIOpeningTimes } from '../../../../types/ocpi/OCPILocation';
-import { OCPISession, OCPISessionStatus } from '../../../../types/ocpi/OCPISession';
-import { OCPIToken, OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
-import Transaction, { InactivityStatus } from '../../../../types/Transaction';
-
+import { StatusCodes } from 'http-status-codes';
+import countries from 'i18n-iso-countries';
+import moment from 'moment';
import AppError from '../../../../exception/AppError';
-import { ChargePointStatus } from '../../../../types/ocpp/OCPPServer';
import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
-import Configuration from '../../../../utils/Configuration';
-import Constants from '../../../../utils/Constants';
-import Consumption from '../../../../types/Consumption';
import ConsumptionStorage from '../../../../storage/mongodb/ConsumptionStorage';
-import { DataResult } from '../../../../types/DataResult';
+import SettingStorage from '../../../../storage/mongodb/SettingStorage';
+import SiteStorage from '../../../../storage/mongodb/SiteStorage';
+import TagStorage from '../../../../storage/mongodb/TagStorage';
+import TransactionStorage from '../../../../storage/mongodb/TransactionStorage';
+import ChargingStation, { ChargePoint, Connector, ConnectorType, CurrentType, Voltage } from '../../../../types/ChargingStation';
+import Consumption from '../../../../types/Consumption';
import DbParams from '../../../../types/database/DbParams';
+import { DataResult } from '../../../../types/DataResult';
import { HTTPError } from '../../../../types/HTTPError';
-import Logging from '../../../../utils/Logging';
-import LoggingHelper from '../../../../utils/LoggingHelper';
import { OCPIBusinessDetails } from '../../../../types/ocpi/OCPIBusinessDetails';
import { OCPICdr } from '../../../../types/ocpi/OCPICdr';
+import { OCPIConnector, OCPIConnectorFormat, OCPIConnectorType, OCPIPowerType, OCPIVoltage } from '../../../../types/ocpi/OCPIConnector';
import OCPICredential from '../../../../types/ocpi/OCPICredential';
+import { OCPIAvailableEndpoints, OCPIEndpointVersions } from '../../../../types/ocpi/OCPIEndpoint';
+import { OCPICapability, OCPIEvse, OCPIEvseStatus } from '../../../../types/ocpi/OCPIEvse';
+import { OCPILocation, OCPILocationOptions, OCPILocationType, OCPIOpeningTimes } from '../../../../types/ocpi/OCPILocation';
import { OCPIResponse } from '../../../../types/ocpi/OCPIResponse';
import { OCPIRole } from '../../../../types/ocpi/OCPIRole';
+import { OCPISession, OCPISessionStatus } from '../../../../types/ocpi/OCPISession';
import { OCPIStatusCode } from '../../../../types/ocpi/OCPIStatusCode';
-import OCPIUtils from '../../OCPIUtils';
-import { OcpiSetting } from '../../../../types/Setting';
+import { OCPIToken, OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
+import { ChargePointStatus } from '../../../../types/ocpp/OCPPServer';
import { PricingSource } from '../../../../types/Pricing';
-import RoamingUtils from '../../../../utils/RoamingUtils';
import { ServerAction } from '../../../../types/Server';
-import SettingStorage from '../../../../storage/mongodb/SettingStorage';
+import { OcpiSetting } from '../../../../types/Setting';
import Site from '../../../../types/Site';
-import SiteStorage from '../../../../storage/mongodb/SiteStorage';
-import { StatusCodes } from 'http-status-codes';
import Tag from '../../../../types/Tag';
-import TagStorage from '../../../../storage/mongodb/TagStorage';
import Tenant from '../../../../types/Tenant';
-import TransactionStorage from '../../../../storage/mongodb/TransactionStorage';
+import Transaction, { InactivityStatus } from '../../../../types/Transaction';
import User from '../../../../types/User';
-import UserStorage from '../../../../storage/mongodb/UserStorage';
+import Configuration from '../../../../utils/Configuration';
+import Constants from '../../../../utils/Constants';
+import Logging from '../../../../utils/Logging';
+import LoggingHelper from '../../../../utils/LoggingHelper';
+import RoamingUtils from '../../../../utils/RoamingUtils';
import Utils from '../../../../utils/Utils';
-import countries from 'i18n-iso-countries';
-import moment from 'moment';
+import OCPPUtils from '../../../ocpp/utils/OCPPUtils';
+import OCPIUtils from '../../OCPIUtils';
+
const MODULE_NAME = 'OCPIUtilsService';
@@ -322,10 +321,10 @@ export default class OCPIUtilsService {
}
}
- public static async updateTransaction(tenant: Tenant, session: OCPISession, transaction?: Transaction): Promise<void> {
+ public static async updateTransaction(tenant: Tenant, session: OCPISession, action: ServerAction, transaction?: Transaction): Promise<void> {
if (!OCPIUtilsService.validateSession(session)) {
throw new AppError({
- module: MODULE_NAME, method: 'updateTransaction',
+ module: MODULE_NAME, method: 'updateTransaction', action,
errorCode: StatusCodes.BAD_REQUEST,
message: 'Session object is invalid',
detailedMessages: { session },
@@ -343,74 +342,118 @@ export default class OCPIUtilsService {
if (!transaction) {
transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, session.id);
}
+ // Get the Charging Station
+ const evse = session.location.evses[0];
+ let chargingStation: ChargingStation;
+ if (transaction) {
+ chargingStation = await ChargingStationStorage.getChargingStation(
+ tenant, transaction.chargeBoxID);
+ } else {
+ chargingStation = await ChargingStationStorage.getChargingStationByOcpiLocationEvseUid(
+ tenant, session.location.id, evse.uid);
+ }
+ if (!chargingStation) {
+ throw new AppError({
+ module: MODULE_NAME, method: 'updateTransaction', action,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `Charging Station with EVSE ID '${evse.uid}' and Location ID '${session.location.id}' does not exist`,
+ detailedMessages: { session },
+ ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR
+ });
+ }
// Create Transaction
if (!transaction) {
- const user = await UserStorage.getUser(tenant, session.auth_id);
+ // Get the Tag
+ const tag = await TagStorage.getTag(tenant, session.auth_id, { withUser: true });
+ if (!tag) {
+ throw new AppError({
+ module: MODULE_NAME, method: 'updateTransaction', action,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `No Tag has been found with ID '${session.auth_id}'`,
+ detailedMessages: { session },
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
+ });
+ }
+ // Get User
+ const user = tag.user;
if (!user) {
throw new AppError({
- module: MODULE_NAME, method: 'updateTransaction',
+ module: MODULE_NAME, method: 'updateTransaction', action,
errorCode: HTTPError.GENERAL_ERROR,
- message: `No User found with ID '${session.auth_id}'`,
+ message: `No User has been found with Tag ID '${session.auth_id}'`,
detailedMessages: { session },
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const evse = session.location.evses[0];
- const chargingStation = await ChargingStationStorage.getChargingStationByOcpiLocationEvseUid(
- tenant, session.location.id, evse.uid);
- if (!chargingStation) {
+ // Check Auth ID
+ if (!user.authorizationID) {
throw new AppError({
- module: MODULE_NAME, method: 'updateTransaction',
+ module: MODULE_NAME, method: 'updateTransaction', action,
errorCode: HTTPError.GENERAL_ERROR,
- message: `Charging Station with EVSE ID '${evse.uid}' and Location ID '${session.location.id}' does not exist`,
+ actionOnUser: user,
+ message: `User has no Authorization ID, expected '${session.authorization_id}'`,
detailedMessages: { session },
- ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
+ if (user.authorizationID !== session.authorization_id) {
+ throw new AppError({
+ module: MODULE_NAME, method: 'updateTransaction', action,
+ errorCode: HTTPError.GENERAL_ERROR,
+ actionOnUser: user,
+ message: `Wrong Authorization ID, expected '${user.authorizationID}', got '${session.authorization_id}'`,
+ detailedMessages: { session },
+ ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
+ });
+ }
+ // Get the connector ID
let connectorId = 1;
if (evse.connectors && evse.connectors.length === 1) {
const evseConnectorId = evse.connectors[0].id;
- for (const connector of chargingStation.connectors) {
- if (evseConnectorId === connector.id) {
- connectorId = connector.connectorId;
+ for (const chargingStationConnector of chargingStation.connectors) {
+ if (evseConnectorId === chargingStationConnector.id) {
+ connectorId = chargingStationConnector.connectorId;
}
}
}
+ // Create the Transaction
transaction = {
issuer: false,
- userID: user.id,
- tagID: session.auth_id,
+ userID: tag.user.id,
+ tagID: tag.id,
timestamp: session.start_datetime,
chargeBoxID: chargingStation.id,
+ companyID: chargingStation.companyID,
+ siteID: chargingStation.siteID,
+ siteAreaID: chargingStation.siteAreaID,
timezone: Utils.getTimezone(chargingStation.coordinates),
connectorId: connectorId,
- meterStart: 0,
- stateOfCharge: 0,
- currentStateOfCharge: 0,
- currentTotalInactivitySecs: 0,
pricingSource: PricingSource.OCPI,
currentInactivityStatus: InactivityStatus.INFO,
- currentInstantWatts: 0,
- currentConsumptionWh: 0,
lastConsumption: {
value: 0,
timestamp: session.start_datetime
},
- signedData: '',
} as Transaction;
}
+ // Set the connector
+ const connector = Utils.getConnectorFromID(chargingStation, transaction.connectorId);
+ if (connector) {
+ connector.status = OCPIUtils.convertOCPISessionStatus2ConnectorStatus(session.status);
+ }
+ // Create the last consumption (first time)
if (!transaction.lastConsumption) {
transaction.lastConsumption = {
value: transaction.meterStart,
timestamp: transaction.timestamp
};
}
+ // Session in the past
if (moment(session.last_updated).isBefore(transaction.lastConsumption.timestamp)) {
await Logging.logDebug({
...LoggingHelper.getTransactionProperties(transaction),
tenantID: tenant.id,
- action: ServerAction.OCPI_PUSH_SESSION,
- module: MODULE_NAME, method: 'updateTransaction',
+ module: MODULE_NAME, method: 'updateTransaction', action,
message: `Ignore session update session.last_updated < transaction.currentTimestamp for transaction ${transaction.id}`,
detailedMessages: { session }
});
@@ -418,12 +461,9 @@ export default class OCPIUtilsService {
}
// Create Consumption
if (session.kwh > 0) {
- await OCPIUtilsService.computeAndSaveConsumption(tenant, transaction, session);
+ await OCPIUtilsService.computeAndSaveConsumption(tenant, chargingStation, transaction, session);
}
- if (!transaction.ocpiData) {
- transaction.ocpiData = {};
- }
- transaction.ocpiData.session = session;
+ transaction.ocpiData = { session };
transaction.currentTimestamp = session.last_updated;
transaction.price = session.total_cost;
transaction.priceUnit = session.currency;
@@ -453,7 +493,7 @@ export default class OCPIUtilsService {
};
}
await TransactionStorage.saveTransaction(tenant, transaction);
- await this.updateConnector(tenant, transaction);
+ await OCPPUtils.updateChargingStationConnectorRuntimeDataWithTransaction(tenant, chargingStation, transaction, true);
}
public static async processCdr(tenant: Tenant, cdr: OCPICdr): Promise<void> {
@@ -466,7 +506,8 @@ export default class OCPIUtilsService {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
- const transaction: Transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, cdr.id);
+ // Get Transaction
+ const transaction = await TransactionStorage.getOCPITransactionBySessionID(tenant, cdr.id);
if (!transaction) {
throw new AppError({
module: MODULE_NAME, method: 'processCdr',
@@ -476,6 +517,18 @@ export default class OCPIUtilsService {
ocpiError: OCPIStatusCode.CODE_2001_INVALID_PARAMETER_ERROR
});
}
+ // Get Charging Station
+ const chargingStation = await ChargingStationStorage.getChargingStation(tenant, transaction.chargeBoxID);
+ if (!chargingStation) {
+ throw new AppError({
+ module: MODULE_NAME, method: 'processCdr',
+ chargingStationID: transaction.chargeBoxID,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Charging Station does not exist',
+ detailedMessages: { transaction, cdr },
+ ocpiError: OCPIStatusCode.CODE_2003_UNKNOWN_LOCATION_ERROR
+ });
+ }
if (!cdr.total_cost) {
cdr.total_cost = 0;
}
@@ -514,7 +567,7 @@ export default class OCPIUtilsService {
}
transaction.ocpiData.cdr = cdr;
await TransactionStorage.saveTransaction(tenant, transaction);
- await this.updateConnector(tenant, transaction);
+ await OCPPUtils.updateChargingStationConnectorRuntimeDataWithTransaction(tenant, chargingStation, transaction, true);
}
public static async updateToken(tenant: Tenant, token: OCPIToken, tag: Tag, emspUser: User): Promise<void> {
@@ -895,39 +948,47 @@ export default class OCPIUtilsService {
}
}
- private static async computeAndSaveConsumption(tenant: Tenant, transaction: Transaction, session: OCPISession): Promise<void> {
+ private static async computeAndSaveConsumption(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction, session: OCPISession): Promise<void> {
const consumptionWh = Utils.createDecimal(session.kwh).mul(1000).minus(Utils.convertToFloat(transaction.lastConsumption.value)).toNumber();
- const duration = Utils.createDecimal(moment(session.last_updated).diff(transaction.lastConsumption.timestamp, 'milliseconds')).div(1000).toNumber();
- if (consumptionWh > 0 || duration > 0) {
- const sampleMultiplier = duration > 0 ? Utils.createDecimal(3600).div(duration).toNumber() : 0;
+ const durationSecs = Utils.createDecimal(moment(session.last_updated).diff(transaction.lastConsumption.timestamp, 'milliseconds')).div(1000).toNumber();
+ if (consumptionWh > 0 || durationSecs > 0) {
+ // Update Transaction
+ const sampleMultiplier = durationSecs > 0 ? Utils.createDecimal(3600).div(durationSecs).toNumber() : 0;
const currentInstantWatts = consumptionWh > 0 ? Utils.createDecimal(consumptionWh).mul(sampleMultiplier).toNumber() : 0;
const amount = Utils.createDecimal(session.total_cost).minus(transaction.price).toNumber();
transaction.currentInstantWatts = currentInstantWatts;
transaction.currentConsumptionWh = consumptionWh > 0 ? consumptionWh : 0;
transaction.currentTotalConsumptionWh = Utils.createDecimal(transaction.currentTotalConsumptionWh).plus(transaction.currentConsumptionWh).toNumber();
if (consumptionWh <= 0) {
- transaction.currentTotalInactivitySecs = Utils.createDecimal(transaction.currentTotalInactivitySecs).plus(duration).toNumber();
+ transaction.currentTotalInactivitySecs = Utils.createDecimal(transaction.currentTotalInactivitySecs).plus(durationSecs).toNumber();
transaction.currentInactivityStatus = Utils.getInactivityStatusLevel(
transaction.chargeBox, transaction.connectorId, transaction.currentTotalInactivitySecs);
}
+ // Create Consumption
const consumption: Consumption = {
transactionId: transaction.id,
connectorId: transaction.connectorId,
chargeBoxID: transaction.chargeBoxID,
+ siteAreaID: transaction.siteAreaID,
+ siteID: transaction.siteID,
userID: transaction.userID,
+ pricingSource: transaction.pricingSource,
startedAt: new Date(transaction.lastConsumption.timestamp),
endedAt: new Date(session.last_updated),
consumptionWh: transaction.currentConsumptionWh,
+ consumptionAmps: Utils.convertWattToAmp(chargingStation, null, transaction.connectorId, transaction.currentConsumptionWh),
instantWatts: Math.floor(transaction.currentInstantWatts),
- instantAmps: Math.floor(transaction.currentInstantAmps) ?? Math.floor(transaction.currentInstantWatts / Voltage.VOLTAGE_230),
+ instantAmps: Utils.convertWattToAmp(chargingStation, null, transaction.connectorId, transaction.currentInstantWatts),
cumulatedConsumptionWh: transaction.currentTotalConsumptionWh,
- cumulatedConsumptionAmps: Math.floor(transaction.currentTotalConsumptionWh / Voltage.VOLTAGE_230),
+ cumulatedConsumptionAmps: Utils.convertWattToAmp(
+ chargingStation, null, transaction.connectorId, transaction.currentTotalConsumptionWh),
totalInactivitySecs: transaction.currentTotalInactivitySecs,
totalDurationSecs: transaction.stop ?
moment.duration(moment(transaction.stop.timestamp).diff(moment(transaction.timestamp))).asSeconds() :
moment.duration(moment(transaction.lastConsumption.timestamp).diff(moment(transaction.timestamp))).asSeconds(),
stateOfCharge: transaction.currentStateOfCharge,
amount: amount,
+ roundedAmount: Utils.truncTo(amount, 2),
currencyCode: session.currency,
cumulatedAmount: session.total_cost
} as Consumption;
@@ -972,36 +1033,4 @@ export default class OCPIUtilsService {
}
return true;
}
-
- private static async updateConnector(tenant: Tenant, transaction: Transaction): Promise<void> {
- const chargingStation = await ChargingStationStorage.getChargingStation(tenant, transaction.chargeBoxID);
- if (chargingStation && chargingStation.connectors) {
- for (const connector of chargingStation.connectors) {
- if (connector.connectorId === transaction.connectorId && connector.currentTransactionID === 0 || connector.currentTransactionID === transaction.id) {
- if (!transaction.stop) {
- connector.status = transaction.status;
- connector.currentTransactionID = transaction.id;
- connector.currentInactivityStatus = transaction.currentInactivityStatus;
- connector.currentTagID = transaction.tagID;
- connector.currentStateOfCharge = transaction.currentStateOfCharge;
- connector.currentInstantWatts = transaction.currentInstantWatts;
- connector.currentTotalConsumptionWh = transaction.currentTotalConsumptionWh;
- connector.currentTransactionDate = transaction.currentTimestamp;
- connector.currentTotalInactivitySecs = transaction.currentTotalInactivitySecs;
- } else {
- connector.status = ChargePointStatus.AVAILABLE;
- connector.currentTransactionID = 0;
- connector.currentTransactionDate = null;
- connector.currentTagID = null;
- connector.currentTotalConsumptionWh = 0;
- connector.currentStateOfCharge = 0;
- connector.currentTotalInactivitySecs = 0;
- connector.currentInstantWatts = 0;
- connector.currentInactivityStatus = null;
- }
- await ChargingStationStorage.saveChargingStationConnectors(tenant, chargingStation.id, chargingStation.connectors);
- }
- }
- }
- }
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPSessionsEndpoint.ts
@@ -95,7 +95,7 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
detailedMessages: { sessionID, session }
});
}
- await OCPIUtilsService.updateTransaction(tenant, session);
+ await OCPIUtilsService.updateTransaction(tenant, session, ServerAction.OCPI_PUT_SESSION);
return OCPIUtils.success({});
}
@@ -129,7 +129,7 @@ export default class EMSPSessionsEndpoint extends AbstractEndpoint {
// Merge
_.merge(transaction.ocpiData.session, req.body);
// Update
- await OCPIUtilsService.updateTransaction(tenant, transaction.ocpiData.session, transaction);
+ await OCPIUtilsService.updateTransaction(tenant, transaction.ocpiData.session, ServerAction.OCPI_PATCH_SESSION, transaction);
return OCPIUtils.success({});
}
}

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
@@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from 'express';
import AppError from '../../../../../exception/AppError';
import ChargingStationStorage from '../../../../../storage/mongodb/ChargingStationStorage';
import TagStorage from '../../../../../storage/mongodb/TagStorage';
+import UserStorage from '../../../../../storage/mongodb/UserStorage';
import { HTTPError } from '../../../../../types/HTTPError';
import { OCPIAllowed } from '../../../../../types/ocpi/OCPIAuthorizationInfo';
import OCPIEndpoint from '../../../../../types/ocpi/OCPIEndpoint';
@@ -134,7 +135,8 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
});
}
// Check User
- if (!tag.user) {
+ const user = tag.user;
+ if (!user) {
throw new AppError({
action: ServerAction.OCPI_AUTHORIZE_TOKEN,
module: MODULE_NAME, method: 'authorizeRequest',
@@ -144,15 +146,19 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
detailedMessages: { tokenID, location, tag }
});
}
- if (tag.user.status !== UserStatus.ACTIVE) {
+ if (user.status !== UserStatus.ACTIVE) {
return OCPIUtils.success({
allowed: tag.user.status === UserStatus.BLOCKED ? OCPIAllowed.BLOCKED : OCPIAllowed.NOT_ALLOWED,
authorization_id: Utils.generateUUID(), location
});
}
+ // Generate and Save Auth ID
+ user.authorizationID = Utils.generateUUID();
+ await UserStorage.saveUser(tenant, user);
return OCPIUtils.success({
allowed: OCPIAllowed.ALLOWED,
- authorization_id: Utils.generateUUID(), location
+ authorization_id: user.authorizationID,
+ location
});
}
}

MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -233,11 +233,11 @@ export default class OCPPService {
// Save Transaction
await TransactionStorage.saveTransaction(tenant, transaction);
// Update Charging Station
- await this.updateChargingStationWithTransaction(tenant, chargingStation, transaction);
- // Handle End Of charge
- await this.checkNotificationEndOfCharge(tenant, chargingStation, transaction);
+ await OCPPUtils.updateChargingStationConnectorRuntimeDataWithTransaction(tenant, chargingStation, transaction);
// Save Charging Station
await ChargingStationStorage.saveChargingStation(tenant, chargingStation);
+ // Handle End Of charge
+ await this.checkNotificationEndOfCharge(tenant, chargingStation, transaction);
// First Meter Value -> Trigger Smart Charging to adjust the limit
if (transaction.numberOfMeterValues === 1 && transaction.phasesUsed) {
// Yes: Trigger Smart Charging
@@ -388,7 +388,7 @@ export default class OCPPService {
// Save it
await TransactionStorage.saveTransaction(tenant, newTransaction);
// Clean up
- await this.updateChargingStationConnectorWithTransaction(tenant, newTransaction, chargingStation, user);
+ await OCPPUtils.updateChargingStationConnectorRuntimeDataWithTransaction(tenant, chargingStation, newTransaction);
// Save
await ChargingStationStorage.saveChargingStation(tenant, chargingStation);
// Notify
@@ -1189,38 +1189,6 @@ export default class OCPPService {
}
}
- private async updateChargingStationWithTransaction(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
- // Get the connector
- const foundConnector: Connector = Utils.getConnectorFromID(chargingStation, transaction.connectorId);
- // Active transaction?
- if (!transaction.stop && foundConnector) {
- // Set consumption
- foundConnector.currentInstantWatts = transaction.currentInstantWatts;
- foundConnector.currentTotalConsumptionWh = transaction.currentTotalConsumptionWh;
- foundConnector.currentTotalInactivitySecs = transaction.currentTotalInactivitySecs;
- foundConnector.currentInactivityStatus = Utils.getInactivityStatusLevel(
- transaction.chargeBox, transaction.connectorId, transaction.currentTotalInactivitySecs);
- foundConnector.currentStateOfCharge = transaction.currentStateOfCharge;
- foundConnector.currentTagID = transaction.tagID;
- // Set Transaction ID
- foundConnector.currentTransactionID = transaction.id;
- foundConnector.currentUserID = transaction.userID;
- const instantPower = Utils.truncTo(Utils.createDecimal(foundConnector.currentInstantWatts).div(1000).toNumber(), 3);
- const totalConsumption = Utils.truncTo(Utils.createDecimal(foundConnector.currentTotalConsumptionWh).div(1000).toNumber(), 3);
- await Logging.logInfo({
- ...LoggingHelper.getChargingStationProperties(chargingStation),
- tenantID: tenant.id,
- module: MODULE_NAME, method: 'updateChargingStationWithTransaction',
- action: ServerAction.CONSUMPTION,
- user: transaction.userID,
- message: `${Utils.buildConnectorInfo(foundConnector.connectorId, foundConnector.currentTransactionID)} Instant: ${instantPower} kW, Total: ${totalConsumption} kW.h${foundConnector.currentStateOfCharge ? ', SoC: ' + foundConnector.currentStateOfCharge.toString() + ' %' : ''}`
- });
- // Cleanup connector transaction data
- } else if (foundConnector) {
- OCPPUtils.clearChargingStationConnectorRuntimeData(chargingStation, foundConnector.connectorId);
- }
- }
-
private notifyEndOfCharge(tenant: Tenant, chargingStation: ChargingStation, transaction: Transaction) {
if (this.chargingStationConfig.notifEndOfChargeEnabled && transaction.user) {
// Get the i18n lib
@@ -1641,29 +1609,6 @@ export default class OCPPService {
}
}
- private async updateChargingStationConnectorWithTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User): Promise<void> {
- const foundConnector = Utils.getConnectorFromID(chargingStation, transaction.connectorId);
- if (foundConnector) {
- foundConnector.currentInstantWatts = 0;
- foundConnector.currentTotalConsumptionWh = 0;
- foundConnector.currentTotalInactivitySecs = 0;
- foundConnector.currentInactivityStatus = InactivityStatus.INFO;
- foundConnector.currentStateOfCharge = 0;
- foundConnector.currentTransactionID = transaction.id;
- foundConnector.currentTransactionDate = transaction.timestamp;
- foundConnector.currentTagID = transaction.tagID;
- foundConnector.currentUserID = transaction.userID;
- } else {
- await Logging.logWarning({
- ...LoggingHelper.getChargingStationProperties(chargingStation),
- tenantID: tenant.id,
- module: MODULE_NAME, method: 'clearChargingStationConnectorRuntimeData',
- action: ServerAction.OCPP_START_TRANSACTION, user: user,
- message: `${Utils.buildConnectorInfo(transaction.connectorId, transaction.id)} Connector does not exist`
- });
- }
- }
-
private async processTransactionCar(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, consumption: Consumption, user: User,
action: TransactionAction): Promise<void> {
let soc = null;

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
@@ -1678,19 +1678,56 @@ export default class OCPPUtils {
return result;
}
+ public static async updateChargingStationConnectorRuntimeDataWithTransaction(tenant: Tenant, chargingStation: ChargingStation,
+ transaction: Transaction, saveConnector = false): Promise<void> {
+ const connector = Utils.getConnectorFromID(chargingStation, transaction.connectorId);
+ if (connector) {
+ if (!transaction.stop) {
+ // Set Transaction data on Connector
+ connector.currentInstantWatts = transaction.currentInstantWatts;
+ connector.currentTotalConsumptionWh = transaction.currentTotalConsumptionWh;
+ connector.currentTotalInactivitySecs = transaction.currentTotalInactivitySecs;
+ connector.currentInactivityStatus = Utils.getInactivityStatusLevel(
+ transaction.chargeBox, transaction.connectorId, transaction.currentTotalInactivitySecs);
+ connector.currentStateOfCharge = transaction.currentStateOfCharge;
+ connector.currentTransactionDate = transaction.timestamp;
+ connector.currentTagID = transaction.tagID;
+ connector.currentTransactionID = transaction.id;
+ connector.currentUserID = transaction.userID;
+ // Log
+ const instantPower = Utils.truncTo(Utils.createDecimal(connector.currentInstantWatts).div(1000).toNumber(), 3);
+ const totalConsumption = Utils.truncTo(Utils.createDecimal(connector.currentTotalConsumptionWh).div(1000).toNumber(), 3);
+ await Logging.logInfo({
+ ...LoggingHelper.getChargingStationProperties(chargingStation),
+ tenantID: tenant.id,
+ module: MODULE_NAME, method: 'updateChargingStationConnectorRuntimeDataWithTransaction',
+ action: ServerAction.CONSUMPTION,
+ user: transaction.userID,
+ message: `${Utils.buildConnectorInfo(connector.connectorId, connector.currentTransactionID)} Power: ${instantPower} kW, Energy: ${totalConsumption} kW.h${connector.currentStateOfCharge ? ', SoC: ' + connector.currentStateOfCharge.toString() + ' %' : ''}`
+ });
+ // Clear Connector
+ } else {
+ OCPPUtils.clearChargingStationConnectorRuntimeData(chargingStation, connector.connectorId);
+ }
+ if (saveConnector) {
+ await ChargingStationStorage.saveChargingStationConnectors(tenant, chargingStation.id, chargingStation.connectors);
+ }
+ }
+ }
+
public static clearChargingStationConnectorRuntimeData(chargingStation: ChargingStation, connectorID: number): void {
// Cleanup connector transaction data
- const foundConnector = Utils.getConnectorFromID(chargingStation, connectorID);
- if (foundConnector) {
- foundConnector.currentInstantWatts = 0;
- foundConnector.currentTotalConsumptionWh = 0;
- foundConnector.currentTotalInactivitySecs = 0;
- foundConnector.currentInactivityStatus = InactivityStatus.INFO;
- foundConnector.currentStateOfCharge = 0;
- foundConnector.currentTransactionID = 0;
- foundConnector.currentTransactionDate = null;
- foundConnector.currentTagID = null;
- foundConnector.currentUserID = null;
+ const connector = Utils.getConnectorFromID(chargingStation, connectorID);
+ if (connector) {
+ connector.currentInstantWatts = 0;
+ connector.currentTotalConsumptionWh = 0;
+ connector.currentTotalInactivitySecs = 0;
+ connector.currentInactivityStatus = InactivityStatus.INFO;
+ connector.currentStateOfCharge = 0;
+ connector.currentTransactionID = 0;
+ connector.currentTransactionDate = null;
+ connector.currentTagID = null;
+ connector.currentUserID = null;
}
}

MODIFY
src/storage/mongodb/TransactionStorage.ts
src/storage/mongodb/TransactionStorage.ts
@@ -69,7 +69,7 @@ export default class TransactionStorage {
priceUnit: transactionToSave.priceUnit,
pricingSource: transactionToSave.pricingSource,
pricingModel: transactionToSave.pricingModel,
- stateOfCharge: transactionToSave.stateOfCharge,
+ stateOfCharge: Utils.convertToInt(transactionToSave.stateOfCharge),
timezone: transactionToSave.timezone,
signedData: transactionToSave.signedData,
numberOfMeterValues: Utils.convertToInt(transactionToSave.numberOfMeterValues),

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -231,6 +231,7 @@ export default class UserStorage {
costCenter: userToSave.costCenter,
importedData: userToSave.importedData,
notificationsActive: userToSave.notificationsActive,
+ authorizationID: userToSave.authorizationID,
notifications: {
sendSessionStarted: userToSave.notifications ? Utils.convertToBoolean(userToSave.notifications.sendSessionStarted) : false,
sendOptimalChargeReached: userToSave.notifications ? Utils.convertToBoolean(userToSave.notifications.sendOptimalChargeReached) : false,

MODIFY
src/types/Transaction.ts
src/types/Transaction.ts
@@ -98,7 +98,6 @@ export default interface Transaction extends AbstractCurrentConsumption {
currentCumulatedPrice?: number;
currentCumulatedRoundedPrice?: number; // added to address rounding issues on multiple pricing dimension
currentSignedData?: string;
- status?: ChargePointStatus;
numberOfMeterValues: number;
uniqueId?: string;
values?: Consumption[];

Handle OCPI Auth ID with User
Check Auth ID in OCPI Session (first call)
Add missing Consumption props
Put in common connector runtime data changes