diff --git a/src/data/energy.ts b/src/data/energy.ts index 70aece7feb..88d318e9ed 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -959,21 +959,13 @@ const computeConsumptionDataPartial = ( }; data.timestamps.forEach((t) => { - const used_total = - (data.from_grid?.[t] || 0) + - (data.solar?.[t] || 0) + - (data.from_battery?.[t] || 0) - - (data.to_grid?.[t] || 0) - - (data.to_battery?.[t] || 0); - - outData.used_total[t] = used_total; - outData.total.used_total += used_total; const { grid_to_battery, battery_to_grid, used_solar, used_grid, used_battery, + used_total, solar_to_battery, solar_to_grid, } = computeConsumptionSingle({ @@ -984,6 +976,8 @@ const computeConsumptionDataPartial = ( from_battery: data.from_battery && (data.from_battery[t] ?? 0), }); + outData.used_total[t] = used_total; + outData.total.used_total += used_total; outData.grid_to_battery[t] = grid_to_battery; outData.total.grid_to_battery += grid_to_battery; outData.battery_to_grid![t] = battery_to_grid; @@ -1017,12 +1011,20 @@ export const computeConsumptionSingle = (data: { used_solar: number; used_grid: number; used_battery: number; + used_total: number; } => { - const to_grid = data.to_grid; - const to_battery = data.to_battery; - const solar = data.solar; - const from_grid = data.from_grid; - const from_battery = data.from_battery; + let to_grid = Math.max(data.to_grid || 0, 0); + let to_battery = Math.max(data.to_battery || 0, 0); + let solar = Math.max(data.solar || 0, 0); + let from_grid = Math.max(data.from_grid || 0, 0); + let from_battery = Math.max(data.from_battery || 0, 0); + + const used_total = + (from_grid || 0) + + (solar || 0) + + (from_battery || 0) - + (to_grid || 0) - + (to_battery || 0); let used_solar = 0; let grid_to_battery = 0; @@ -1031,41 +1033,57 @@ export const computeConsumptionSingle = (data: { let solar_to_grid = 0; let used_battery = 0; let used_grid = 0; - if ((to_grid != null || to_battery != null) && solar != null) { - used_solar = (solar || 0) - (to_grid || 0) - (to_battery || 0); - if (used_solar < 0) { - if (to_battery != null) { - grid_to_battery = used_solar * -1; - if (grid_to_battery > (from_grid || 0)) { - battery_to_grid = grid_to_battery - (from_grid || 0); - grid_to_battery = from_grid || 0; - } - } - used_solar = 0; - } - } - if (from_battery != null) { - used_battery = (from_battery || 0) - battery_to_grid; - } + let used_total_remaining = Math.max(used_total, 0); + // Consumption Priority + // Battery_Out -> Grid_Out + // Solar -> Grid_Out + // Solar -> Battery_In + // Grid_In -> Battery_In + // Solar -> Consumption + // Battery_Out -> Consumption + // Grid_In -> Consumption - if (from_grid != null) { - used_grid = from_grid - grid_to_battery; - } + // Battery_Out -> Grid_Out + battery_to_grid = Math.min(from_battery, to_grid); + from_battery -= battery_to_grid; + to_grid -= battery_to_grid; - if (solar != null) { - if (to_battery != null) { - solar_to_battery = Math.max(0, (to_battery || 0) - grid_to_battery); - } - if (to_grid != null) { - solar_to_grid = Math.max(0, (to_grid || 0) - battery_to_grid); - } - } + // Solar -> Grid_Out + solar_to_grid = Math.min(solar, to_grid); + to_grid -= solar_to_grid; + solar -= solar_to_grid; + + // Solar -> Battery_In + solar_to_battery = Math.min(solar, to_battery); + to_battery -= solar_to_battery; + solar -= solar_to_battery; + + // Grid_In -> Battery_In + grid_to_battery = Math.min(from_grid, to_battery); + from_grid -= grid_to_battery; + to_battery -= to_battery; + + // Solar -> Consumption + used_solar = Math.min(used_total_remaining, solar); + used_total_remaining -= used_solar; + solar -= used_solar; + + // Battery_Out -> Consumption + used_battery = Math.min(from_battery, used_total_remaining); + from_battery -= used_battery; + used_total_remaining -= used_battery; + + // Grid_In -> Consumption + used_grid = Math.min(used_total_remaining, from_grid); + from_grid -= used_grid; + used_total_remaining -= from_grid; return { used_solar, used_grid, used_battery, + used_total, grid_to_battery, battery_to_grid, solar_to_battery, diff --git a/test/data/energy.test.ts b/test/data/energy.test.ts index e5fcbe9dc6..5609803ced 100644 --- a/test/data/energy.test.ts +++ b/test/data/energy.test.ts @@ -14,6 +14,47 @@ import { } from "../../src/data/energy"; import type { HomeAssistant } from "../../src/types"; +const checkConsumptionResult = ( + input: { + from_grid: number | undefined; + to_grid: number | undefined; + solar: number | undefined; + to_battery: number | undefined; + from_battery: number | undefined; + }, + exact = true +): { + grid_to_battery: number; + battery_to_grid: number; + solar_to_battery: number; + solar_to_grid: number; + used_solar: number; + used_grid: number; + used_battery: number; + used_total: number; +} => { + const result = computeConsumptionSingle(input); + if (exact) { + assert.equal( + result.used_total, + result.used_solar + result.used_battery + result.used_grid + ); + assert.equal( + input.to_grid || 0, + result.solar_to_grid + result.battery_to_grid + ); + assert.equal( + input.to_battery || 0, + result.grid_to_battery + result.solar_to_battery + ); + assert.equal( + input.solar || 0, + result.solar_to_battery + result.solar_to_grid + result.used_solar + ); + } + return result; +}; + describe("Energy Short Format Test", () => { // Create default to not have to specify a not relevant TimeFormat over and over again. const defaultLocale: FrontendLocaleData = { @@ -88,7 +129,7 @@ describe("Energy Usage Calculation Tests", () => { it("Consuming Energy From the Grid", () => { [0, 5, 1000].forEach((x) => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: x, to_grid: undefined, solar: undefined, @@ -101,6 +142,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: x, used_battery: 0, + used_total: x, solar_to_battery: 0, solar_to_grid: 0, } @@ -108,61 +150,78 @@ describe("Energy Usage Calculation Tests", () => { }); }); it("Solar production, consuming some and returning the remainder to grid.", () => { - [2.99, 3, 10, 100].forEach((s) => { + ( + [ + [2.99, false], // unsolveable : solar < to_grid + [3, true], + [10, true], + [100, true], + ] as any + ).forEach(([s, exact]) => { assert.deepEqual( - computeConsumptionSingle({ - from_grid: 0, - to_grid: 3, - solar: s, - to_battery: undefined, - from_battery: undefined, - }), + checkConsumptionResult( + { + from_grid: 0, + to_grid: 3, + solar: s, + to_battery: undefined, + from_battery: undefined, + }, + exact + ), { grid_to_battery: 0, battery_to_grid: 0, - used_solar: Math.max(0, s - 3), + used_solar: Math.min(s, Math.max(0, s - 3)), used_grid: 0, used_battery: 0, + used_total: s - 3, solar_to_battery: 0, - solar_to_grid: 3, + solar_to_grid: Math.min(3, s), } ); }); }); it("Solar production with simultaneous grid consumption. Excess solar returned to the grid.", () => { - [ - [0, 0], - [3, 0], - [0, 3], - [5, 4], - [4, 5], - [10, 3], - [3, 7], - [3, 7.1], - ].forEach(([from_grid, to_grid]) => { + ( + [ + [0, 0, true], + [3, 0, true], + [0, 3, true], + [5, 4, true], + [4, 5, true], + [10, 3, true], + [3, 7, true], + [3, 7.1, false], // unsolveable: to_grid > solar + ] as any + ).forEach(([from_grid, to_grid, exact]) => { assert.deepEqual( - computeConsumptionSingle({ - from_grid, - to_grid, - solar: 7, - to_battery: undefined, - from_battery: undefined, - }), + checkConsumptionResult( + { + from_grid, + to_grid, + solar: 7, + to_battery: undefined, + from_battery: undefined, + }, + exact + ), { grid_to_battery: 0, battery_to_grid: 0, used_solar: Math.max(0, 7 - to_grid), - used_grid: from_grid, + used_grid: from_grid - Math.max(0, to_grid - 7), + used_total: from_grid - to_grid + 7, used_battery: 0, solar_to_battery: 0, - solar_to_grid: to_grid, + solar_to_grid: Math.min(7, to_grid), } ); }); }); it("Charging the battery from the grid", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: 0, @@ -175,6 +234,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 2, used_battery: 0, + used_total: 2, solar_to_battery: 0, solar_to_grid: 0, } @@ -182,7 +242,7 @@ describe("Energy Usage Calculation Tests", () => { }); it("Consuming from the grid and battery simultaneously", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: 0, @@ -195,6 +255,7 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 5, + used_total: 10, solar_to_battery: 0, solar_to_grid: 0, } @@ -202,7 +263,7 @@ describe("Energy Usage Calculation Tests", () => { }); it("Consuming some battery and returning some battery to the grid", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 0, to_grid: 4, solar: 0, @@ -215,15 +276,15 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 0, used_battery: 1, + used_total: 1, solar_to_battery: 0, solar_to_grid: 0, } ); }); - /* Fails it("Charging and discharging the battery to/from the grid in the same interval.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 1, solar: 0, @@ -234,15 +295,18 @@ describe("Energy Usage Calculation Tests", () => { grid_to_battery: 3, battery_to_grid: 1, used_solar: 0, - used_grid: 1, + used_grid: 2, used_battery: 0, + used_total: 2, + solar_to_battery: 0, + solar_to_grid: 0, } ); - }); */ - /* Test does not pass, battery is not really correct when solar is not present + }); + it("Charging the battery with no solar sensor.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 0, solar: undefined, @@ -255,13 +319,15 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 2, used_battery: 0, + used_total: 2, + solar_to_battery: 0, + solar_to_grid: 0, } ); - }); */ - /* Test does not pass + }); it("Discharging battery to grid while also consuming from grid.", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 4, solar: 0, @@ -274,14 +340,16 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 0, + used_total: 5, + solar_to_grid: 0, + solar_to_battery: 0, } ); }); - */ it("Grid, solar, and battery", () => { assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 7, @@ -294,12 +362,13 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 1, used_grid: 5, used_battery: 0, + used_total: 6, solar_to_battery: 3, solar_to_grid: 3, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 7, @@ -308,16 +377,17 @@ describe("Energy Usage Calculation Tests", () => { }), { grid_to_battery: 0, - battery_to_grid: 0, - used_solar: 1, + battery_to_grid: 3, + used_solar: 4, used_grid: 5, - used_battery: 10, + used_battery: 7, + used_total: 16, solar_to_battery: 3, - solar_to_grid: 3, + solar_to_grid: 0, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 2, to_grid: 7, solar: 7, @@ -325,17 +395,18 @@ describe("Energy Usage Calculation Tests", () => { from_battery: 1, }), { - grid_to_battery: 1, - battery_to_grid: 0, + grid_to_battery: 0, + battery_to_grid: 1, used_solar: 0, - used_grid: 1, - used_battery: 1, - solar_to_battery: 0, - solar_to_grid: 7, + used_grid: 2, + used_battery: 0, + used_total: 2, + solar_to_battery: 1, + solar_to_grid: 6, } ); assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 2, to_grid: 7, solar: 9, @@ -344,17 +415,17 @@ describe("Energy Usage Calculation Tests", () => { }), { grid_to_battery: 0, - battery_to_grid: 0, - used_solar: 1, + battery_to_grid: 1, + used_solar: 2, used_grid: 2, - used_battery: 1, + used_battery: 0, + used_total: 4, solar_to_battery: 1, - solar_to_grid: 7, + solar_to_grid: 6, } ); - /* Test does not pass assert.deepEqual( - computeConsumptionSingle({ + checkConsumptionResult({ from_grid: 5, to_grid: 3, solar: 1, @@ -367,8 +438,48 @@ describe("Energy Usage Calculation Tests", () => { used_solar: 0, used_grid: 5, used_battery: 0, + used_total: 5, + solar_to_battery: 0, + solar_to_grid: 1, + } + ); + assert.deepEqual( + checkConsumptionResult({ + from_grid: 6, + to_grid: 0, + solar: 3, + to_battery: 6, + from_battery: 6, + }), + { + grid_to_battery: 3, + battery_to_grid: 0, + used_solar: 0, + used_grid: 3, + used_battery: 6, + solar_to_battery: 3, + solar_to_grid: 0, + used_total: 9, + } + ); + assert.deepEqual( + checkConsumptionResult({ + from_grid: 0, + to_grid: 1, + solar: 1, + to_battery: 1, + from_battery: 1, + }), + { + grid_to_battery: 0, + battery_to_grid: 1, + used_solar: 0, + used_grid: 0, + used_battery: 0, + solar_to_battery: 1, + solar_to_grid: 0, + used_total: 0, } ); - */ }); });