Implemented Scheduler task for assets
MODIFY
src/scheduler/SchedulerManager.ts
src/scheduler/SchedulerManager.ts
+import cron from 'node-cron';
+import { ServerAction } from '../types/Server';
+import Configuration from '../utils/Configuration';
+import Constants from '../utils/Constants';
+import Logging from '../utils/Logging';
+import SchedulerTask from './SchedulerTask';
+import AssetGetConsumptionTask from './tasks/AssetGetConsumptionTask';
import CheckAndComputeSmartChargingTask from './tasks/CheckAndComputeSmartChargingTask';
import CheckOfflineChargingStationsTask from './tasks/CheckOfflineChargingStationsTask';
import CheckPreparingSessionNotStartedTask from './tasks/CheckPreparingSessionNotStartedTask';
import CheckSessionNotStartedAfterAuthorizeTask from './tasks/CheckSessionNotStartedAfterAuthorizeTask';
import CheckUserAccountInactivityTask from './tasks/CheckUserAccountInactivityTask';
-import Configuration from '../utils/Configuration';
-import Constants from '../utils/Constants';
-import Logging from '../utils/Logging';
import LoggingDatabaseTableCleanupTask from './tasks/LoggingDatabaseTableCleanupTask';
import OCPICheckCdrsTask from './tasks/ocpi/OCPICheckCdrsTask';
import OCPICheckLocationsTask from './tasks/ocpi/OCPICheckLocationsTask';
@@ -15,13 +19,10 @@ import OCPIGetLocationsTask from './tasks/ocpi/OCPIGetLocationsTask';
import OCPIGetSessionsTask from './tasks/ocpi/OCPIGetSessionsTask';
import OCPIGetTokensTask from './tasks/ocpi/OCPIGetTokensTask';
import OCPIPatchLocationsTask from './tasks/ocpi/OCPIPatchLocationsTask';
-import SchedulerTask from './SchedulerTask';
-import { ServerAction } from '../types/Server';
import SynchronizeBillingInvoicesTask from './tasks/SynchronizeBillingInvoicesTask';
import SynchronizeBillingUsersTask from './tasks/SynchronizeBillingUsersTask';
import SynchronizeCarsTask from './tasks/SynchronizeCarsTask';
import SynchronizeRefundTransactionsTask from './tasks/SynchronizeRefundTransactionsTask';
-import cron from 'node-cron';
const MODULE_NAME = 'SchedulerManager';
@@ -109,6 +110,8 @@ export default class SchedulerManager {
case 'CheckAndComputeSmartChargingTask':
schedulerTask = new CheckAndComputeSmartChargingTask();
break;
+ case 'AssetGetConsumptionTask':
+ schedulerTask = new AssetGetConsumptionTask();
default:
Logging.logError({

MODIFY
src/scheduler/tasks/AssetGetConsumptionTask.ts
src/scheduler/tasks/AssetGetConsumptionTask.ts
+import AssetFactory from '../../integration/asset/AssetFactory';
+import LockingHelper from '../../locking/LockingHelper';
+import LockingManager from '../../locking/LockingManager';
+import AssetStorage from '../../storage/mongodb/AssetStorage';
+import ConsumptionStorage from '../../storage/mongodb/ConsumptionStorage';
+import Asset from '../../types/Asset';
+import Consumption, { AbstractCurrentConsumption } from '../../types/Consumption';
+import { ServerAction } from '../../types/Server';
import { TaskConfig } from '../../types/TaskConfig';
import Tenant from '../../types/Tenant';
-import SchedulerTask from '../SchedulerTask';
-import Utils from '../../utils/Utils';
import TenantComponents from '../../types/TenantComponents';
import Logging from '../../utils/Logging';
-import { ServerAction } from '../../types/Server';
-import LockingHelper from '../../locking/LockingHelper';
+import Utils from '../../utils/Utils';
+import SchedulerTask from '../SchedulerTask';
const MODULE_NAME = 'AssetGetConsumptionTask';
@@ -24,5 +30,66 @@ export default class AssetGetConsumptionTask extends SchedulerTask {
return;
}
const assetLock = await LockingHelper.createAssetLock(tenant.id);
+ if (assetLock) {
+ try {
+ // Get dynamic assets only
+ const dynamicAssets = await AssetStorage.getAssets(tenant.id,
+ {
+ dynamicOnly: true,
+ withSiteArea: true
+ },
+ {
+ limit: 100,
+ skip: 0,
+ }
+ );
+ for (const asset of dynamicAssets.result) {
+ // Get asset factory
+ const assetImpl = await AssetFactory.getAssetImpl(tenant.id, asset.connectionID);
+ if (assetImpl) {
+ // Retrieve consumption
+ const assetConsumption = await assetImpl.retrieveConsumption(asset);
+ this.assignAssetConsumption(asset, assetConsumption);
+ // Save Asset
+ await AssetStorage.saveAsset(tenant.id, asset);
+ // Create Consumption
+ const consumption: Consumption = {
+ id: '1234',
+ startedAt: asset.lastConsumption.timestamp,
+ assetID: asset.id,
+ cumulatedConsumptionWh: asset.currentConsumptionWh,
+ cumulatedConsumptionAmps: Math.floor(asset.currentConsumptionWh / 230),
+ };
+ Utils.addSiteLimitationToConsumption(tenant.id, asset.siteArea, consumption);
+ // Save
+ ConsumptionStorage.saveConsumption(tenant.id, consumption);
+ }
+ }
+ } catch (error) {
+ // Log error
+ Logging.logActionExceptionMessage(tenant.id, ServerAction.RETRIEVE_ASSET_CONSUMPTION, error);
+ } finally {
+ // Release the lock
+ await LockingManager.release(assetLock);
+ }
+ }
+ }
+
+ private assignAssetConsumption(asset: Asset, consumption: AbstractCurrentConsumption) {
+ // Assign
+ asset.lastConsumption = consumption.lastConsumption;
+ asset.currentConsumptionWh = consumption.currentConsumptionWh;
+ asset.currentInstantAmps = consumption.currentInstantAmps;
+ asset.currentInstantAmpsL1 = consumption.currentInstantAmpsL1;
+ asset.currentInstantAmpsL2 = consumption.currentInstantAmpsL2;
+ asset.currentInstantAmpsL3 = consumption.currentInstantAmpsL3;
+ asset.currentInstantVolts = consumption.currentInstantVolts;
+ asset.currentInstantVoltsL1 = consumption.currentInstantVoltsL1;
+ asset.currentInstantVoltsL2 = consumption.currentInstantVoltsL2;
+ asset.currentInstantVoltsL3 = consumption.currentInstantVoltsL3;
+ asset.currentInstantWatts = consumption.currentInstantWatts;
+ asset.currentInstantWattsL1 = consumption.currentInstantWattsL1;
+ asset.currentInstantWattsL2 = consumption.currentInstantWattsL2;
+ asset.currentInstantWattsL3 = consumption.currentInstantWattsL3;
}
}

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
-import { ActionsResponse, KeyValue } from '../../../types/GlobalType';
-import { ChargingProfile, ChargingProfilePurposeType } from '../../../types/ChargingProfile';
-import ChargingStation, { ChargingStationCapabilities, ChargingStationOcppParameters, ChargingStationTemplate, ConnectorCurrentLimitSource, CurrentType, OcppParameter, SiteAreaLimitSource, TemplateUpdateResult } from '../../../types/ChargingStation';
-import { OCPPChangeConfigurationCommandParam, OCPPChangeConfigurationCommandResult, OCPPChargingProfileStatus, OCPPConfigurationStatus, OCPPGetConfigurationCommandParam, OCPPGetConfigurationCommandResult } from '../../../types/ocpp/OCPPClient';
-import { OCPPMeasurand, OCPPNormalizedMeterValue, OCPPPhase, OCPPReadingContext, OCPPStopTransactionRequestExtended, OCPPUnitOfMeasure } from '../../../types/ocpp/OCPPServer';
-import Transaction, { InactivityStatus, TransactionAction, TransactionStop } from '../../../types/Transaction';
-
+import moment from 'moment';
+import ChargingStationClientFactory from '../../../client/ocpp/ChargingStationClientFactory';
import BackendError from '../../../exception/BackendError';
-import { BillingDataTransactionStop } from '../../../types/Billing';
import BillingFactory from '../../../integration/billing/BillingFactory';
-import ChargingStationClientFactory from '../../../client/ocpp/ChargingStationClientFactory';
-import ChargingStationStorage from '../../../storage/mongodb/ChargingStationStorage';
import ChargingStationVendorFactory from '../../../integration/charging-station-vendor/ChargingStationVendorFactory';
-import Constants from '../../../utils/Constants';
-import Consumption from '../../../types/Consumption';
+import PricingFactory from '../../../integration/pricing/PricingFactory';
+import ChargingStationStorage from '../../../storage/mongodb/ChargingStationStorage';
import ConsumptionStorage from '../../../storage/mongodb/ConsumptionStorage';
-import Logging from '../../../utils/Logging';
import OCPPStorage from '../../../storage/mongodb/OCPPStorage';
+import TransactionStorage from '../../../storage/mongodb/TransactionStorage';
+import { BillingDataTransactionStop } from '../../../types/Billing';
+import { ChargingProfile, ChargingProfilePurposeType } from '../../../types/ChargingProfile';
+import ChargingStation, { ChargingStationCapabilities, ChargingStationOcppParameters, ChargingStationTemplate, ConnectorCurrentLimitSource, CurrentType, OcppParameter, TemplateUpdateResult } from '../../../types/ChargingStation';
+import Consumption from '../../../types/Consumption';
+import { ActionsResponse, KeyValue } from '../../../types/GlobalType';
+import { OCPPChangeConfigurationCommandParam, OCPPChangeConfigurationCommandResult, OCPPChargingProfileStatus, OCPPConfigurationStatus, OCPPGetConfigurationCommandParam, OCPPGetConfigurationCommandResult } from '../../../types/ocpp/OCPPClient';
+import { OCPPMeasurand, OCPPNormalizedMeterValue, OCPPPhase, OCPPReadingContext, OCPPStopTransactionRequestExtended, OCPPUnitOfMeasure } from '../../../types/ocpp/OCPPServer';
import { PricedConsumption } from '../../../types/Pricing';
-import PricingFactory from '../../../integration/pricing/PricingFactory';
-import { PricingSettingsType } from '../../../types/Setting';
import { ServerAction } from '../../../types/Server';
+import { PricingSettingsType } from '../../../types/Setting';
import SiteArea from '../../../types/SiteArea';
-import SiteAreaStorage from '../../../storage/mongodb/SiteAreaStorage';
-import Tenant from '../../../types/Tenant';
-import TenantComponents from '../../../types/TenantComponents';
-import TenantStorage from '../../../storage/mongodb/TenantStorage';
-import TransactionStorage from '../../../storage/mongodb/TransactionStorage';
+import Transaction, { InactivityStatus, TransactionAction, TransactionStop } from '../../../types/Transaction';
import User from '../../../types/User';
import UserToken from '../../../types/UserToken';
+import Constants from '../../../utils/Constants';
+import Logging from '../../../utils/Logging';
import Utils from '../../../utils/Utils';
-import moment from 'moment';
const MODULE_NAME = 'OCPPUtils';
@@ -742,7 +737,7 @@ export default class OCPPUtils {
// Handle current Connector limitation
await OCPPUtils.addConnectorLimitationToConsumption(tenantID, chargingStation, transaction.connectorId, consumption);
// Handle current Site Area limitation
- await OCPPUtils.addSiteLimitationToConsumption(tenantID, chargingStation.siteArea, consumption);
+ await Utils.addSiteLimitationToConsumption(tenantID, chargingStation.siteArea, consumption);
// Keep last one
transaction.lastConsumption = {
value: Utils.convertToFloat(meterValue.value),
@@ -775,36 +770,6 @@ export default class OCPPUtils {
}
}
- public static async addSiteLimitationToConsumption(tenantID: string, siteArea: SiteArea, consumption: Consumption): Promise<void> {
- const tenant: Tenant = await TenantStorage.getTenant(tenantID);
- if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
- // Get limit of the site area
- consumption.limitSiteAreaWatts = 0;
- // Maximum power of the Site Area provided?
- if (siteArea && siteArea.maximumPower) {
- consumption.limitSiteAreaWatts = siteArea.maximumPower;
- consumption.limitSiteAreaAmps = siteArea.maximumPower / siteArea.voltage;
- consumption.limitSiteAreaSource = SiteAreaLimitSource.SITE_AREA;
- } else {
- // Compute it for Charging Stations
- const chargingStationsOfSiteArea = await ChargingStationStorage.getChargingStations(tenantID, { siteAreaIDs: [siteArea.id] }, Constants.DB_PARAMS_MAX_LIMIT);
- for (const chargingStationOfSiteArea of chargingStationsOfSiteArea.result) {
- for (const connector of chargingStationOfSiteArea.connectors) {
- consumption.limitSiteAreaWatts += connector.power;
- }
- }
- consumption.limitSiteAreaAmps = Math.round(consumption.limitSiteAreaWatts / siteArea.voltage);
- consumption.limitSiteAreaSource = SiteAreaLimitSource.CHARGING_STATIONS;
- // Save Site Area max consumption
- if (siteArea) {
- siteArea.maximumPower = consumption.limitSiteAreaWatts;
- await SiteAreaStorage.saveSiteArea(tenantID, siteArea);
- }
- }
- consumption.smartChargingActive = siteArea.smartCharging;
- }
- }
-
public static async getChargingStationTemplate(chargingStation: ChargingStation): Promise<ChargingStationTemplate> {
let foundTemplate: ChargingStationTemplate = null;
// Get the Templates

MODIFY
src/server/rest/service/AssetService.ts
src/server/rest/service/AssetService.ts
@@ -311,7 +311,8 @@ export default class AssetService {
search: filteredRequest.Search,
siteAreaIDs: (filteredRequest.SiteAreaID ? filteredRequest.SiteAreaID.split('|') : null),
withSiteArea: filteredRequest.WithSiteArea,
- withNoSiteArea: filteredRequest.WithNoSiteArea
+ withNoSiteArea: filteredRequest.WithNoSiteArea,
+ dynamicOnly: filteredRequest.DynamicOnly,
},
{ limit: filteredRequest.Limit, skip: filteredRequest.Skip, sort: filteredRequest.Sort, onlyRecordCount: filteredRequest.OnlyRecordCount },
[ 'id', 'name', 'siteAreaID', 'siteArea.id', 'siteArea.name', 'siteArea.siteID', 'assetType', 'coordinates', 'dynamicAsset', 'connectionID', 'meterID', 'currentInstantWatts']

MODIFY
src/server/rest/service/security/AssetSecurity.ts
src/server/rest/service/security/AssetSecurity.ts
@@ -27,6 +27,7 @@ export default class AssetSecurity {
SiteAreaID: sanitize(request.SiteAreaID),
WithSiteArea: !request.WithSiteArea ? false : UtilsSecurity.filterBoolean(request.WithSiteArea),
WithNoSiteArea: !request.WithNoSiteArea ? false : UtilsSecurity.filterBoolean(request.WithNoSiteArea),
+ DynamicOnly: !request.DynamicOnly ? false : UtilsSecurity.filterBoolean(request.DynamicOnly),
ErrorType: sanitize(request.ErrorType)
} as HttpAssetsRequest;
UtilsSecurity.filterSkipAndLimit(request, filteredRequest);

MODIFY
src/storage/mongodb/AssetStorage.ts
src/storage/mongodb/AssetStorage.ts
@@ -112,7 +112,7 @@ export default class AssetStorage {
public static async getAssets(tenantID: string,
params: { search?: string; assetIDs?: string[]; siteAreaIDs?: string[]; withSiteArea?: boolean;
- withNoSiteArea?: boolean; } = {},
+ withNoSiteArea?: boolean; dynamicOnly?: boolean } = {},
dbParams?: DbParams, projectFields?: string[]): Promise<DataResult<Asset>> {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getAssets');
@@ -139,6 +139,10 @@ export default class AssetStorage {
$in: params.siteAreaIDs.map((id) => Utils.convertToObjectID(id))
};
}
+ // Dynamic Asset
+ if (params.dynamicOnly) {
+ filters.dynamicAsset = true;
+ }
// Create Aggregation
const aggregation = [];
// Limit on Asset for Basic Users

MODIFY
src/types/Consumption.ts
src/types/Consumption.ts
@@ -24,45 +24,46 @@ export interface AbstractCurrentConsumption {
}
export interface AbstractConsumption {
- instantWatts: number;
- instantWattsL1: number;
- instantWattsL2: number;
- instantWattsL3: number;
- instantWattsDC: number;
- instantAmps: number;
- instantAmpsL1: number;
- instantAmpsL2: number;
- instantAmpsL3: number;
- instantAmpsDC: number;
- instantVolts: number;
- instantVoltsL1: number;
- instantVoltsL2: number;
- instantVoltsL3: number;
- instantVoltsDC: number;
- consumptionWh: number;
- consumptionAmps: number;
+ instantWatts?: number;
+ instantWattsL1?: number;
+ instantWattsL2?: number;
+ instantWattsL3?: number;
+ instantWattsDC?: number;
+ instantAmps?: number;
+ instantAmpsL1?: number;
+ instantAmpsL2?: number;
+ instantAmpsL3?: number;
+ instantAmpsDC?: number;
+ instantVolts?: number;
+ instantVoltsL1?: number;
+ instantVoltsL2?: number;
+ instantVoltsL3?: number;
+ instantVoltsDC?: number;
+ consumptionWh?: number;
+ consumptionAmps?: number;
}
export default interface Consumption extends AbstractConsumption {
id: string;
startedAt: Date;
- endedAt: Date;
- transactionId: number;
- chargeBoxID: string;
- connectorId: number;
- siteAreaID: string;
- siteID: string;
- cumulatedAmount: number;
+ endedAt?: Date;
+ transactionId?: number;
+ chargeBoxID?: string;
+ connectorId?: number;
+ siteAreaID?: string;
+ siteID?: string;
+ assetID?: string;
cumulatedConsumptionWh: number;
cumulatedConsumptionAmps: number;
- pricingSource: string;
- amount: number;
- roundedAmount: number;
- currencyCode: string;
- totalInactivitySecs: number;
- totalDurationSecs: number;
- stateOfCharge: number;
- userID: string;
+ pricingSource?: string;
+ amount?: number;
+ roundedAmount?: number;
+ cumulatedAmount?: number;
+ currencyCode?: string;
+ totalInactivitySecs?: number;
+ totalDurationSecs?: number;
+ stateOfCharge?: number;
+ userID?: string;
toPrice?: boolean;
limitAmps?: number;
limitWatts?: number;

MODIFY
src/types/requests/HttpAssetRequest.ts
src/types/requests/HttpAssetRequest.ts
@@ -10,5 +10,6 @@ export interface HttpAssetsRequest extends HttpDatabaseRequest {
SiteAreaID?: string;
WithSiteArea?: boolean;
WithNoSiteArea?: boolean;
+ DynamicOnly?: boolean;
ErrorType?: string;
}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -15,15 +15,18 @@ import validator from 'validator';
import Authorizations from '../authorization/Authorizations';
import AppError from '../exception/AppError';
import BackendError from '../exception/BackendError';
+import ChargingStationStorage from '../storage/mongodb/ChargingStationStorage';
+import SiteAreaStorage from '../storage/mongodb/SiteAreaStorage';
import TenantStorage from '../storage/mongodb/TenantStorage';
import UserStorage from '../storage/mongodb/UserStorage';
import Address from '../types/Address';
import Asset from '../types/Asset';
import { Car, CarCatalog, CarType } from '../types/Car';
import { ChargingProfile } from '../types/ChargingProfile';
-import ChargingStation, { ChargePoint, Connector, ConnectorCurrentLimitSource, CurrentType } from '../types/ChargingStation';
+import ChargingStation, { ChargePoint, Connector, ConnectorCurrentLimitSource, CurrentType, SiteAreaLimitSource } from '../types/ChargingStation';
import Company from '../types/Company';
import ConnectorStats from '../types/ConnectorStats';
+import Consumption from '../types/Consumption';
import { ActionsResponse } from '../types/GlobalType';
import { HTTPError } from '../types/HTTPError';
import OCPIEndpoint from '../types/ocpi/OCPIEndpoint';
@@ -1719,6 +1722,36 @@ export default class Utils {
}
}
+ public static async addSiteLimitationToConsumption(tenantID: string, siteArea: SiteArea, consumption: Consumption): Promise<void> {
+ const tenant: Tenant = await TenantStorage.getTenant(tenantID);
+ if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
+ // Get limit of the site area
+ consumption.limitSiteAreaWatts = 0;
+ // Maximum power of the Site Area provided?
+ if (siteArea && siteArea.maximumPower) {
+ consumption.limitSiteAreaWatts = siteArea.maximumPower;
+ consumption.limitSiteAreaAmps = siteArea.maximumPower / siteArea.voltage;
+ consumption.limitSiteAreaSource = SiteAreaLimitSource.SITE_AREA;
+ } else {
+ // Compute it for Charging Stations
+ const chargingStationsOfSiteArea = await ChargingStationStorage.getChargingStations(tenantID, { siteAreaIDs: [siteArea.id] }, Constants.DB_PARAMS_MAX_LIMIT);
+ for (const chargingStationOfSiteArea of chargingStationsOfSiteArea.result) {
+ for (const connector of chargingStationOfSiteArea.connectors) {
+ consumption.limitSiteAreaWatts += connector.power;
+ }
+ }
+ consumption.limitSiteAreaAmps = Math.round(consumption.limitSiteAreaWatts / siteArea.voltage);
+ consumption.limitSiteAreaSource = SiteAreaLimitSource.CHARGING_STATIONS;
+ // Save Site Area max consumption
+ if (siteArea) {
+ siteArea.maximumPower = consumption.limitSiteAreaWatts;
+ await SiteAreaStorage.saveSiteArea(tenantID, siteArea);
+ }
+ }
+ consumption.smartChargingActive = siteArea.smartCharging;
+ }
+ }
+
public static getTimezone(coordinates: number[]): string {
if (coordinates && coordinates.length === 2) {
return tzlookup(coordinates[1], coordinates[0]);

Implemented Scheduler task for assets