Skip to content
Open
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
Binary file removed .DS_Store
Binary file not shown.
12 changes: 10 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ RELEASE_NOTES_TEMP.txt
# Ignore notes and development files
notes/
template_sensor_bob_battery_dashboard.yaml
.DS_Store

# Ignore opencode local workspace
.opencode/

# Ignore SASS cache files
docs/.sass-cache

# Node.js dependencies and test output
node_modules/

coverage/

# Claude Code per-user state — keep local; commands/skills/agents/settings.json are shared
.claude/settings.local.json
.claude/sessions/
.claude/sessions/
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.PHONY: setup test test-unit test-integration

setup:
npm ci

test:
npm test

test-unit:
npm run test:unit

test-integration:
npm run test:integration
8 changes: 8 additions & 0 deletions docs/06-advanced-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ nav_order: 6
## Multi-Battery Management
- **More than 6 batteries:** Override or change `input_number.house_battery_count` and you are good to go.
- The dashboard supports 6 batteries out of the box. For 7 or more, duplicate and edit these cards or create your own dashboard.
- **Manual phase assignment:** Assign each configured battery to `L1`, `L2`, `L3`, or `Unassigned` from the dashboard.
- The overview shows the assigned phase on each battery header and shows live battery AC power per phase in kW.
- The Node-RED battery object exposes this as `battery.phase`, so custom strategies can use the mapping.
- Optional per-phase grid power aliases can be configured in `packages/house_battery_control_config.yaml` as `sensor.p1_meter_l1_power`, `sensor.p1_meter_l2_power`, and `sensor.p1_meter_l3_power`. Leave them commented out if your setup is not three-phase.
- Node-RED exposes configured phase meter values as `msg.grid_power_phase.L1`, `.L2`, and `.L3`, with missing or unreadable values set to `null`.
- Built-in strategies still use aggregate control by default. Enable per-phase peak shaving to let peak shaving also react to phase-level power limits.
- **3-Phase self-consumption:** if you require 0 W grid consumption on a per phase basis, the setup changes slightly.

Note: most homes get billed for the net total of all phases. If that is the case for you as well, ignore these instructions.
Expand Down Expand Up @@ -94,6 +100,8 @@ Controls grid import/export thresholds for `peak shaving` functionality.

- **Import limit:** Maximum power to draw from the grid (example: 16A × 230V = 3680W for CAPTAR contracts)
- **Export limit:** Maximum power to feed back to the grid (example: 3000W if grid connection has export limits)
- **Max phase power:** Shared per-phase safety threshold. This value is exposed to Node-RED as `msg.grid_power_limit_phase` and is used by per-phase peak shaving when enabled.
- **Per-phase peak shaving:** Optional protection that uses configured L1/L2/L3 grid power sensors and battery phase assignments. Charge, Sell, Charge PV, Self-consumption, and Dynamic strategies that select them cap new battery commands against available phase headroom first; if limiting battery interaction is not enough, peak shaving reduces the overloaded phase before any strategy except Full stop is executed. Phase meter aliases must report power in watts; current-only sensors must be converted in `house_battery_control_config.yaml`. Missing phase sensors, unassigned batteries, or the feature being disabled keep the existing aggregate-only behavior. Unavailable batteries are treated as 0W assignable capacity, so remaining batteries on the same phase are asked to carry the correction.
- **Configuration:** Adjust from the "Settings" tab in the Home Assistant dashboard

