UX-08 2c review: unit settings anchor to inverters, plainer texts
checks / unit (pull_request) Successful in 8m50s
infra-pin / fresh (pull_request) Successful in 2s

- messages.setting() for one inverter in the list reads
  “Inverter rating” for inverter-b (inverters.max_w), so the config panel
  files per-unit reasons under `inverters`, where the value is changed.
- The tariff card and plausibility reasons name
  “Distribution, levies and VAT” (price_taxes_eur_kwh) again.
- The wrong-direction reasons say "safety stop", the sign_disagree_w
  option says what it protects, and the roster, price-entity,
  integrator_max_w and capacity_kwh texts read more plainly.
- NL: stuurprint -> stuurmodule, "De piekbeperking".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
goodwe-review
2026-09-27 06:51:57 +02:00
parent 2c516a032c
commit c84d5fd14f
13 changed files with 51 additions and 49 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ This applies to everything a home owner or installer reads: notifications, phone
- **Title**: at most 5 everyday words. It says what is wrong ("Meter reading may be stuck"). No "GoodWe:" prefix, no full stop.
- **Body**: **one sentence** of at most 25 words, saying what happened and what to do. A **red alarm** may use up to **3 sentences** (70 words, each sentence at most 30 words), so that no action a person must take is lost.
- **Details**: the last line links to the add-on documentation and names the section with the rest: `Details: add-on documentation, section "<title>" under Notifications.` Background, figures, reason codes and history go there or into the log, never into the body.
- **Status row**: one sentence of at most 15 words. A **red** row may use up to 3 sentences (30 words), like a red alarm. It shows the live numbers the reader acts on and never an HA entity id. **Banner**: at most 12 words. **Refusal**: at most 30 words, naming the setting by its label and then its key, as `messages.setting()` writes it: “Inverter rating” (max_w). **Option name**: at most 6 words, not counting the group prefix. **Option description**: at most 30 words. Where a value comes from belongs in DOCS.md.
- **Status row**: one sentence of at most 15 words. A **red** row may use up to 3 sentences (30 words), like a red alarm. It shows the live numbers the reader acts on and never an HA entity id. **Banner**: at most 12 words. **Refusal**: at most 30 words, naming the setting by its label and then its key, as `messages.setting()` writes it: “Inverter rating” (max_w), or “Inverter rating” for inverter-b (inverters.max_w) for one inverter in the list. **Option name**: at most 6 words, not counting the group prefix. **Option description**: at most 30 words. Where a value comes from belongs in DOCS.md.
- **Log line** (WARNING/ERROR): one line, at most 25 words. The reasoning goes on a DEBUG line. Log lines stay in English and are not translated.
- **Sentences**: one idea each, active voice, present tense, with nothing nested in brackets and no semicolons (a semicolon is a second sentence in disguise). These translate well into Dutch and French.
@@ -40,7 +40,7 @@ Every row of the Commissioning list and every banner is now in `app/messages.py`
Validator refusals, the parse errors they carry, and every option name and description are now short. No rule, key, schema, default or exit code changed. Over 36 spoiled configurations the old and new validators refuse the same number of times. The allowlist went from 167 entries to 43, and only log lines (phase 2d) remain on it.
1. **A refusal names the setting by its label and then its key**, as `messages.setting()` writes it: “Inverter rating” (max_w). The key has to stay, because `configui.problems_by_field()` shows a reason beside every key its text names. A test checks each label against en.yaml. A few reasons no longer mention keys they only named in passing, such as `price_markup_eur_kwh` in the withdrawn-provider reason, so the panel shows those reasons beside fewer settings.
1. **A refusal names the setting by its label and then its key**, as `messages.setting()` writes it: “Inverter rating” (max_w). The key has to stay, because `configui.problems_by_field()` shows a reason beside every key its text names. A test checks each label against en.yaml. A few reasons no longer mention keys they only named in passing, such as `price_markup_eur_kwh` in the withdrawn-provider reason, so the panel shows those reasons beside fewer settings. Each reason still names the setting that fixes it. A setting of one inverter in the list is written “Inverter rating” for inverter-b (inverters.max_w), so the panel shows it beside `inverters`, where that value is changed, and not only beside the flat `max_w` that a list does not read.
2. **Citations moved to DOCS.** Option descriptions say "Measured value, leave it unchanged" or similar, and the record behind the value is under "For installers: where the numbers come from". The UX-01 citation check in `test_translations.py` now checks the tab for that instruction and DOCS for the record.
3. **What left the refusals:** the shipped default in the peak-reserve reason, the superseded-default note in the levy-table reason (DOCS already covers it), the endpoints of the withdrawn providers (DOCS "Energy price providers"), the plant measurement behind `slew_w` (DOCS, beside the setting), and the leg-specific paragraphs of the tariff-formula errors. Each reason keeps the rejected value, the limit and what to change.
4. **Not rewritten:** errors that only a code defect can raise, such as plugin-contract and argument-type checks in `price.py`, `price_forecast.py`, `static_tariff.py`, `arbitrage.py` and `main.py`, which a home owner never reads. INFO lines such as the tariff-formula preview were also left.
+3 -3
View File
@@ -151,7 +151,7 @@ The label and description each setting shows on the Configuration tab, in tab or
| Hardware · Inverter list | Battery level entity (%) (optional) | `inverters.soc_entity` | This battery's own level sensor. Leave it empty if this inverter has none. |
| Hardware · Inverter list | Battery power entity (W) (optional) | `inverters.batt_entity` | This inverter's own battery power sensor. Leave it empty if it has none. |
| Hardware · Inverter list | Battery power sign is reversed (optional) | `inverters.batt_invert` | Switch this on if this inverter's sensor shows giving power as a negative number. |
| Hardware · Inverter list | Usable battery capacity (kWh) (optional) | `inverters.capacity_kwh` | This battery's usable capacity from its datasheet. Fill it in on every inverter or on none, and the work is then shared by energy left. |
| Hardware · Inverter list | Usable battery capacity (kWh) (optional) | `inverters.capacity_kwh` | This battery's usable capacity from its datasheet. Fill it in on every inverter or on none. The add-on then shares the work by the energy each battery has left. |
| Hardware | RS485 emergency stop is fitted | `estop_fitted` | Switch this on if the emergency stop is installed. It only changes a warning on the status page. |
| Hardware | Solar production entity (W) | `pv_entity` | Optional. Your solar inverter's power sensor, positive while producing. The add-on records it and does not use it for control. |
| Hardware | Solar production entities, per inverter (W) | `pv_entities` | Optional. Use one row per solar inverter instead of “Solar production entity” when you have several. If one stops reporting, no total is recorded. |
@@ -201,7 +201,7 @@ The label and description each setting shows on the Configuration tab, in tab or
| Maintenance | Draw down to (%) | `maintenance_soc_floor` | The level the monthly cycle empties the battery to. It is not the “Peak reserve”, and the two differ on purpose. |
| Maintenance | Recharge up to (%) | `maintenance_soc_target` | The level the recharge aims for before the hold starts. |
| Maintenance | Hold at full for (minutes) | `maintenance_hold_min` | How long the battery stays full so its cells can balance. |
| Safety | Wrong-direction threshold (W) | `sign_disagree_w` | A wrong direction counts only when the command and the meter reading both exceed this. Measured value, leave it unchanged. |
| Safety | Wrong-direction threshold (W) | `sign_disagree_w` | Part of the safety stop for an inverter pushing the wrong way. It counts only when command and meter reading both exceed this. Measured value, leave it unchanged. |
| Safety | Wrong-direction time (s) | `sign_disagree_s` | How long a wrong direction may last before a safety stop sets every inverter to 0 W. Measured value, leave it unchanged. |
| Safety | Per-inverter wrong-direction threshold (W) | `unit_sign_disagree_w` | For two or more inverters: the wrong-direction threshold for each inverter. Derived value, leave it unchanged. |
| Safety | Per-inverter wrong-direction time (s) | `unit_sign_disagree_s` | For two or more inverters: how long one inverter may push the wrong way before it is set to 0 W. Derived value, leave it unchanged. |
@@ -220,7 +220,7 @@ The label and description each setting shows on the Configuration tab, in tab or
| Advanced | Command rounding (W) | `step_w` | The command is rounded to a multiple of this, so noise does not change it every cycle. |
| Advanced | Inverter at limit gap (W) | `saturation_w` | When an inverter delivers this much less than asked, it counts as at its limit. The per-inverter wrong-direction threshold is half of this. |
| Advanced | Cycles at limit before freezing | `saturation_cycles` | How many cycles in a row at the limit before control stops building up error. Do not set it to 1. |
| Advanced | Error build-up limit (W) | `integrator_max_w` | Limits how much old error control can carry, and 0 means “Inverter rating”. Do not raise it above the rating. |
| Advanced | Error build-up limit (W) | `integrator_max_w` | Caps the error that control saves up from past cycles. 0 means “Inverter rating”. Do not raise it above the rating. |
| Advanced | Command refresh interval (s) | `heartbeat_s` | How often the command is sent again even when unchanged. Keep it well under the control board's own 30 s limit. |
| Advanced | Missing inputs time limit (s) | `stale_input_s` | How long inputs may be missing before every inverter is set to 0 W. With a P1 meter it starts after “Reading too old after”. |
| Advanced | Longest time between cycles (s) | `cycle_floor_s` | A deadline between control cycles that a healthy meter never reaches. Raise it only for a meter slower than about 10 s. |
+7 -8
View File
@@ -292,7 +292,8 @@ _LEGS = {
neg_factor="Put the minus sign on the offset, not on the factor",
card_required=True,
card_note="The add-on needs it to check your two numbers",
card_causes="that network charges are not in the offset",
card_causes=("that network charges are in “Distribution, levies and VAT” "
"(price_taxes_eur_kwh), not in the offset"),
),
EXPORT_FORMULA_OPTION: _Leg(
option=EXPORT_FORMULA_OPTION,
@@ -678,10 +679,8 @@ def parse_export_formula(value):
def card_refusal(spec: FormulaSpec, delta: float) -> str:
"""The sentence an operator reads when their formula misses its own card.
⚠️ IT NAMES THE DISCREPANCY AND THE SIX WAYS IT HAPPENS. "Invalid
configuration" would be true and useless: every one of these errors looks
exactly like a working tariff from the status page, which is why the check
exists at all.
It names both numbers and what to check. The usual causes are in DOCS.md,
because each of these errors looks like a working tariff on the status page.
"""
got = in_card_units(spec, spec.card_index_eur_per_mwh)
leg = _LEGS[spec.option]
@@ -744,9 +743,9 @@ def implausible_all_in(spec: FormulaSpec, taxes_eur_kwh: float):
return None
return (
f"{spec.option} gives {energy:.5f} EUR/kWh for energy and "
f"{all_in:.5f} EUR/kWh in total on its own card, outside the "
f"plausible band of {PLAUSIBLE_LO_EUR_KWH}-{PLAUSIBLE_HI_EUR_KWH} "
f"EUR/kWh. Check \"units\" against the unit beside your formula.")
f"{all_in:.5f} EUR/kWh with “Distribution, levies and VAT” "
f"(price_taxes_eur_kwh), outside the plausible band of "
f"{PLAUSIBLE_LO_EUR_KWH}-{PLAUSIBLE_HI_EUR_KWH} EUR/kWh. Check \"units\" against the unit beside your formula.")
def implausible_injection(spec: FormulaSpec):
+6 -2
View File
@@ -844,5 +844,9 @@ LABELS = {
def setting(key: str, unit: str = "") -> str:
"""`max_w` -> `“Inverter rating” (max_w)`, plus ` for inverter-b` in a list."""
return f"“{LABELS[key]}” ({key})" + (f" for {unit}" if unit else "")
"""`max_w` -> `“Inverter rating” (max_w)`. For one inverter in a list it is
`“Inverter rating” for inverter-b (inverters.max_w)`, so the config panel
also shows the reason beside `inverters`, where that value is changed."""
if unit:
return f"“{LABELS[key]}” for {unit} (inverters.{key})"
return f"“{LABELS[key]}” ({key})"
+8 -9
View File
@@ -703,8 +703,8 @@ def _roster_problems(opts: dict) -> list[str]:
if entries and flat_sp:
problems.append(
f"{S('setpoint_entity')} is set to {flat_sp}, but the "
f"{S('inverters')} has {len(entries)} entries. Move that inverter "
"into the list and clear this setting.")
f"{S('inverters')} is also filled in. Move that inverter into the "
"list and clear this setting.")
return problems
@@ -924,8 +924,8 @@ def validate_startup_config(opts: dict, dev_min=None, dev_max=None) -> list[str]
where = _unit_label(opts, min(caps_ix, key=lambda p: p[1])[0])
problems.append(
f"{S('sign_disagree_w')} is {sign_w:.0f} W, but the "
f"{S('max_w', where)} is {weakest:.0f} W, so this check can never "
f"trip. Lower it below {weakest:.0f} W, or raise the rating.")
f"{S('max_w', where)} is {weakest:.0f} W, so this safety stop can "
f"never trip. Lower it below {weakest:.0f} W, or raise the rating.")
# ⚠️ AND THE SAME RULE FOR SAFETY-30's PER-UNIT THRESHOLD, WHICH IS A
# SEPARATE COMPARISON AND THEREFORE A SEPARATE WAY TO DISARM. It grades a
@@ -978,8 +978,8 @@ def validate_startup_config(opts: dict, dev_min=None, dev_max=None) -> list[str]
elif weakest is not None and weakest > 0 and unit_sign_w >= weakest:
problems.append(
f"{S('unit_sign_disagree_w')} is {unit_sign_w:.0f} W, but the "
f"{S('max_w', unit_where)} is {weakest:.0f} W, so this check can "
f"never trip. Lower it below {weakest:.0f} W, or raise the "
f"{S('max_w', unit_where)} is {weakest:.0f} W, so this safety stop "
f"can never trip. Lower it below {weakest:.0f} W, or raise the "
"rating.")
# --- 6. a maintenance charge the ceiling can actually accommodate --------
@@ -1301,9 +1301,8 @@ def _price_problems(opts: dict) -> list[str]:
# day/night tariff has no way to see that it lost.
if ha_price and str(opts.get("static_profile", "") or "").strip():
problems.append(
f"{S('static_profile')} and {_labels(ha_price)} are both set, and "
"only the Home Assistant price entities would count. Clear one of "
"the two.")
f"{S('static_profile')} is set, but {_labels(ha_price)} would win "
"over it. Clear the one you do not use.")
# ⚠️ THE PROFILE ITSELF, PARSED AT STARTUP RATHER THAN AT 02:00. A profile
# that does not tile the day has hours with no price, and the only ways to
+8 -8
View File
@@ -1653,7 +1653,7 @@ check("a flat setpoint_entity left set BESIDE a declared roster is refused. It "
"names an actuator the roster does not, so nothing would ever write it - "
"a site running quietly smaller than its own configuration, which is the "
"'silently a different config' outcome this card ranks below a refusal",
_why(_p_flat, "but the “Inverter list” (inverters) has"))
_why(_p_flat, "but the “Inverter list” (inverters) is also filled in"))
_p_dup = validate_startup_config(_opts(setpoint_entity="", inverters=[
dict(name="twin", setpoint_entity="number.sp_a", max_w=A_RATING),
dict(name="twin", setpoint_entity="number.sp_b",
@@ -1739,8 +1739,8 @@ check("the fixture actually desynchronises the two lists, or the two checks "
check("⚠️ RULE 5 NAMES THE UNIT THE NUMBER CAME OFF. The weakest cap is "
"inverter-c's 1000 W; it sits at caps index 1, and `_unit_label(opts, 1)` "
"is inverter-B. The refusal says inverter-c",
_why(_p_skew, "(max_w) for inverter-c is 1000 W")
and not _why(_p_skew, "(max_w) for inverter-b is 1000 W"))
_why(_p_skew, "for inverter-c (inverters.max_w) is 1000 W")
and not _why(_p_skew, "for inverter-b (inverters.max_w) is 1000 W"))
# ⚠️ MAINT-05 MOVED RULE 6's SUBJECT, AND THIS CHECK MOVED WITH IT. It used to
# assert the refusal named inverter-b, because rule 6 graded the FIRST entry
# with a usable cap - `Controller.maint_unit` was `inverters[0]` and the shaping
@@ -1754,15 +1754,15 @@ check("⚠️ RULE 6 TOO, AND SINCE MAINT-05 IT IS THE WEAKEST PACK IT NAMES. "
"inverter-c's 1000 W sits at caps index 1, and `_unit_label(opts, 1)` is "
"inverter-B - so a caps index leaking into a roster index would say "
"inverter-b here. The refusal says inverter-c, with inverter-c's number",
_why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” (max_w) for "
"inverter-c is 1000 W")
and not _why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” (max_w) for "
_why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” for "
"inverter-c (inverters.max_w) is 1000 W")
and not _why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” for "
"inverter-b"))
check("...and it is no longer graded against the FIRST usable cap at all, which "
"is the behaviour change MAINT-05 makes here: inverter-b's 2000 W was the "
"pre-card answer and appears nowhere in the maintenance refusal now",
not _why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” (max_w) for "
"inverter-b is 2000 W"))
not _why(_p_skew, "(maintenance_charge_w) is 3000 W, but the “Inverter rating” for "
"inverter-b (inverters.max_w) is 2000 W"))
check("...and the counterfactual, so this is not a check that would pass on a "
"validator that named nothing: the OLD expressions still evaluate, on "
"this same config, to the wrong names",
+1 -1
View File
@@ -554,7 +554,7 @@ check(f"every label a refusal quotes is the setting's name in en.yaml "
check("...and a refusal quotes the label and then the key, so the config "
"panel still files it under that setting",
messages.setting("max_w", "inverter-b")
== "“Inverter rating” (max_w) for inverter-b")
== "“Inverter rating” for inverter-b (inverters.max_w)")
print(f"CI: UX-08 allowlist {len(allow)} entries "
+ ", ".join(f"{k} {sum(1 for x in allow if x.startswith(k + ' '))}"
for k in LIMITS))
+3 -3
View File
@@ -2198,7 +2198,7 @@ _ps = validate_startup_config(
-5000, 5000)
check("a static_profile AND the Home Assistant price entities is refused too - "
"main.py reads one of them for `cheap` and the loser is silent",
len(_ps) == 1 and why(_ps, "Home Assistant price entities would count"))
len(_ps) == 1 and why(_ps, "(price_now_entity) and “Average price entity” (price_avg_entity) would win"))
check("the Home Assistant entities alone still start, so nothing is taken away "
"from a live install",
validate_startup_config(vbase(price_now_entity="sensor.now",
@@ -3428,7 +3428,7 @@ check(f"network charges folded into `offset` move the card's own price to "
check(f"...and it is refused, by the CARD rule, naming the discrepancy and "
f"the network-charge cause ({_double[:60]!r})",
"does not match its own card" in _double and "the card says" in _double
and "network charges are not in the offset" in _double)
and "(price_taxes_eur_kwh), not in the offset" in _double)
_CORRUPTIONS = {
"VAT left out (vat_pct 0)": spoilt(vat_pct=0),
@@ -5360,7 +5360,7 @@ _T16_DIFFER = [
# UX-08 2c: a negative factor gets one sentence on both legs now ("put the
# minus sign on the offset"), so it left this table.
("a card miss -> what else to check on THIS leg",
spoilt(factor=0.120), "network charges are not in the offset",
spoilt(factor=0.120), "(price_taxes_eur_kwh), not in the offset",
"vat_pct is 0 for injection"),
("invalid JSON -> WHEN this option is read at all, which is not the "
"same answer on the two legs: `price_formula` decides nothing unless "
+1 -1
View File
@@ -859,7 +859,7 @@ check("static_profile alongside the Home Assistant price entities is refused - "
why(validate_startup_config(
vbase(static_profile=json.dumps(TWO_PERIOD),
price_now_entity="sensor.now", price_avg_entity="sensor.avg"),
-5000, 5000), "Home Assistant price entities would count"))
-5000, 5000), "(price_avg_entity) would win over it"))
check("...and the Home Assistant pair ALONE still starts, so nothing is taken "
"away from a live install",
validate_startup_config(vbase(price_now_entity="sensor.now",
+3 -3
View File
@@ -452,11 +452,11 @@ check("⚠️ A CHARGE RATE THE SMALLEST PACK CANNOT TAKE IS REFUSED, even thoug
"recalibrated, so its cap could not contradict anything; it is now, and "
"its cheap-window charge would ask for hi=-3000 against lo=-2000 - the "
"`lo > hi` contradiction, 0 W, on the pack nobody was watching",
len(_p_two) == 1 and why(_p_two, "(max_w) for inverter-b is 2000 W"))
len(_p_two) == 1 and why(_p_two, "for inverter-b (inverters.max_w) is 2000 W"))
check("...and the message names the pack the number came off, not the first "
"entry - an operator told to lower a number on the wrong device is being "
"sent to the wrong device by the message that exists to stop that",
not why(_p_two, "(max_w) for inverter-a"))
not why(_p_two, "for inverter-a (inverters.max_w)"))
check("...while the same rate against two packs that can both take it starts, "
"so this is a comparison and not a blanket refusal of rosters",
[p for p in validate_startup_config(
@@ -1223,7 +1223,7 @@ def _verdict(opts, token):
ps = [p for p in validate_startup_config(opts, -5000, 5000) if token in p]
if not ps:
return (False, None, None)
m = re.search(r"\(max_w\) for (\S+) is (\d+) W", ps[0])
m = re.search(r"for (\S+) \(inverters\.max_w\) is (\d+) W", ps[0])
return (True, m.group(1) if m else None, float(m.group(2)) if m else None)
+3 -3
View File
@@ -78,7 +78,7 @@ configuration:
description: "Switch this on if this inverter's sensor shows giving power as a negative number."
capacity_kwh:
name: "Usable battery capacity (kWh) (optional)"
description: "This battery's usable capacity from its datasheet. Fill it in on every inverter or on none, and the work is then shared by energy left."
description: "This battery's usable capacity from its datasheet. Fill it in on every inverter or on none. The add-on then shares the work by the energy each battery has left."
estop_fitted:
name: "Hardware · RS485 emergency stop is fitted"
description: "Switch this on if the emergency stop is installed. It only changes a warning on the status page."
@@ -228,7 +228,7 @@ configuration:
description: "How long the battery stays full so its cells can balance."
sign_disagree_w:
name: "Safety · Wrong-direction threshold (W)"
description: "A wrong direction counts only when the command and the meter reading both exceed this. Measured value, leave it unchanged."
description: "Part of the safety stop for an inverter pushing the wrong way. It counts only when command and meter reading both exceed this. Measured value, leave it unchanged."
sign_disagree_s:
name: "Safety · Wrong-direction time (s)"
description: "How long a wrong direction may last before a safety stop sets every inverter to 0 W. Measured value, leave it unchanged."
@@ -285,7 +285,7 @@ configuration:
description: "How many cycles in a row at the limit before control stops building up error. Do not set it to 1."
integrator_max_w:
name: "Advanced · Error build-up limit (W)"
description: "Limits how much old error control can carry, and 0 means “Inverter rating”. Do not raise it above the rating."
description: "Caps the error that control saves up from past cycles. 0 means “Inverter rating”. Do not raise it above the rating."
heartbeat_s:
name: "Advanced · Command refresh interval (s)"
description: "How often the command is sent again even when unchanged. Keep it well under the control board's own 30 s limit."
+6 -6
View File
@@ -25,7 +25,7 @@ configuration:
description: "De ESPHome-entiteit waar de add-on het vermogen van de omvormer naartoe schrijft. Laat dit leeg als u de lijst met omvormers invult."
soc_entity:
name: "Hardware · Entiteit laadniveau (%)"
description: "De sensor met het laadniveau van de batterij in procent. Gebruik de meting van de stuurprint zelf."
description: "De sensor met het laadniveau van de batterij in procent. Gebruik de meting van de stuurmodule zelf."
batt_entity:
name: "Hardware · Entiteit batterijvermogen (W)"
description: "De sensor met het batterijvermogen van deze omvormer. Positief moet betekenen dat de batterij stroom levert."
@@ -59,7 +59,7 @@ configuration:
description: "Zet dit aan als de sensor van deze omvormer leveren als een negatief getal toont."
capacity_kwh:
name: "Bruikbare capaciteit (kWh) (optioneel)"
description: "De bruikbare capaciteit van deze batterij, van het datablad. Vul ze bij elke omvormer in of bij geen enkele. Het werk wordt dan verdeeld volgens de energie die over is."
description: "De bruikbare capaciteit van deze batterij, van het datablad. Vul ze bij elke omvormer in of bij geen enkele. De add-on verdeelt het werk dan volgens de energie die elke batterij nog heeft."
estop_fitted:
name: "Hardware · RS485-noodstop is geplaatst"
description: "Zet dit aan als de noodstop geïnstalleerd is. Het verandert alleen een waarschuwing op de statuspagina."
@@ -128,7 +128,7 @@ configuration:
description: "Optioneel. Een prognose van het gemiddelde vermogen in het lopende kwartier. Laat u dit leeg, dan is er geen piekgrens."
peak_soc_floor:
name: "Grenzen · Piekreserve (%)"
description: "Het afvlakken van de piek laat de batterij niet onder dit niveau zakken, gewoon verbruik wel. Afgeleide waarde, laat ze ongewijzigd."
description: "De piekbeperking laat de batterij niet onder dit niveau zakken, gewoon verbruik wel. Afgeleide waarde, laat ze ongewijzigd."
price_provider:
name: "Tarief · Bron van dynamische prijzen"
description: "Waar de prijzen vandaan komen. Een bron kiezen start het ophalen van prijzen, ook als handelen uit staat."
@@ -209,7 +209,7 @@ configuration:
description: "Hoe lang de batterij vol blijft zodat de cellen in evenwicht komen."
sign_disagree_w:
name: "Veiligheid · Drempel verkeerde richting (W)"
description: "Een verkeerde richting telt pas als het commando en de meterstand allebei boven deze waarde liggen. Gemeten waarde, laat ze ongewijzigd."
description: "Deel van de veiligheidsstop voor een omvormer die de verkeerde kant op duwt. Het telt pas als commando en meterstand allebei boven deze waarde liggen. Gemeten waarde, laat ze ongewijzigd."
sign_disagree_s:
name: "Veiligheid · Tijd verkeerde richting (s)"
description: "Hoe lang een verkeerde richting mag duren voor een veiligheidsstop elke omvormer op 0 W zet. Gemeten waarde, laat ze ongewijzigd."
@@ -266,10 +266,10 @@ configuration:
description: "Hoeveel cycli na elkaar aan de grens voor de regeling stopt met afwijking opbouwen. Zet dit niet op 1."
integrator_max_w:
name: "Geavanceerd · Grens opgebouwde afwijking (W)"
description: "Beperkt hoeveel oude afwijking de regeling meedraagt, en 0 betekent het vermogen van de omvormer. Zet dit niet hoger dan dat vermogen."
description: "Begrenst de afwijking die de regeling uit vorige cycli opspaart. 0 betekent het vermogen van de omvormer. Zet dit niet hoger dan dat vermogen."
heartbeat_s:
name: "Geavanceerd · Commando opnieuw sturen na (s)"
description: "Hoe vaak het commando opnieuw gestuurd wordt, ook als het gelijk bleef. Houd dit ruim onder de eigen grens van 30 s van de stuurprint."
description: "Hoe vaak het commando opnieuw gestuurd wordt, ook als het gelijk bleef. Houd dit ruim onder de eigen grens van 30 s van de stuurmodule."
stale_input_s:
name: "Geavanceerd · Tijdslimiet ontbrekende invoer (s)"
description: "Hoe lang invoer mag ontbreken voor elke omvormer op 0 W gaat. Bij een P1-meter begint dit pas na de grens voor een te oude meting."