Handle get asset consumptions
MODIFY
src/scheduler/tasks/AssetGetConsumptionTask.ts
src/scheduler/tasks/AssetGetConsumptionTask.ts
@@ -58,6 +58,8 @@ export default class AssetGetConsumptionTask extends SchedulerTask {
assetID: asset.id,
cumulatedConsumptionWh: asset.currentConsumptionWh,
cumulatedConsumptionAmps: Math.floor(asset.currentConsumptionWh / 230),
+ instantAmps: asset.currentInstantAmps,
+ instantWatts: asset.currentInstantWatts,
};
// Add limits
await Utils.addSiteLimitationToConsumption(tenant.id, asset.siteArea, consumption);

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -135,6 +135,7 @@ class RequestMapper {
[ServerAction.ASSETS_IN_ERROR]: AssetService.handleGetAssetsInError.bind(this),
[ServerAction.CHECK_ASSET_CONNECTION]: AssetService.handleCheckAssetConnection.bind(this),
[ServerAction.RETRIEVE_ASSET_CONSUMPTION]: AssetService.handleRetrieveConsumption.bind(this),
+ [ServerAction.ASSET_CONSUMPTION]: AssetService.handleGetAssetConsumption.bind(this),
[ServerAction.SITES]: SiteService.handleGetSites.bind(this),
[ServerAction.SITE]: SiteService.handleGetSite.bind(this),
[ServerAction.SITE_IMAGE]: SiteService.handleGetSiteImage.bind(this),

MODIFY
src/server/rest/service/AssetService.ts
src/server/rest/service/AssetService.ts
-import { Action, Entity } from '../../../types/Authorization';
-import { HTTPAuthError, HTTPError } from '../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
-
+import moment from 'moment';
+import Authorizations from '../../../authorization/Authorizations';
import AppAuthError from '../../../exception/AppAuthError';
import AppError from '../../../exception/AppError';
-import Asset from '../../../types/Asset';
import AssetFactory from '../../../integration/asset/AssetFactory';
-import { AssetInErrorType } from '../../../types/InError';
-import AssetSecurity from './security/AssetSecurity';
import AssetStorage from '../../../storage/mongodb/AssetStorage';
-import Authorizations from '../../../authorization/Authorizations';
-import Constants from '../../../utils/Constants';
-import Logging from '../../../utils/Logging';
-import { ServerAction } from '../../../types/Server';
+import ConsumptionStorage from '../../../storage/mongodb/ConsumptionStorage';
import SiteAreaStorage from '../../../storage/mongodb/SiteAreaStorage';
+import Asset from '../../../types/Asset';
+import { Action, Entity } from '../../../types/Authorization';
+import { HTTPAuthError, HTTPError } from '../../../types/HTTPError';
+import { AssetInErrorType } from '../../../types/InError';
+import { ServerAction } from '../../../types/Server';
import TenantComponents from '../../../types/TenantComponents';
+import Constants from '../../../utils/Constants';
+import Logging from '../../../utils/Logging';
import Utils from '../../../utils/Utils';
+import AssetSecurity from './security/AssetSecurity';
import UtilsService from './UtilsService';
const MODULE_NAME = 'AssetService';
export default class AssetService {
+ public static async handleGetAssetConsumption(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ASSET,
+ Action.LIST, Entity.ASSETS, MODULE_NAME, 'handleGetAssetConsumption');
+ // Filter
+ const filteredRequest = AssetSecurity.filterAssetConsumptionRequest(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.AssetID, MODULE_NAME,
+ 'handleGetAssetConsumption', req.user);
+ // Check auth
+ if (!Authorizations.canReadAsset(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.ERROR,
+ user: req.user,
+ action: Action.READ,
+ entity: Entity.ASSET,
+ module: MODULE_NAME,
+ method: 'handleGetAsset',
+ value: filteredRequest.AssetID
+ });
+ }
+ // Get it
+ const asset = await AssetStorage.getAsset(req.user.tenantID, filteredRequest.AssetID);
+ UtilsService.assertObjectExists(action, asset, `Asset with ID '${filteredRequest.AssetID}' does not exist`,
+ MODULE_NAME, 'handleGetAssetConsumption', req.user);
+ // Check dates
+ if (!filteredRequest.StartDate || !filteredRequest.EndDate) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Start date and end date must be provided',
+ module: MODULE_NAME, method: 'handleGetAssetConsumption',
+ user: req.user,
+ action: action
+ });
+ }
+ // Check dates order
+ if (filteredRequest.StartDate &&
+ filteredRequest.EndDate &&
+ moment(filteredRequest.StartDate).isAfter(moment(filteredRequest.EndDate))) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `The requested start date '${filteredRequest.StartDate.toISOString()}' is after the end date '${filteredRequest.EndDate.toISOString()}' `,
+ module: MODULE_NAME, method: 'handleGetAssetConsumption',
+ user: req.user,
+ action: action
+ });
+ }
+ // Get the ConsumptionValues
+ const consumptions = await ConsumptionStorage.getAssetConsumptions(req.user.tenantID, {
+ assetID: filteredRequest.AssetID,
+ startDate: filteredRequest.StartDate,
+ endDate: filteredRequest.EndDate
+ });
+ // Return
+ res.json(AssetSecurity.filterAssetConsumptionResponse(asset, consumptions, req.user));
+ next();
+ }
+
public static async handleCheckAssetConnection(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ASSET,

MODIFY
src/server/rest/service/security/AssetSecurity.ts
src/server/rest/service/security/AssetSecurity.ts
-import { HttpAssetRequest, HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
-
-import Asset from '../../../../types/Asset';
+import sanitize from 'mongo-sanitize';
import Authorizations from '../../../../authorization/Authorizations';
+import Asset from '../../../../types/Asset';
+import Consumption from '../../../../types/Consumption';
import { DataResult } from '../../../../types/DataResult';
-import SiteAreaSecurity from './SiteAreaSecurity';
+import { HttpAssetConsumptionRequest, HttpAssetRequest, HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
import UserToken from '../../../../types/UserToken';
+import SiteAreaSecurity from './SiteAreaSecurity';
import UtilsSecurity from './UtilsSecurity';
-import sanitize from 'mongo-sanitize';
export default class AssetSecurity {
@@ -82,6 +82,35 @@ export default class AssetSecurity {
return filteredAsset;
}
+ public static filterAssetConsumptionRequest(request: any): HttpAssetConsumptionRequest {
+ return {
+ AssetID: sanitize(request.AssetID),
+ StartDate: sanitize(request.StartDate),
+ EndDate: sanitize(request.EndDate)
+ };
+ }
+
+ public static filterAssetConsumptionResponse(asset: Asset, consumptions: Consumption[], loggedUser: UserToken): Asset {
+ asset.values = [];
+ if (!consumptions) {
+ consumptions = [];
+ }
+ const filteredAsset = this.filterAssetResponse(asset, loggedUser);
+ if (consumptions.length === 0) {
+ filteredAsset.values = [];
+ return filteredAsset;
+ }
+ // Clean
+ filteredAsset.values = consumptions.map((consumption) => ({
+ date: consumption.endedAt,
+ instantWatts: consumption.instantWatts,
+ instantAmps: consumption.instantAmps,
+ limitWatts: consumption.limitWatts,
+ limitAmps: consumption.limitAmps,
+ }));
+ return filteredAsset;
+ }
+
public static filterAssetsResponse(assets: DataResult<Asset>, loggedUser: UserToken): void {
const filteredAssets = [];
if (!assets.result) {

MODIFY
src/storage/mongodb/ConsumptionStorage.ts
src/storage/mongodb/ConsumptionStorage.ts
import Consumption from '../../types/Consumption';
+import global from '../../types/GlobalType';
import Cypher from '../../utils/Cypher';
-import DatabaseUtils from './DatabaseUtils';
import Logging from '../../utils/Logging';
import Utils from '../../utils/Utils';
-import global from '../../types/GlobalType';
+import DatabaseUtils from './DatabaseUtils';
const MODULE_NAME = 'ConsumptionStorage';
@@ -173,6 +173,83 @@ export default class ConsumptionStorage {
return consumptionsMDB;
}
+ static async getAssetConsumptions(tenantID: string, params: { assetID: string; startDate: Date; endDate: Date }): Promise<Consumption[]> {
+ // Debug
+ const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getAssetConsumptions');
+ // Check
+ await Utils.checkTenant(tenantID);
+ // Create filters
+ const filters: any = {};
+ // ID
+ if (params.assetID) {
+ filters.assetID = Utils.convertToObjectID(params.assetID);
+ }
+ // Date provided?
+ if (params.startDate || params.endDate) {
+ filters.startedAt = {};
+ }
+ // Start date
+ if (params.startDate) {
+ filters.startedAt.$gte = Utils.convertToDate(params.startDate);
+ }
+ // End date
+ if (params.endDate) {
+ filters.startedAt.$lte = Utils.convertToDate(params.endDate);
+ }
+ // Create Aggregation
+ const aggregation = [];
+ // Filters
+ if (filters) {
+ aggregation.push({
+ $match: filters
+ });
+ }
+ // Group consumption values per minute
+ aggregation.push({
+ $group: {
+ _id: {
+ year: { '$year': '$startedAt' },
+ month: { '$month': '$startedAt' },
+ day: { '$dayOfMonth': '$startedAt' },
+ hour: { '$hour': '$startedAt' },
+ minute: { '$minute': '$startedAt' }
+ },
+ instantWatts: { $sum: '$instantWatts' },
+ instantAmps: { $sum: '$instantAmps' },
+ limitWatts: { $last: '$limitSiteAreaWatts' },
+ limitAmps: { $last: '$limitSiteAreaAmps' }
+ }
+ });
+ // Rebuild the date
+ aggregation.push({
+ $addFields: {
+ startedAt: {
+ $dateFromParts: { 'year': '$_id.year', 'month': '$_id.month', 'day': '$_id.day', 'hour': '$_id.hour', 'minute': '$_id.minute' }
+ }
+ }
+ });
+ // Same date
+ aggregation.push({
+ $addFields: {
+ endedAt: '$startedAt'
+ }
+ });
+ // Convert Object ID to string
+ DatabaseUtils.pushConvertObjectIDToString(aggregation, 'assetID');
+ aggregation.push({
+ $sort: {
+ startedAt: 1
+ }
+ });
+ // Read DB
+ const consumptionsMDB = await global.database.getCollection<Consumption>(tenantID, 'consumptions')
+ .aggregate(...aggregation, { allowDiskUse: true })
+ .toArray();
+ // Debug
+ Logging.traceEnd(MODULE_NAME, 'getAssetConsumptions', uniqueTimerID, { assetID: params.assetID });
+ return consumptionsMDB;
+ }
+
static async getTransactionConsumptions(tenantID: string, params: { transactionId: number }): Promise<Consumption[]> {
// Debug
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getTransactionConsumptions');

MODIFY
src/types/Asset.ts
src/types/Asset.ts
@@ -14,6 +14,15 @@ export default interface Asset extends CreatedUpdatedProps, AbstractCurrentConsu
dynamicAsset: boolean;
connectionID?: string;
meterID?: string;
+ values: AssetConsumption[],
+}
+
+export interface AssetConsumption {
+ date: Date;
+ instantWatts: number;
+ instantAmps: number;
+ limitWatts: number;
+ limitAmps: number;
}
export enum SchneiderProperty {

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -271,6 +271,7 @@ export enum ServerAction {
ASSET_DELETE = 'AssetDelete',
CHECK_ASSET_CONNECTION = 'CheckAssetConnection',
RETRIEVE_ASSET_CONSUMPTION = 'RetrieveAssetConsumption',
+ ASSET_CONSUMPTION = 'AssetConsumption',
TENANT_CREATE = 'TenantCreate',
TENANTS = 'Tenants',

MODIFY
src/types/requests/HttpAssetRequest.ts
src/types/requests/HttpAssetRequest.ts
@@ -13,3 +13,9 @@ export interface HttpAssetsRequest extends HttpDatabaseRequest {
DynamicOnly?: boolean;
ErrorType?: string;
}
+
+export interface HttpAssetConsumptionRequest {
+ AssetID: string;
+ StartDate: Date;
+ EndDate: Date;
+}

Handle get asset consumptions