### Charge / Sell Power Mode
Expand Down
Binary file removed home assistant/.DS_Store
Binary file not shown.
115 changes: 93 additions & 22 deletions home assistant/dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,30 @@ views:
columns: full
rows: 1
- type: heading
heading: Marstek M1
heading: Battery Phase Interaction
heading_style: subtitle
icon: ''
icon: mdi:transmission-tower
visibility:
- condition: state
entity: binary_sensor.house_battery_has_phase_assignment
state: 'on'
- type: glance
entities:
- entity: sensor.house_battery_l1_power
name: L1
- entity: sensor.house_battery_l2_power
name: L2
- entity: sensor.house_battery_l3_power
name: L3
columns: 3
visibility:
- condition: state
entity: binary_sensor.house_battery_has_phase_assignment
state: 'on'
- type: markdown
content: >-
### Marstek M1{% set phase = states('input_select.marstek_m1_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: state
entity: sensor.marstek_m1_device_name
Expand All @@ -431,10 +452,10 @@ views:
- condition: state
entity: sensor.marstek_m1_device_name
state_not: unknown
- type: heading
heading: Marstek M2
heading_style: subtitle
icon: ''
- type: markdown
content: >-
### Marstek M2{% set phase = states('input_select.marstek_m2_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: and
conditions:
Expand Down Expand Up @@ -463,10 +484,10 @@ views:
- condition: numeric_state
entity: input_number.house_battery_count
above: 1
- type: heading
heading: Marstek M3
heading_style: subtitle
icon: ''
- type: markdown
content: >-
### Marstek M3{% set phase = states('input_select.marstek_m3_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: and
conditions:
Expand Down Expand Up @@ -495,10 +516,10 @@ views:
- condition: numeric_state
entity: input_number.house_battery_count
above: 2
- type: heading
heading: Marstek M4
heading_style: subtitle
icon: ''
- type: markdown
content: >-
### Marstek M4{% set phase = states('input_select.marstek_m4_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: and
conditions:
Expand Down Expand Up @@ -527,10 +548,10 @@ views:
- condition: numeric_state
entity: input_number.house_battery_count
above: 3
- type: heading
heading: Marstek M5
heading_style: subtitle
icon: ''
- type: markdown
content: >-
### Marstek M5{% set phase = states('input_select.marstek_m5_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: and
conditions:
Expand Down Expand Up @@ -559,10 +580,10 @@ views:
- condition: numeric_state
entity: input_number.house_battery_count
above: 4
- type: heading
heading: Marstek M6
heading_style: subtitle
icon: ''
- type: markdown
content: >-
### Marstek M6{% set phase = states('input_select.marstek_m6_phase') %}{% if phase == 'L1' %} - Phase 1{% elif phase == 'L2' %} - Phase 2{% elif phase == 'L3' %} - Phase 3{% endif %}
text_only: true
visibility:
- condition: and
conditions:
Expand Down Expand Up @@ -1848,6 +1869,32 @@ views:
text_only: true
grid_options:
columns: full
- type: grid
cards:
- type: heading
icon: mdi:fuse
heading: Per-phase safety limit
heading_style: subtitle
- type: entities
entities:
- entity: input_boolean.house_battery_control_has_phase_protection
- entity: input_number.house_battery_control_max_phase_power
name: Max phase power
- type: markdown
content: >-
<font color=grey><i>When enabled, configured L1/L2/L3 grid power
sensors and phase-assigned batteries are used for per-phase
protection. (Dis)charge throttling is applied by
Self-consumption, Charge PV, Charge, Sell, and Dynamic/Dynamic 2
whenever they select those strategies. Forced phase relief
applies before every selected strategy except Full stop when the
phase overload is not caused by battery interaction. Custom
strategies that write battery commands directly must opt into the
shared phase command limits themselves. Leave disabled for
aggregate-only behavior.</i></font>
text_only: true
grid_options:
columns: full
- type: grid
cards:
- type: heading
Expand Down Expand Up @@ -1890,6 +1937,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M1
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m1_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down Expand Up @@ -1959,6 +2010,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M2
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m2_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down Expand Up @@ -2024,6 +2079,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M3
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m3_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down Expand Up @@ -2089,6 +2148,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M4
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m4_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down Expand Up @@ -2154,6 +2217,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M5
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m5_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down Expand Up @@ -2219,6 +2286,10 @@ views:
icon: mdi:power-plug-battery
heading: Marstek M6
heading_style: title
- type: entities
entities:
- entity: input_select.marstek_m6_phase
name: Phase
- type: vertical-stack
cards:
- type: markdown
Expand Down
Loading