PushEVSEData in Batches
MODIFY
src/client/oicp/CpoOICPClient.ts
src/client/oicp/CpoOICPClient.ts
@@ -16,6 +16,7 @@ import NotificationHandler from '../../notification/NotificationHandler';
import OCPPStorage from '../../storage/mongodb/OCPPStorage';
import { OICPAcknowledgment } from '../../types/oicp/OICPAcknowledgment';
import { OICPAuthorizationStatus } from '../../types/oicp/OICPAuthentication';
+import { OICPBatchSize } from '../../types/oicp/OICPGeneral';
import { OICPChargeDetailRecord } from '../../types/oicp/OICPChargeDetailRecord';
import OICPClient from './OICPClient';
import OICPEndpoint from '../../types/oicp/OICPEndpoint';
@@ -38,6 +39,7 @@ const MODULE_NAME = 'CpoOICPClient';
export default class CpoOICPClient extends OICPClient {
+
constructor(tenant: Tenant, settings: OicpSetting, oicpEndpoint: OICPEndpoint) {
super(tenant, settings, oicpEndpoint, OICPRole.CPO);
if (oicpEndpoint.role !== OICPRole.CPO) {
@@ -229,15 +231,17 @@ export default class CpoOICPClient extends OICPClient {
};
// Get timestamp before starting process - to be saved in DB at the end of the process
const startDate = new Date();
- // Get all EVSEs from tenant
- const evses = await OICPMapping.getAllEvses(this.tenant, 0, 0, options);
+ // Get all charging stations from tenant
+ const chargingStations = await OICPMapping.getAllChargingStations(this.tenant, 0, 0);
+ // Convert (public) charging stations to OICP EVSEs
+ const evses = await OICPMapping.convertChargingStationsToEVSEs(this.tenant, chargingStations, options);
let evsesToProcess: OICPEvseDataRecord[] = [];
let chargeBoxIDsToProcessFromInput = [];
// Check if all EVSEs should be processed - in case of delta send - process only following EVSEs:
// - EVSEs (ChargingStations) in error from previous push
// - EVSEs (ChargingStations) with status notification from latest pushDate
if (processAllEVSEs) {
- evsesToProcess = evses.result;
+ evsesToProcess = evses;
chargeBoxIDsToProcessFromInput = evsesToProcess.map((evse) => evse.ChargingStationID);
} else {
let chargeBoxIDsToProcess = [];
@@ -248,7 +252,7 @@ export default class CpoOICPClient extends OICPClient {
// Remove duplicates
chargeBoxIDsToProcess = _.uniq(chargeBoxIDsToProcess);
// Loop through EVSE
- for (const evse of evses.result) {
+ for (const evse of evses) {
if (evse) {
// Check if Charging Station should be processed
if (!processAllEVSEs && !chargeBoxIDsToProcess.includes(evse.ChargingStationID)) {
@@ -262,18 +266,30 @@ export default class CpoOICPClient extends OICPClient {
}
// Only one post request to Hubject for multiple EVSEs
result.total = evsesToProcess.length;
+ if (evsesToProcess.length > OICPBatchSize.EVSE_DATA) {
+ // Incase of multiple batches:
+ // delete all EVSEs on Hubject by overwriting with empty array
+ // set action type to insert to avoid overwriting each batch with a full load request
+ await this.pushEvseData([], OICPActionType.FULL_LOAD);
+ actionType = OICPActionType.INSERT;
+ }
if (evsesToProcess) {
// Process it if not empty
+ do {
+ // Send EVSEs in batches to avoid maxBodyLength limit of request.
+ const evseBatch = evsesToProcess.splice(0, OICPBatchSize.EVSE_DATA);
+ const evseIDBatch = chargeBoxIDsToProcessFromInput.splice(0, OICPBatchSize.EVSE_DATA);
try {
- await this.pushEvseData(evsesToProcess, actionType);
- result.success = result.total;
+ await this.pushEvseData(evseBatch, actionType);
+ result.success += evseBatch.length;
} catch (error) {
- result.failure = result.total;
- result.objectIDsInFailure.push(...chargeBoxIDsToProcessFromInput);
+ result.failure += evseBatch.length;
+ result.objectIDsInFailure.push(...evseIDBatch);
result.logs.push(
`Failed to update the EVSEs from tenant '${this.tenant.id}': ${String(error.message)}`
);
}
+ } while (!Utils.isEmptyArray(evsesToProcess));
if (result.failure > 0) {
// Send notification to admins
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -346,15 +362,17 @@ export default class CpoOICPClient extends OICPClient {
};
// Get timestamp before starting process - to be saved in DB at the end of the process
const startDate = new Date();
- // Get all EVSE Statuses from tenant
- const evseStatuses = await OICPMapping.getAllEvseStatuses(this.tenant, 0, 0, options);
+ // Get all charging stations from tenant
+ const chargingStations = await OICPMapping.getAllChargingStations(this.tenant, 0, 0);
+ // Convert (public) charging stations to OICP EVSE Statuses
+ const evseStatuses = await OICPMapping.convertChargingStationsToEvseStatuses(this.tenant, chargingStations, options);
let evseStatusesToProcess: OICPEvseStatusRecord[] = [];
let chargeBoxIDsToProcessFromInput = [];
// Check if all EVSE Statuses should be processed - in case of delta send - process only following EVSEs:
// - EVSEs (ChargingStations) in error from previous push
// - EVSEs (ChargingStations) with status notification from latest pushDate
if (processAllEVSEs) {
- evseStatusesToProcess = evseStatuses.result;
+ evseStatusesToProcess = evseStatuses;
chargeBoxIDsToProcessFromInput = evseStatusesToProcess.map((evseStatus) => evseStatus.ChargingStationID);
} else {
let chargeBoxIDsToProcess = [];
@@ -365,7 +383,7 @@ export default class CpoOICPClient extends OICPClient {
// Remove duplicates
chargeBoxIDsToProcess = _.uniq(chargeBoxIDsToProcess);
// Loop through EVSE statuses
- for (const evseStatus of evseStatuses.result) {
+ for (const evseStatus of evseStatuses) {
if (evseStatus) {
// Check if Charging Station should be processed
if (!processAllEVSEs && !chargeBoxIDsToProcess.includes(evseStatus.ChargingStationID)) {
@@ -576,7 +594,8 @@ export default class CpoOICPClient extends OICPClient {
requestError = error;
}
if (!pushEvseStatusResponse?.Result || pushEvseStatusResponse?.Result !== true) {
- throw new BackendError({
+ Logging.logError({
+ tenantID: this.tenant.id,
action: ServerAction.OICP_PUSH_EVSE_STATUSES,
message: `'pushEvseStatus' Error: '${pushEvseStatusResponse?.StatusCode?.AdditionalInfo ? pushEvseStatusResponse?.StatusCode?.AdditionalInfo : pushEvseStatusResponse?.StatusCode?.Description}' '${String(requestError?.message)}`,
module: MODULE_NAME, method: 'pushEvseStatus',
@@ -722,7 +741,8 @@ export default class CpoOICPClient extends OICPClient {
requestError = error;
}
if (requestError) {
- throw new BackendError({
+ Logging.logError({
+ tenantID: this.tenant.id,
user: user,
action: ServerAction.OICP_AUTHORIZE_STOP,
message: `'authorizeStop' Error: '${authorizeResponse?.StatusCode?.AdditionalInfo ? authorizeResponse?.StatusCode?.AdditionalInfo : authorizeResponse?.StatusCode?.Description}' '${String(requestError?.message)}'`,
@@ -736,7 +756,8 @@ export default class CpoOICPClient extends OICPClient {
});
}
if (authorizeResponse?.AuthorizationStatus !== OICPAuthorizationStatus.Authorized) {
- throw new BackendError({
+ Logging.logError({
+ tenantID: this.tenant.id,
user: user,
action: ServerAction.OICP_AUTHORIZE_STOP,
module: MODULE_NAME, method: 'authorizeStop',
@@ -808,7 +829,9 @@ export default class CpoOICPClient extends OICPClient {
cdr.SessionEnd = transaction.oicpData.session.end_datetime;
cdr.MeterValueStart = Utils.convertWattHourToKiloWattHour(transaction.meterStart, 3); // Optional
cdr.MeterValueEnd = Utils.convertWattHourToKiloWattHour(transaction.stop.meterStop, 3); // Optional
+ if (!Utils.isEmptyArray(transaction.oicpData.session.meterValueInBetween)) {
cdr.MeterValueInBetween = { meterValues: transaction.oicpData.session.meterValueInBetween.map((wattHour) => Utils.convertWattHourToKiloWattHour(wattHour, 3)) }; // Optional
+ }
cdr.ConsumedEnergy = Utils.convertWattHourToKiloWattHour(transaction.stop.totalConsumptionWh, 3); // In kW.h
cdr.SignedMeteringValues; // Optional
cdr.CalibrationLawVerificationInfo; // Optional
@@ -924,7 +947,7 @@ export default class CpoOICPClient extends OICPClient {
requestError = error;
}
if (!notificationStartResponse?.Result || notificationStartResponse?.Result !== true) {
- Logging.logError({
+ Logging.logWarning({
tenantID: this.tenant.id,
user: transaction.user,
action: ServerAction.OICP_SEND_CHARGING_NOTIFICATION_START,
@@ -1008,7 +1031,7 @@ export default class CpoOICPClient extends OICPClient {
}
transaction.oicpData.session.last_progress_notification = new Date();
if (!notificationProgressResponse?.Result || notificationProgressResponse?.Result !== true) {
- Logging.logError({
+ Logging.logWarning({
tenantID: this.tenant.id,
user: transaction.user,
action: ServerAction.OICP_SEND_CHARGING_NOTIFICATION_PROGRESS,
@@ -1102,7 +1125,7 @@ export default class CpoOICPClient extends OICPClient {
requestError = error;
}
if (!notificationEndResponse?.Result || notificationEndResponse?.Result !== true) {
- Logging.logError({
+ Logging.logWarning({
tenantID: this.tenant.id,
user: transaction.user,
action: ServerAction.OICP_SEND_CHARGING_NOTIFICATION_END,
@@ -1178,7 +1201,7 @@ export default class CpoOICPClient extends OICPClient {
requestError = err;
}
if (!notificationErrorResponse?.Result || notificationErrorResponse?.Result !== true) {
- Logging.logError({
+ Logging.logWarning({
tenantID: this.tenant.id,
user: transaction.user,
action: ServerAction.OICP_SEND_CHARGING_NOTIFICATION_ERROR,

MODIFY
src/server/oicp/oicp-services-impl/oicp-2.3.0/OICPMapping.ts
src/server/oicp/oicp-services-impl/oicp-2.3.0/OICPMapping.ts
@@ -4,10 +4,10 @@ import { OICPAccessibility, OICPAddressIso19773, OICPAuthenticationMode, OICPCal
import Address from '../../../../types/Address';
import BackendError from '../../../../exception/BackendError';
import { ChargePointStatus } from '../../../../types/ocpp/OCPPServer';
+import ChargingStationStorage from '../../../../storage/mongodb/ChargingStationStorage';
import Constants from '../../../../utils/Constants';
import Countries from 'i18n-iso-countries';
import CountryLanguage from 'country-language';
-import { DataResult } from '../../../../types/DataResult';
import RoamingUtils from '../../../../utils/RoamingUtils';
import { ServerAction } from '../../../../types/Server';
import Site from '../../../../types/Site';
@@ -54,26 +54,35 @@ export default class OICPMapping {
}
/**
- * Get evses from SiteArea
+ * Get All Charging Stations from given Tenant
* @param {Tenant} tenant
- * @param {SiteArea} siteArea
- * @param options
- * @return Array of OICP EVSES
*/
- static async getEvsesFromSiteaArea(tenant: Tenant, siteArea: SiteArea, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseDataRecord[]> {
- // Build evses array
+ public static async getAllChargingStations(tenant: Tenant, limit: number, skip: number): Promise<ChargingStation[]> {
+ // Result
+ const chargingStations: ChargingStation[] = [];
+ // Get all sites
+ const sites = await SiteStorage.getSites(tenant.id, { issuer: true, onlyPublicSite: true }, { limit, skip });
+ // Get Charging Stations from Sites
+ for (const site of sites.result) {
+ chargingStations.push(...await OICPMapping.getAllChargingStationsFromSite(tenant, site));
+ }
+ // Return Charging Stations
+ return chargingStations;
+ }
+
+ public static async convertChargingStationsToEVSEs(tenant: Tenant, chargingStations: ChargingStation[], options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseDataRecord[]> {
const evses: OICPEvseDataRecord[] = [];
// Convert charging stations to evse(s)
- for (const chargingStation of siteArea.chargingStations) {
+ for (const chargingStation of chargingStations) {
if (chargingStation.issuer && chargingStation.public) {
if (!Utils.isEmptyArray(chargingStation.chargePoints)) {
for (const chargePoint of chargingStation.chargePoints) {
// OICP does not support multiple connectors in one EVSE object
// It is not possible to flag if connectors of charge points can charge in parallel or not
- evses.push(...OICPMapping.convertChargingStation2MultipleEvses(tenant,siteArea, chargingStation, chargePoint, options));
+ evses.push(...OICPMapping.convertChargingStation2MultipleEvses(tenant, chargingStation.siteArea, chargingStation, chargePoint, options));
}
} else {
- evses.push(...OICPMapping.convertChargingStation2MultipleEvses(tenant, siteArea, chargingStation, null, options));
+ evses.push(...OICPMapping.convertChargingStation2MultipleEvses(tenant, chargingStation.siteArea, chargingStation, null, options));
}
}
}
@@ -81,42 +90,40 @@ export default class OICPMapping {
return evses;
}
- /**
- * Get All OICP Evses from given tenant
- * @param {Tenant} tenant
- */
- public static async getAllEvses(tenant: Tenant, limit: number, skip: number, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<DataResult<OICPEvseDataRecord>> {
- // Result
- const oicpEvsesResult: DataResult<OICPEvseDataRecord> = { count: 0, result: [] };
- // Get all sites
- const sites = await SiteStorage.getSites(tenant.id, { issuer: true, onlyPublicSite: true }, { limit, skip });
- // Convert Sites to Evses
- for (const site of sites.result) {
- oicpEvsesResult.result.push(...await OICPMapping.getEvsesFromSite(tenant, site, options));
+ public static async convertChargingStationsToEvseStatuses(tenant: Tenant, chargingStations: ChargingStation[], options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseStatusRecord[]> {
+ const evseStatuses: OICPEvseStatusRecord[] = [];
+ // Convert charging stations to evse status(es)
+ for (const chargingStation of chargingStations) {
+ if (chargingStation.issuer && chargingStation.public) {
+ if (!Utils.isEmptyArray(chargingStation.chargePoints)) {
+ for (const chargePoint of chargingStation.chargePoints) {
+ // OICP does not support multiple connectors in one EVSE object
+ // It is not possible to flag if connectors of charge points can charge in parallel or not
+ evseStatuses.push(...OICPMapping.convertChargingStation2MultipleEvseStatuses(tenant, chargingStation, chargePoint, options));
}
- // Set count
- oicpEvsesResult.count = oicpEvsesResult.result.length;
- // Return EVSEs
- return oicpEvsesResult;
+ } else {
+ evseStatuses.push(...OICPMapping.convertChargingStation2MultipleEvseStatuses(tenant, chargingStation, null, options));
+ }
+ }
+ }
+ // Return evse status(es)
+ return evseStatuses;
}
/**
- * Get All OICP Evse Statuses from given tenant
+ * Get evses from SiteArea
* @param {Tenant} tenant
+ * @param {SiteArea} siteArea
+ * @param options
+ * @return Array of charging stations
*/
- public static async getAllEvseStatuses(tenant: Tenant, limit: number, skip: number, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<DataResult<OICPEvseStatusRecord>> {
- // Result
- const oicpEvsesResult: DataResult<OICPEvseStatusRecord> = { count: 0, result: [] };
- // Get all sites
- const sites = await SiteStorage.getSites(tenant.id, { issuer: true, onlyPublicSite: true }, { limit, skip });
- // Convert Sites to Evses
- for (const site of sites.result) {
- oicpEvsesResult.result.push(...await OICPMapping.getEvseStatusesFromSite(tenant, site, options));
- }
- // Set count
- oicpEvsesResult.count = oicpEvsesResult.result.length;
- // Return EVSE Stauses
- return oicpEvsesResult;
+ private static async getAllChargingStationsFromSiteArea(tenant: Tenant, siteArea: SiteArea): Promise<ChargingStation[]> {
+ // Get Charging Stations
+ // TODO: Add parameter to select only public charging stations
+ const chargingStations = await ChargingStationStorage.getChargingStations(tenant.id,
+ { siteAreaIDs: [siteArea.id], includeDeleted: false }, Constants.DB_PARAMS_MAX_LIMIT);
+ // Return charging Stations
+ return chargingStations.result;
}
/**
@@ -293,73 +300,19 @@ export default class OICPMapping {
}
/**
- * Get EVSE Statuses from SiteArea
- * @param {Tenant} tenant
- * @param {SiteArea} siteArea
- * @param options
- * @return Array of OICP EVSE Statuses
- */
- private static async getEvseStatusesFromSiteaArea(tenant: Tenant, siteArea: SiteArea, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseStatusRecord[]> {
- // Build evses array
- const evseStatuses: OICPEvseStatusRecord[] = [];
- // Convert charging stations to evse status(es)
- for (const chargingStation of siteArea.chargingStations) {
- if (chargingStation.issuer && chargingStation.public) {
- if (!Utils.isEmptyArray(chargingStation.chargePoints)) {
- for (const chargePoint of chargingStation.chargePoints) {
- // OICP does not support multiple connectors in one EVSE object
- // It is not possible to flag if connectors of charge points can charge in parallel or not
- evseStatuses.push(...OICPMapping.convertChargingStation2MultipleEvseStatuses(tenant, chargingStation, chargePoint, options));
- }
- } else {
- evseStatuses.push(...OICPMapping.convertChargingStation2MultipleEvseStatuses(tenant, chargingStation, null, options));
- }
- }
- }
- // Return evses
- return evseStatuses;
- }
-
- /**
- * Get evse statuses from Site
- * @param {Tenant} tenant
- * @param {Site} site
- * @param options
- * @return Array of OICP EVSE Statuses
- */
- private static async getEvseStatusesFromSite(tenant: Tenant, site: Site, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseStatusRecord[]> {
- // Build evses array
- const evseStatuses: OICPEvseStatusRecord[] = [];
- const siteAreas = await SiteAreaStorage.getSiteAreas(tenant.id,
- {
- withOnlyChargingStations: true,
- withChargingStations: true,
- siteIDs: [site.id],
- issuer: true
- },
- Constants.DB_PARAMS_MAX_LIMIT);
- for (const siteArea of siteAreas.result) {
- // Get charging station statuses from SiteArea
- evseStatuses.push(...await OICPMapping.getEvseStatusesFromSiteaArea(tenant, siteArea, options));
- }
- // Return evse statuses
- return evseStatuses;
- }
-
- /**
- * Get evses from Site
+ * Get charging stations from Site
* @param {Tenant} tenant
* @param {Site} site
* @param options
- * @return Array of OICP EVSEs
+ * @return Array of charging stations
*/
- private static async getEvsesFromSite(tenant: Tenant, site: Site, options: { countryID: string; partyID: string; addChargeBoxID?: boolean }): Promise<OICPEvseDataRecord[]> {
- // Build evses array
- const evses = [];
+ private static async getAllChargingStationsFromSite(tenant: Tenant, site: Site): Promise<ChargingStation[]> {
+ // Build charging station array
+ const chargingStations: ChargingStation[] = [];
const siteAreas = await SiteAreaStorage.getSiteAreas(tenant.id,
{
- withOnlyChargingStations: true,
- withChargingStations: true,
+ withOnlyChargingStations: false,
+ withChargingStations: false,
withSite: true,
siteIDs: [site.id],
issuer: true
@@ -367,10 +320,10 @@ export default class OICPMapping {
Constants.DB_PARAMS_MAX_LIMIT);
for (const siteArea of siteAreas.result) {
// Get charging stations from SiteArea
- evses.push(...await OICPMapping.getEvsesFromSiteaArea(tenant, siteArea, options));
+ chargingStations.push(...await OICPMapping.getAllChargingStationsFromSiteArea(tenant, siteArea));
}
- // Return evses
- return evses;
+ // Return charging stations
+ return chargingStations;
}
/**

PushEVSEData in Batches