Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/core-owns-house-load.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ftw-webapp": patch
---

When a box does not report the car on its live stream, the app still shows the charger's power on the car, but it no longer takes that power off what the home uses. The box decides what the home's use holds, as the phone app already does.
19 changes: 14 additions & 5 deletions src/lib/state/flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,32 @@ describe('withLoadpointEv', () => {
expect(flowReadings(out).planets.find((p) => p.id === 'ev')!.kw).toBeCloseTo(7.2)
})

it('splits a silent stream so the car is not the house', () => {
// The phone bug: field 10 at 0 W, house holding house+car (~12.7 kW).
const folded = fields([
it('adds the charger to a silent stream and leaves Core’s load alone', () => {
// Field 10 at 0 W while a loadpoint charges: the car gets its node, and
// the house keeps the load the box sent. Core decides what that load
// holds; the app subtracting the car from it guessed for the box.
const silent = fields([
[FID.GRID_W, 0],
[FID.PV_W, -3300],
[FID.BATTERY_W, -9900],
[FID.LOAD_W, 12_700],
[FID.EV_W, 0],
])
const out = withLoadpointEv(folded, 11_400)
const out = withLoadpointEv(silent, 11_400)
const r = flowReadings(out)
expect(r.load).toBeCloseTo(1.3)
expect(r.load).toBeCloseTo(12.7)
const ev = r.planets.find((p) => p.id === 'ev')!
expect(ev.kw).toBeCloseTo(11.4)
expect(ev.sub).toBe('charging')
})

it('keeps a missing or zero load as the box sent it', () => {
const missing = fields([[FID.GRID_W, 0], [FID.EV_W, 0]])
expect(withLoadpointEv(missing, 7_200).has(FID.LOAD_W)).toBe(false)
const zero = fields([[FID.GRID_W, 0], [FID.LOAD_W, 0], [FID.EV_W, 0]])
expect(withLoadpointEv(zero, 7_200).get(FID.LOAD_W)).toBe(0)
})

it('draws the EV node when the stream never sent field 10', () => {
const without = fields(FULL.filter(([fid]) => fid !== FID.EV_W))
const r = flowReadings(withLoadpointEv(without, 7200))
Expand Down
8 changes: 3 additions & 5 deletions src/lib/state/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,12 @@ export function loadpointChargeW(points: readonly { powerW: number }[]): number
}

/**
* Put the car back on its own node when the 1 Hz stream folded it into the
* house.
* Add the charger's reading when the stream has no active EV field.
*
* The overlay fires only when the stream is silent or idle and a loadpoint
* is not. A box that already sends field 10 is left alone, so the two
* sources cannot fight.
* sources cannot fight. Core owns the house load: it stays as the box sent
* it, missing and zero included, as on the phone.
*/
export function withLoadpointEv(
fields: ReadonlyMap<number, number>,
Expand All @@ -236,9 +236,7 @@ export function withLoadpointEv(
if (wire !== undefined && Math.abs(wire) > FLOW_IDLE_W) return fields

const out = new Map(fields)
const load = out.get(FID.LOAD_W) ?? 0
out.set(FID.EV_W, Math.round(evW))
out.set(FID.LOAD_W, Math.max(0, load - evW))
return out
}

Expand Down
Loading