Ajusted static limitation + charging plan with the new design
MODIFY
src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts
src/integration/charging-station-vendor/ChargingStationVendorIntegration.ts
-import ChargingStation, { ConnectorCurrentLimit, ConnectorCurrentLimitSource, StaticLimitAmps } from '../../types/ChargingStation';
-import { OCPPChangeConfigurationCommandResult, OCPPChargingProfileStatus, OCPPClearChargingProfileCommandResult, OCPPClearChargingProfileStatus, OCPPConfigurationStatus, OCPPGetCompositeScheduleCommandResult, OCPPGetCompositeScheduleStatus, OCPPSetChargingProfileCommandResult } from '../../types/ocpp/OCPPClient';
-
-import BackendError from '../../exception/BackendError';
-import { ChargingProfile } from '../../types/ChargingProfile';
import ChargingStationClientFactory from '../../client/ocpp/ChargingStationClientFactory';
-import ChargingStationStorage from '../../storage/mongodb/ChargingStationStorage';
-import Logging from '../../utils/Logging';
+import BackendError from '../../exception/BackendError';
import OCPPUtils from '../../server/ocpp/utils/OCPPUtils';
+import ChargingStationStorage from '../../storage/mongodb/ChargingStationStorage';
+import { ChargingProfile } from '../../types/ChargingProfile';
+import ChargingStation, { ConnectorCurrentLimit, ConnectorCurrentLimitSource, StaticLimitAmps } from '../../types/ChargingStation';
+import { OCPPChangeConfigurationCommandResult, OCPPChargingProfileStatus, OCPPClearChargingProfileCommandResult, OCPPClearChargingProfileStatus, OCPPConfigurationStatus, OCPPGetCompositeScheduleCommandResult, OCPPGetCompositeScheduleStatus, OCPPSetChargingProfileCommandResult } from '../../types/ocpp/OCPPClient';
import { ServerAction } from '../../types/Server';
+import Logging from '../../utils/Logging';
import Utils from '../../utils/Utils';
const MODULE_NAME = 'ChargingStationVendor';
@@ -25,7 +24,7 @@ export default abstract class ChargingStationVendorIntegration {
tenantID: tenantID,
source: chargingStation.id,
action: ServerAction.CHARGING_STATION_LIMIT_POWER,
- message: 'Set Power limitation is being called',
+ message: `Set Power limitation is being called with ${maxAmps}A for '${chargingStation.chargePointVendor}'`,
module: MODULE_NAME, method: 'setPowerLimitation',
detailedMessages: { connectorID, maxAmps }
});
@@ -64,7 +63,7 @@ export default abstract class ChargingStationVendorIntegration {
});
}
// Fixed the max amp per connector
- const maxAmpsPerConnector = maxAmps / chargingStation.connectors.length;
+ const occpLimitAmpValue = this.getOCPPParamValueForChargingLimitation(chargingStation, maxAmps);
// Get the OCPP Client
const chargingStationClient = await ChargingStationClientFactory.getChargingStationClient(tenantID, chargingStation);
if (!chargingStationClient) {
@@ -77,10 +76,18 @@ export default abstract class ChargingStationVendorIntegration {
}
let result: OCPPChangeConfigurationCommandResult;
try {
+ Logging.logDebug({
+ tenantID: tenantID,
+ source: chargingStation.id,
+ action: ServerAction.CHARGING_STATION_LIMIT_POWER,
+ message: `Set Power limitation via OCPP to ${occpLimitAmpValue}A for '${chargingStation.chargePointVendor}'`,
+ module: MODULE_NAME, method: 'setPowerLimitation',
+ detailedMessages: { connectorID, maxAmps, occpLimitAmpValue }
+ });
// Change the config
result = await chargingStationClient.changeConfiguration({
key: this.getOCPPParamNameForChargingLimitation(),
- value: maxAmpsPerConnector + ''
+ value: occpLimitAmpValue + ''
});
} catch (error) {
if (!error.status) {
@@ -97,7 +104,7 @@ export default abstract class ChargingStationVendorIntegration {
await OCPPUtils.requestAndSaveChargingStationOcppParameters(tenantID, chargingStation);
// Update the charger's connectors
for (const connector of chargingStation.connectors) {
- connector.amperageLimit = maxAmpsPerConnector;
+ connector.amperageLimit = maxAmps / chargingStation.connectors.length;
}
// Save it
await ChargingStationStorage.saveChargingStation(tenantID, chargingStation);
@@ -177,22 +184,15 @@ export default abstract class ChargingStationVendorIntegration {
message: 'Charging Station is not connected to the backend',
});
}
- // Clone
- const schneiderChargingProfile = JSON.parse(JSON.stringify(chargingProfile));
- // Check connector
- if (schneiderChargingProfile.connectorID === 0 && schneiderChargingProfile.profile && schneiderChargingProfile.profile.chargingSchedule && chargingStation.connectors) {
- // Divide the power by the number of connectors
- for (const schedulePeriod of schneiderChargingProfile.profile.chargingSchedule.chargingSchedulePeriod) {
- schedulePeriod.limit /= chargingStation.connectors.length;
- }
- }
+ // Convert to vendor specific profile
+ const vendorSpecificChargingProfile = this.convertToVendorChargingProfile(chargingStation, chargingProfile);
try {
// Check if we have to load all connectors in case connector 0 fails
if (chargingProfile.connectorID === 0) {
// Set the Profile
const result = await chargingStationClient.setChargingProfile({
- connectorId: schneiderChargingProfile.connectorID,
- csChargingProfiles: schneiderChargingProfile.profile
+ connectorId: vendorSpecificChargingProfile.connectorID,
+ csChargingProfiles: vendorSpecificChargingProfile.profile
});
// Call each connector?
if (result.status !== OCPPChargingProfileStatus.ACCEPTED) {
@@ -208,7 +208,7 @@ export default abstract class ChargingStationVendorIntegration {
for (const connector of chargingStation.connectors) {
const ret = await chargingStationClient.setChargingProfile({
connectorId: connector.connectorId,
- csChargingProfiles: schneiderChargingProfile.profile
+ csChargingProfiles: vendorSpecificChargingProfile.profile
});
results.push(ret);
}
@@ -234,8 +234,8 @@ export default abstract class ChargingStationVendorIntegration {
}
// Connector ID > 0
const result = await chargingStationClient.setChargingProfile({
- connectorId: schneiderChargingProfile.connectorID,
- csChargingProfiles: schneiderChargingProfile.profile
+ connectorId: vendorSpecificChargingProfile.connectorID,
+ csChargingProfiles: vendorSpecificChargingProfile.profile
});
Logging.logDebug({
tenantID: tenantID,
@@ -600,4 +600,9 @@ export default abstract class ChargingStationVendorIntegration {
}
public abstract getOCPPParamNameForChargingLimitation(): string;
+
+ public abstract getOCPPParamValueForChargingLimitation(chargingStation: ChargingStation, limitAmp: number);
+
+ public abstract convertToVendorChargingProfile(chargingStation: ChargingStation, chargingProfile: ChargingProfile): ChargingProfile;
}
+

MODIFY
src/integration/charging-station-vendor/delta/DeltaChargingStationVendorIntegration.ts
src/integration/charging-station-vendor/delta/DeltaChargingStationVendorIntegration.ts
+import { ChargingProfile } from '../../../types/ChargingProfile';
import ChargingStation from '../../../types/ChargingStation';
import ChargingStationVendorIntegration from '../ChargingStationVendorIntegration';
@@ -9,4 +10,36 @@ export default class DeltaChargingStationVendorIntegration extends ChargingStati
public getOCPPParamNameForChargingLimitation(): string {
return 'Device/GridCurrent';
}
+
+ public getOCPPParamValueForChargingLimitation(chargingStation: ChargingStation, limitAmp: number): number {
+ let numberOfPhase = 1;
+ for (const connector of chargingStation.connectors) {
+ if (connector && connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ break;
+ }
+ }
+ return limitAmp / chargingStation.connectors.length / numberOfPhase;
+ }
+
+ public convertToVendorChargingProfile(chargingStation: ChargingStation, chargingProfile: ChargingProfile): ChargingProfile {
+ // Get vendor specific charging profile
+ const vendorSpecificChargingProfile = JSON.parse(JSON.stringify(chargingProfile));
+ // Check connector
+ if (chargingStation.connectors && vendorSpecificChargingProfile.profile && vendorSpecificChargingProfile.profile.chargingSchedule) {
+ let numberOfPhase = 1;
+ // Check the Connector
+ for (const connector of chargingStation.connectors) {
+ if (connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ }
+ }
+ // Divide the power by the number of connectors and number of phases
+ for (const schedulePeriod of vendorSpecificChargingProfile.profile.chargingSchedule.chargingSchedulePeriod) {
+ schedulePeriod.limit = schedulePeriod.limit / (vendorSpecificChargingProfile.connectorID === 0 ?
+ chargingStation.connectors.length : 1) / numberOfPhase;
+ }
+ }
+ return vendorSpecificChargingProfile;
+ }
}

MODIFY
src/integration/charging-station-vendor/schneider/SchneiderChargingStationVendorIntegration.ts
src/integration/charging-station-vendor/schneider/SchneiderChargingStationVendorIntegration.ts
+import { ChargingProfile } from '../../../types/ChargingProfile';
import ChargingStation from '../../../types/ChargingStation';
import ChargingStationVendorIntegration from '../ChargingStationVendorIntegration';
@@ -9,4 +10,36 @@ export default class SchneiderChargingStationVendorIntegration extends ChargingS
public getOCPPParamNameForChargingLimitation(): string {
return 'maxintensitysocket';
}
+
+ public getOCPPParamValueForChargingLimitation(chargingStation: ChargingStation, limitAmp: number): number {
+ let numberOfPhase = 1;
+ for (const connector of chargingStation.connectors) {
+ if (connector && connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ break;
+ }
+ }
+ return limitAmp / chargingStation.connectors.length / numberOfPhase;
+ }
+
+ public convertToVendorChargingProfile(chargingStation: ChargingStation, chargingProfile: ChargingProfile): ChargingProfile {
+ // Get vendor specific charging profile
+ const vendorSpecificChargingProfile = JSON.parse(JSON.stringify(chargingProfile));
+ // Check connector
+ if (chargingStation.connectors && vendorSpecificChargingProfile.profile && vendorSpecificChargingProfile.profile.chargingSchedule) {
+ let numberOfPhase = 1;
+ // Check the Connector
+ for (const connector of chargingStation.connectors) {
+ if (connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ }
+ }
+ // Divide the power by the number of connectors and number of phases
+ for (const schedulePeriod of vendorSpecificChargingProfile.profile.chargingSchedule.chargingSchedulePeriod) {
+ schedulePeriod.limit = schedulePeriod.limit / (vendorSpecificChargingProfile.connectorID === 0 ?
+ chargingStation.connectors.length : 1) / numberOfPhase;
+ }
+ }
+ return vendorSpecificChargingProfile;
+ }
}

MODIFY
src/integration/charging-station-vendor/webasto/WebastoChargingStationVendorIntegration.ts
src/integration/charging-station-vendor/webasto/WebastoChargingStationVendorIntegration.ts
+import { ChargingProfile } from '../../../types/ChargingProfile';
import ChargingStation from '../../../types/ChargingStation';
import ChargingStationVendorIntegration from '../ChargingStationVendorIntegration';
@@ -9,4 +10,36 @@ export default class WebastoChargingStationVendorIntegration extends ChargingSta
public getOCPPParamNameForChargingLimitation(): string {
return 'OperatorCurrentLimit';
}
+
+ public getOCPPParamValueForChargingLimitation(chargingStation: ChargingStation, limitAmp: number): number {
+ let numberOfPhase = 1;
+ for (const connector of chargingStation.connectors) {
+ if (connector && connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ break;
+ }
+ }
+ return limitAmp / chargingStation.connectors.length / numberOfPhase;
+ }
+
+ public convertToVendorChargingProfile(chargingStation: ChargingStation, chargingProfile: ChargingProfile): ChargingProfile {
+ // Get vendor specific charging profile
+ const vendorSpecificChargingProfile = JSON.parse(JSON.stringify(chargingProfile));
+ // Check connector
+ if (chargingStation.connectors && vendorSpecificChargingProfile.profile && vendorSpecificChargingProfile.profile.chargingSchedule) {
+ let numberOfPhase = 1;
+ // Check the Connector
+ for (const connector of chargingStation.connectors) {
+ if (connector.numberOfConnectedPhase > 0) {
+ numberOfPhase = connector.numberOfConnectedPhase;
+ }
+ }
+ // Divide the power by the number of connectors and number of phases
+ for (const schedulePeriod of vendorSpecificChargingProfile.profile.chargingSchedule.chargingSchedulePeriod) {
+ schedulePeriod.limit = schedulePeriod.limit / (vendorSpecificChargingProfile.connectorID === 0 ?
+ chargingStation.connectors.length : 1) / numberOfPhase;
+ }
+ }
+ return vendorSpecificChargingProfile;
+ }
}

MODIFY
src/server/rest/service/ChargingStationService.ts
src/server/rest/service/ChargingStationService.ts
@@ -440,7 +440,6 @@ export default class ChargingStationService {
}
}
// Ok
- // FIXME: handle failure to take the lock in the response sent
res.json(Constants.REST_RESPONSE_SUCCESS);
next();
}
@@ -448,12 +447,12 @@ export default class ChargingStationService {
public static async handleUpdateChargingProfile(action: ServerAction, req: Request, res: Response, next: NextFunction) {
// Filter
const filteredRequest = ChargingStationSecurity.filterChargingProfileUpdateRequest(req.body);
- // Check Mandatory fields
- Utils.checkIfChargingProfileIsValid(filteredRequest, req);
// Check existence
const chargingStation = await ChargingStationStorage.getChargingStation(req.user.tenantID, filteredRequest.chargingStationID);
UtilsService.assertObjectExists(action, chargingStation, `ChargingStation '${req.body.ChargingStationID}' doesn't exist.`,
MODULE_NAME, 'handleUpdateChargingProfile', req.user);
+ // Check Mandatory fields
+ Utils.checkIfChargingProfileIsValid(chargingStation, filteredRequest, req);
let siteID = null;
if (Utils.isComponentActiveFromToken(req.user, TenantComponents.ORGANIZATION)) {
// Get the Site Area

MODIFY
src/types/ChargingStation.ts
src/types/ChargingStation.ts
-import { ChargePointStatus, OCPPFirmwareStatus, OCPPProtocol, OCPPVersion } from './ocpp/OCPPServer';
-
import CreatedUpdatedProps from './CreatedUpdatedProps';
-import { InactivityStatus } from './Transaction';
import { KeyValue } from './GlobalType';
import { OCPIEvse } from './ocpi/OCPIEvse';
+import { ChargePointStatus, OCPPFirmwareStatus, OCPPProtocol, OCPPVersion } from './ocpp/OCPPServer';
import SiteArea from './SiteArea';
+import { InactivityStatus } from './Transaction';
export default interface ChargingStation extends CreatedUpdatedProps {
id?: string;
@@ -33,6 +32,7 @@ export default interface ChargingStation extends CreatedUpdatedProps {
lastReboot: Date;
chargingStationURL: string;
maximumPower: number;
+ voltage: number;
excludeFromPowerLimitation?: boolean;
powerLimitUnit: PowerLimitUnits;
coordinates: number[];
@@ -87,7 +87,7 @@ export enum PowerLimitUnits {
}
export enum StaticLimitAmps {
- MIN_LIMIT = 2,
+ MIN_LIMIT = 6,
}
export interface Connector {

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
-import { Car, UserCar } from '../types/Car';
-import { ChargePointStatus, OCPPProtocol, OCPPVersion } from '../types/ocpp/OCPPServer';
-import ChargingStation, { ConnectorCurrentType, StaticLimitAmps } from '../types/ChargingStation';
-import User, { UserRole, UserStatus } from '../types/User';
+import fs from 'fs';
+import path from 'path';
+import url from 'url';
+
+import bcrypt from 'bcryptjs';
+import { Request } from 'express';
+import _ from 'lodash';
+import moment from 'moment';
+import { ObjectID } from 'mongodb';
+import passwordGenerator from 'password-generator';
+import tzlookup from 'tz-lookup';
+import { v4 as uuid } from 'uuid';
+import validator from 'validator';
-import { ActionsResponse } from '../types/GlobalType';
-import AppError from '../exception/AppError';
-import Asset from '../types/Asset';
import Authorizations from '../authorization/Authorizations';
+import AppError from '../exception/AppError';
import BackendError from '../exception/BackendError';
+import TenantStorage from '../storage/mongodb/TenantStorage';
+import UserStorage from '../storage/mongodb/UserStorage';
+import Asset from '../types/Asset';
+import { Car } from '../types/Car';
import { ChargingProfile } from '../types/ChargingProfile';
+import ChargingStation, { ConnectorCurrentType, StaticLimitAmps } from '../types/ChargingStation';
import Company from '../types/Company';
-import Configuration from './Configuration';
import ConnectorStats from '../types/ConnectorStats';
-import Constants from './Constants';
-import Cypher from './Cypher';
+import { ActionsResponse } from '../types/GlobalType';
import { HTTPError } from '../types/HTTPError';
-import { InactivityStatus } from '../types/Transaction';
-import Logging from './Logging';
import OCPIEndpoint from '../types/ocpi/OCPIEndpoint';
-import { ObjectID } from 'mongodb';
-import { Request } from 'express';
+import { ChargePointStatus, OCPPProtocol, OCPPVersion } from '../types/ocpp/OCPPServer';
import { ServerAction } from '../types/Server';
import { SettingDBContent } from '../types/Setting';
import Site from '../types/Site';
@@ -27,19 +34,13 @@ import SiteArea from '../types/SiteArea';
import Tag from '../types/Tag';
import Tenant from '../types/Tenant';
import TenantComponents from '../types/TenantComponents';
-import TenantStorage from '../storage/mongodb/TenantStorage';
-import UserStorage from '../storage/mongodb/UserStorage';
+import { InactivityStatus } from '../types/Transaction';
+import User, { UserRole, UserStatus } from '../types/User';
import UserToken from '../types/UserToken';
-import _ from 'lodash';
-import bcrypt from 'bcryptjs';
-import fs from 'fs';
-import moment from 'moment';
-import passwordGenerator from 'password-generator';
-import path from 'path';
-import tzlookup from 'tz-lookup';
-import url from 'url';
-import { v4 as uuid } from 'uuid';
-import validator from 'validator';
+import Configuration from './Configuration';
+import Constants from './Constants';
+import Cypher from './Cypher';
+import Logging from './Logging';
const _centralSystemFrontEndConfig = Configuration.getCentralSystemFrontEndConfig();
const _tenants = [];
@@ -856,7 +857,7 @@ export default class Utils {
}
}
- public static checkIfChargingProfileIsValid(filteredRequest: ChargingProfile, req: Request): void {
+ public static checkIfChargingProfileIsValid(chargingStation: ChargingStation, filteredRequest: ChargingProfile, req: Request): void {
if (!filteredRequest.profile) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
@@ -923,7 +924,14 @@ export default class Utils {
});
}
// Check Min Limitation of each Schedule
+ let maxAmpLimit = 0;
+ for (const connector of chargingStation.connectors) {
+ if (connector) {
+ maxAmpLimit += connector.amperage;
+ }
+ }
for (const chargingSchedulePeriod of filteredRequest.profile.chargingSchedule.chargingSchedulePeriod) {
+ // Check Min
if (chargingSchedulePeriod.limit < StaticLimitAmps.MIN_LIMIT) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
@@ -935,6 +943,18 @@ export default class Utils {
detailedMessages: { chargingSchedulePeriod }
});
}
+ // Check Max
+ if (chargingSchedulePeriod.limit > maxAmpLimit) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ action: ServerAction.CHARGING_PROFILE_UPDATE,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: `Charging Schedule is above the max limitation (${maxAmpLimit}A)`,
+ module: MODULE_NAME, method: 'checkIfChargingProfileIsValid',
+ user: req.user.id,
+ detailedMessages: { chargingSchedulePeriod }
+ });
+ }
}
}

Ajusted static limitation + charging plan with the new design