Trigger smartcharging after first AC consumption + Unit test sticky limit implementation
MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -420,8 +420,7 @@ export default class OCPPService {
// Save Charging Station
await ChargingStationStorage.saveChargingStation(headers.tenantID, chargingStation);
// First Meter Value -> Trigger Smart Charging to adjust the single phase Car
- if (transaction.numberOfMeterValues === 1 && transaction.phasesUsed &&
- !Utils.isTransactionInProgressOnThreePhases(chargingStation, transaction)) {
+ if (transaction.numberOfMeterValues === 1 && transaction.phasesUsed) {
// Yes: Trigger Smart Charging
await this.triggerSmartCharging(headers.tenantID, chargingStation);
}

MODIFY
test/api/SmartChargingTest.ts
test/api/SmartChargingTest.ts
import { ChargePointErrorCode, ChargePointStatus, OCPPStatusNotificationRequest } from '../../src/types/ocpp/OCPPServer';
import { SapSmartChargingSetting, SettingDB, SmartChargingSetting, SmartChargingSettingsType } from '../../src/types/Setting';
+import Transaction, { CSPhasesUsed } from '../types/Transaction';
import chai, { assert, expect } from 'chai';
import CentralServerService from './client/CentralServerService';
@@ -14,8 +15,8 @@ import SiteAreaContext from './context/SiteAreaContext';
import SiteContext from './context/SiteContext';
import SmartChargingFactory from '../../src/integration/smart-charging/SmartChargingFactory';
import SmartChargingIntegration from '../../src/integration/smart-charging/SmartChargingIntegration';
+import { StaticLimitAmps } from '../../src/types/ChargingStation';
import TenantContext from './context/TenantContext';
-import Transaction from '../types/Transaction';
import chaiSubset from 'chai-subset';
import config from '../config';
import global from '../../src/types/GlobalType';
@@ -85,6 +86,45 @@ class TestData {
}
});
}
+
+ public static async sendMeterValue(voltage: number, amperagePerPhase:number, transaction: Transaction, chargingStationContext: ChargingStationContext,
+ phasesUsed?: CSPhasesUsed): Promise<void> {
+ const meterValues = {
+ energyActiveImportMeterValue: 0,
+ amperageMeterValue: amperagePerPhase,
+ voltageMeterValue: voltage,
+ powerImportMeterValue: !phasesUsed ? amperagePerPhase * voltage : 0,
+ voltageL1MeterValue: 0,
+ voltageL2MeterValue: 0,
+ voltageL3MeterValue: 0,
+ amperageL1MeterValue: 0,
+ amperageL2MeterValue: 0,
+ amperageL3MeterValue: 0,
+ };
+ if (phasesUsed) {
+ if (phasesUsed.csPhase1) {
+ meterValues.amperageL1MeterValue = amperagePerPhase;
+ meterValues.voltageL1MeterValue = voltage;
+ meterValues.powerImportMeterValue += amperagePerPhase * voltage;
+ }
+ if (phasesUsed.csPhase2) {
+ meterValues.amperageL2MeterValue = amperagePerPhase;
+ meterValues.voltageL2MeterValue = voltage;
+ meterValues.powerImportMeterValue += amperagePerPhase * voltage;
+ }
+ if (phasesUsed.csPhase3) {
+ meterValues.amperageL3MeterValue = amperagePerPhase;
+ meterValues.voltageL3MeterValue = voltage;
+ meterValues.powerImportMeterValue += amperagePerPhase * voltage;
+ }
+ }
+ await chargingStationContext.sendConsumptionMeterValue(
+ transaction.connectorId,
+ transaction.id,
+ new Date(),
+ meterValues
+ );
+ }
}
const testData: TestData = new TestData();
@@ -154,6 +194,36 @@ const limit32 = [
}
];
+const limitMinThreePhased = [
+ {
+ 'startPeriod': 0,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE * 3
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE * 3
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE * 3
+ }
+];
+
+const limitMinSinglePhased = [
+ {
+ 'startPeriod': 0,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': StaticLimitAmps.MIN_LIMIT_PER_PHASE
+ }
+];
+
const limit0 = [
{
'startPeriod': 0,
@@ -223,7 +293,6 @@ describe('Smart Charging Service', function() {
});
describe('Test for three phased site area', () => {
- describe('Safe Cars', () => {
before(async () => {
testData.siteContext = testData.tenantContext.getSiteContext(ContextDefinition.SITE_CONTEXTS.SITE_BASIC);
testData.siteAreaContext = testData.siteContext.getSiteAreaContext(ContextDefinition.SITE_AREA_CONTEXTS.WITH_SMART_CHARGING_THREE_PHASED);
@@ -319,38 +388,8 @@ describe('Smart Charging Service', function() {
it('Test for 3 single phased cars charging with lower site area limit and one car on a single phased station (meter value received)', async () => {
// Send meter values for both connectors of the 3 phased stations
- await testData.chargingStationContext.sendConsumptionMeterValue(
- 1,
- transaction.id,
- new Date(),
- {
- energyActiveImportMeterValue: 7360,
- voltageMeterValue: 230,
- voltageL1MeterValue: 230,
- voltageL2MeterValue: 230,
- voltageL3MeterValue: 230,
- amperageMeterValue: 32,
- amperageL1MeterValue: 0,
- amperageL2MeterValue: 32,
- amperageL3MeterValue: 0,
- }
- );
- await testData.chargingStationContext.sendConsumptionMeterValue(
- 2,
- transaction1.id,
- new Date(),
- {
- energyActiveImportMeterValue: 7360,
- voltageMeterValue: 230,
- voltageL1MeterValue: 230,
- voltageL2MeterValue: 230,
- voltageL3MeterValue: 230,
- amperageMeterValue: 32,
- amperageL1MeterValue: 0,
- amperageL2MeterValue: 0,
- amperageL3MeterValue: 32,
- }
- );
+ await TestData.sendMeterValue(230, 32, transaction, testData.chargingStationContext, { csPhase1: false, csPhase2: true, csPhase3: false });
+ await TestData.sendMeterValue(230, 32, transaction1, testData.chargingStationContext, { csPhase1: false, csPhase2: false, csPhase3: true });
const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
// Charging Profiles should use the full power, because every car is charging on another phase
TestData.validateChargingProfile(chargingProfiles[0], transaction);
@@ -360,8 +399,94 @@ describe('Smart Charging Service', function() {
TestData.validateChargingProfile(chargingProfiles[2], transaction2);
expect(chargingProfiles[2].profile.chargingSchedule.chargingSchedulePeriod).containSubset(limit32);
});
+
+ it('Test for sticky limit - 3 single phased cars charging with lower site area limit and one car on a single phased station', async () => {
+ await testData.chargingStationContext.stopTransaction(transaction.id, testData.userContext.tags[0].id, 180, new Date());
+ const transactionStartResponse = await testData.chargingStationContext.startTransaction(1, testData.userContext.tags[0].id, 180, new Date);
+ const transactionResponse = await testData.centralUserService.transactionApi.readById(transactionStartResponse.transactionId);
+ transaction = transactionResponse.data;
+ // Send meter values for both connectors of the 3 phased stations
+ await TestData.sendMeterValue(230, 13, transaction, testData.chargingStationContext, { csPhase1: true, csPhase2: true, csPhase3: true });
+ await TestData.sendMeterValue(230, 24, transaction1, testData.chargingStationContext, { csPhase1: false, csPhase2: false, csPhase3: true });
+ await TestData.sendMeterValue(230, 20, transaction2, testData.chargingStationContext1, { csPhase1: true, csPhase2: false, csPhase3: false });
+
+ const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
+ // Charging Profiles should use the full power, because every car is charging on another phase
+ TestData.validateChargingProfile(chargingProfiles[0], transaction);
+ expect(chargingProfiles[0].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 42
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 42
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 42
+ }
+ ]);
+ TestData.validateChargingProfile(chargingProfiles[1], transaction1);
+ expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 79
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 79
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 79
+ }
+ ]);
+ TestData.validateChargingProfile(chargingProfiles[2], transaction2);
+ expect(chargingProfiles[2].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 22
+ }
+ ]);
});
+
+ it('Test for sticky limit - 3 single phased cars charging with lower site area limit and one car on a single phased station with no consumption on two cars', async () => {
+ // Send meter values for both connectors of the 3 phased stations
+ await TestData.sendMeterValue(230, 0, transaction, testData.chargingStationContext, { csPhase1: true, csPhase2: true, csPhase3: true });
+ await TestData.sendMeterValue(230, 0, transaction2, testData.chargingStationContext1, { csPhase1: true, csPhase2: false, csPhase3: false });
+ const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
+ // Charging Profiles should use the full power, because every car is charging on another phase
+ TestData.validateChargingProfile(chargingProfiles[0], transaction);
+ expect(chargingProfiles[0].profile.chargingSchedule.chargingSchedulePeriod).containSubset(limitMinThreePhased);
+ TestData.validateChargingProfile(chargingProfiles[1], transaction1);
+ expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 79
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 79
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 79
+ }
+ ]);
+ TestData.validateChargingProfile(chargingProfiles[2], transaction2);
+ expect(chargingProfiles[2].profile.chargingSchedule.chargingSchedulePeriod).containSubset(limitMinSinglePhased);
});
+ });
+
describe('Test for single phased site area', () => {
before(async () => {
testData.siteContext = testData.tenantContext.getSiteContext(ContextDefinition.SITE_CONTEXTS.SITE_BASIC);
@@ -404,8 +529,67 @@ describe('Smart Charging Service', function() {
TestData.validateChargingProfile(chargingProfiles[1], transaction1);
expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset(limit32);
});
+
+ it('Test for sticky limit - two cars charging with lower site area limit', async () => {
+ await TestData.sendMeterValue(230, 16, transaction, testData.chargingStationContext, { csPhase1: true, csPhase2: false, csPhase3:false });
+ await TestData.sendMeterValue(230, 20, transaction1, testData.chargingStationContext, { csPhase1: true, csPhase2: false, csPhase3:false });
+ const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
+ TestData.validateChargingProfile(chargingProfiles[0], transaction);
+ expect(chargingProfiles[0].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 17
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 17
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 17
+ }
+ ]);
+ TestData.validateChargingProfile(chargingProfiles[1], transaction1);
+ expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 22
+ }
+ ]);
+ });
+ it('Test for sticky limit - two cars charging with lower site area limit and no consumption on one car', async () => {
+ await TestData.sendMeterValue(230, 0, transaction, testData.chargingStationContext, { csPhase1: true, csPhase2: false, csPhase3:false });
+ await TestData.sendMeterValue(230, 20, transaction1, testData.chargingStationContext, { csPhase1: true, csPhase2: false, csPhase3:false });
+ const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
+ TestData.validateChargingProfile(chargingProfiles[0], transaction);
+ expect(chargingProfiles[0].profile.chargingSchedule.chargingSchedulePeriod).containSubset(limitMinSinglePhased);
+ TestData.validateChargingProfile(chargingProfiles[1], transaction1);
+ expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 22
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 22
+ }
+ ]);
+ });
});
+
describe('Test for DC site area', () => {
before(async () => {
testData.siteContext = testData.tenantContext.getSiteContext(ContextDefinition.SITE_CONTEXTS.SITE_BASIC);
@@ -514,6 +698,42 @@ describe('Smart Charging Service', function() {
}
]);
});
+
+ it('Test for sticky limit - two cars charging with lower site area limit', async () => {
+ await TestData.sendMeterValue(400, 125, transaction, testData.chargingStationContext);
+ await TestData.sendMeterValue(400, 75, transaction1, testData.chargingStationContext);
+ const chargingProfiles = await smartChargingIntegration.buildChargingProfiles(testData.siteAreaContext.getSiteArea());
+ TestData.validateChargingProfile(chargingProfiles[0], transaction);
+ expect(chargingProfiles[0].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 237
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 237
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 237
+ },
+ ]);
+ TestData.validateChargingProfile(chargingProfiles[1], transaction1);
+ expect(chargingProfiles[1].profile.chargingSchedule.chargingSchedulePeriod).containSubset([
+ {
+ 'startPeriod': 0,
+ 'limit': 141
+ },
+ {
+ 'startPeriod': 900,
+ 'limit': 141
+ },
+ {
+ 'startPeriod': 1800,
+ 'limit': 141
+ }
+ ]);
+ });
});
});
describe('Test for ChargingStation refusing the charging profile', () => {

Trigger smartcharging after first AC consumption + Unit test sticky limit implementation