diff --git a/goodwe_controller/DECISIONS/SAFETY-136-slow-meter-settle.md b/goodwe_controller/DECISIONS/SAFETY-136-slow-meter-settle.md index 27b1207..4818202 100644 --- a/goodwe_controller/DECISIONS/SAFETY-136-slow-meter-settle.md +++ b/goodwe_controller/DECISIONS/SAFETY-136-slow-meter-settle.md @@ -18,7 +18,7 @@ limit of SAFETY-128's fast meter fit). It counts arrivals, not changes of value: on the recorded day at 1-2 s the value-based test (`MeterFit.slow()`) called 24 % of the cycles slow, because the recorded house repeats a value for 5 s or more, and the commands then differed from base. So there is no -option. At a 1-3 s meter it is false, the tuning is never touched and the +option. At a meter with a usual gap of 3.75 s or less (1-3 s) it is false, the tuning is never touched and the commands are those of the tree before. A meter that Home Assistant passes on only when its value changes looks as slow as its changes are far apart, as it does to SAFETY-128's fits: a 1-2 s meter with a value that holds for 5 s or @@ -63,10 +63,18 @@ from base. Together their export is 10 363 -> 9 559 Wh (-8 %) and import 8 389 -> 7 835 Wh (-7 %). Only 5 export more than 1 Wh and 14 import more: export by at most 11.5 Wh (a 10 s battery lag, +10 %), import by at most 17 Wh (a 12 s dead time, one inverter, +7 %), and the quarter average by at -most 35 W, once 73 W (10 s meter, 10 s dead time, export -59 Wh). Slow +most 35 W, except two import quarters: +73 W (10 s meter, 10 s dead time, +export -59 Wh) and +40 W (`dead10_p6`, -6 to 34 W, export -68 Wh). Slow batteries with a dead time of 8-10 s export 5-39 % less; a 12 s dead time is the same. `READINGS_LATE` holds are the same seconds in every run. +**Live, a 1-2 s meter can be judged slow.** Through Home Assistant on change, +a value that holds for 4-10 s makes a 1 s meter slow. In 20 rig scenarios the +commands were those of base. On the recorded day (1 s, N=1, 1 day, repeats +dropped; its values hold about 5 s) it was slow in 33 % of cycles, half gain +in 83 cycles, 15 422 commands in both trees, 4 016 with another value (at +most 190 W apart), cost 0.61 EUR in both (review of SAFETY-136). + **Does not change.** Meters of 1-2 s: the same commands in all 63 runs (34 at 1 s, 29 at 2 s), and the recorded-day digests (see the report). The late verdict, the hold and the freeze tests. 15 s late is only partly helped, and diff --git a/goodwe_controller/app/settle.py b/goodwe_controller/app/settle.py index 04e202a..41a7ab4 100644 --- a/goodwe_controller/app/settle.py +++ b/goodwe_controller/app/settle.py @@ -1,10 +1,10 @@ """SAFETY-136: on a slow meter, follow the meter gently until the last move shows. -A meter whose readings come 4 s or more apart, read through Home Assistant, can -be 10-15 s late. The loop then answers the same error again at each cycle +A meter whose usual gap between readings is over 3.75 s (a 4-5 s meter), read +through Home Assistant, can be 10-15 s late. The loop then answers the same error again at each cycle and overshoots. After a move of MOVE_W or more the loop therefore runs at WAIT_SHARE of its gain until the meter has fallen SHOWN of the move, or for at -most WAIT_CYCLES cycles. Off for a meter under 4 s, so those sites run as +most WAIT_CYCLES cycles. Off for a meter with a usual gap of 3.75 s or less, so those sites run as before. DECISIONS/SAFETY-136-slow-meter-settle.md. """ diff --git a/goodwe_controller/test_safety136.py b/goodwe_controller/test_safety136.py index bca5545..5f3a957 100644 --- a/goodwe_controller/test_safety136.py +++ b/goodwe_controller/test_safety136.py @@ -118,6 +118,14 @@ check("the meter fell 200 W of 600 W: still half the gain", check("two cycles waited: the third has the whole gain", s.tuning(T, 300.0, True) is T and s.pend is None) s.moved(500.0, 0.0, 600.0, True) +check("the meter went the other way by half the move: the wait stands", + s.tuning(T, 800.0, True).gain == T.gain * 0.5) +s.reset() +s.moved(500.0, 0.0, -600.0, True) +check("a move down, the meter rose half the move: the whole gain", + s.tuning(T, 800.0, True) is T) +s.reset() +s.moved(500.0, 0.0, 600.0, True) check("the meter fell half the move: the whole gain at once", s.tuning(T, 200.0, True) is T and s.pend is None) s.moved(500.0, 0.0, -600.0, True) @@ -148,6 +156,38 @@ print(f" sum: {tot_on:.1f} Wh, off {tot_off:.1f} Wh") check("the four together export at most 65 % of the run with it off", tot_on <= 0.65 * tot_off) +# --------------------------------------------------------------------------- # +print("\n2b. Another writer overrides the loop: the wait ends") +halved = [] +real_tuning = settle.Settle.tuning + + +def watch_tuning(self, tuning, grid_w, slow): + r = real_tuning(self, tuning, grid_w, slow) + halved.append(r is not tuning) + return r + + +settle.Settle.tuning = watch_tuning +try: + mark = [] + + def bump(rig, c, t): + # right after a cycle that started a wait, another writer moves the + # command, so the next cycle sees `target != loop_w` + if not mark and c.settle.pend and t > 330.0: + mark.append(len(halved)) + c.target = c.target + 300.0 + + r = R.Rig(house=oven, noise=80.0, p1_s=5.0, lag_s=10.0, lag_on=ON, + lag_off=900.0) + r.run(500.0, bump) +finally: + settle.Settle.tuning = real_tuning +nxt = nth(halved, mark[0] if mark else 10 ** 6) +check("the cycle after an override has the whole gain", + bool(mark) and present(nxt) and nxt is False) + # --------------------------------------------------------------------------- # print("\n3. 5 s meter, no lag") for name in ("oven", "kettle", "step", "stepup"): @@ -202,15 +242,19 @@ for p1, expect in ((1.0, False), (2.0, False), (3.0, False), (4.0, True), last = nth(seen, -1) check(f"{p1:.0f} s meter: slow is {expect}", present(last) and last is expect) -# A 1 s meter on a house that changes every 5 s repeats its value 4 times in 5: -# the fit that counts changed values calls that slow, the readings do not (the -# recorded day at 1-2 s showed it). -s, m = settle.Settle(), MeterFit() +# A 1 s meter on a house that changes every 5 s repeats its value 4 times in 5. +# Where every reading arrives (the rig, a polled meter) it is not slow; where +# Home Assistant sends a reading only when the value changes (ha_signed) the +# same meter is slow (T3 of the review; DECISIONS). +s, m, h = settle.Settle(), MeterFit(), settle.Settle() for k in range(60): s.arrive(float(k), k) + h.arrive(float(k), k // 5) m.add(float(k), 0.0, 1800.0 + 100.0 * (k // 5)) -check("a 1 s meter whose value changes every 5 s: not slow, though its value " - "repeats", not s.slow() and m.slow()) +check("every reading arrives, value changes every 5 s: not slow (the value " + "fit says slow)", not s.slow() and m.slow()) +check("the same meter through Home Assistant on change (a reading per " + "change): slow", h.slow()) print() if fails: