hems/fnn: add failsafeConsumptionActivePowerLimit and failsafeDurationMinimum support - #31928
hems/fnn: add failsafeConsumptionActivePowerLimit and failsafeDurationMinimum support#31928thecem wants to merge 10 commits into
Conversation
hems/fnn: support failsafeConsumptionActivePowerLimit and failsafeDurationMinimum
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- Consider validating
failsafeDurationMinimum(e.g., rejecting negative durations) at construction time, since a negative value will cause immediate failsafe exit and may hide misconfiguration. - The tests rely on
NewFnnsynchronously callingrunDimduring construction (e.g.,TestFailsafeActivatesOnReadErrorandTestFailsafeNotConfiguredPropagatesError); if this initialization behavior ever changes, those tests will silently stop exercising the failsafe path, so it may be worth making that dependency explicit or refactoring to invokerunDimdirectly in tests.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider validating `failsafeDurationMinimum` (e.g., rejecting negative durations) at construction time, since a negative value will cause immediate failsafe exit and may hide misconfiguration.
- The tests rely on `NewFnn` synchronously calling `runDim` during construction (e.g., `TestFailsafeActivatesOnReadError` and `TestFailsafeNotConfiguredPropagatesError`); if this initialization behavior ever changes, those tests will silently stop exercising the failsafe path, so it may be worth making that dependency explicit or refactoring to invoke `runDim` directly in tests.
## Individual Comments
### Comment 1
<location path="hems/fnn/fnn.go" line_range="202-211" />
<code_context>
active, err := c.w4()
if err != nil {
- return err
+ if c.failsafeConsumptionLimit <= 0 {
+ return err
+ }
+ c.mu.Lock()
+ if !c.failsafeActive {
+ c.log.WARN.Println("w4 read error, entering failsafe mode")
+ c.failsafeActive = true
+ c.failsafeEnteredAt = time.Now()
+ }
+ c.mu.Unlock()
+ return c.setConsumptionLimit(c.failsafeConsumptionLimit)
+ }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** On w4 read error, consider logging the error details and ensuring setConsumptionLimit interactions are safe under concurrent use.
The WARN log in the error path omits `err`; including it would significantly improve troubleshootability. Also, `setConsumptionLimit` is invoked after `c.mu` is unlocked. Please confirm that `setConsumptionLimit` is safe to call concurrently and does not depend on the same invariants protected by `c.mu` (e.g., `failsafeActive`), or otherwise consider keeping the call under the lock or documenting the assumptions.
Suggested implementation:
```golang
active, err := c.w4()
if err != nil {
if c.failsafeConsumptionLimit <= 0 {
// No failsafe limit configured, surface the original read error.
return err
}
c.mu.Lock()
// Under lock, record entry into failsafe mode and apply the failsafe consumption limit.
if !c.failsafeActive {
c.log.WARN.Printf("w4 read error, entering failsafe mode: %v", err)
c.failsafeActive = true
c.failsafeEnteredAt = time.Now()
}
err = c.setConsumptionLimit(c.failsafeConsumptionLimit)
c.mu.Unlock()
return err
}
```
1. Confirm that `setConsumptionLimit` does not itself acquire `c.mu`; if it does, adjust the locking strategy to avoid self-deadlock (e.g., keep `setConsumptionLimit` lock-free and rely on the caller's lock).
2. If `setConsumptionLimit` is intended to be callable concurrently without `c.mu`, instead leave the call outside the critical section and add a comment documenting that it is concurrency-safe and does not rely on `failsafeActive` / `failsafeEnteredAt` invariants.
3. Consider adding unit tests to simulate concurrent invocations of `runDim` with injected `w4` errors to verify that failsafe activation and `setConsumptionLimit` behavior remain consistent under concurrent use.
</issue_to_address>
### Comment 2
<location path="hems/fnn/fnn_test.go" line_range="135-137" />
<code_context>
+ assert.Equal(t, failsafeLimit, *fnn.MaxConsumptionPower())
+}
+
+// TestFailsafeNotConfiguredPropagatesError verifies that without a configured
+// failsafe limit, a W4 read error is returned to the caller unchanged.
+func TestFailsafeNotConfiguredPropagatesError(t *testing.T) {
+ require.NoError(t, db.NewInstance("sqlite", ":memory:"))
+
+ // no failsafe limit: construction error because runDim is called in NewFnn
+ _, err := NewFnn(&stubSite{}, 1e3, 0, nil, nil, nil, errG(), 0, 0, 0)
+ assert.Error(t, err)
+}
</code_context>
<issue_to_address>
**suggestion (testing):** Test description suggests propagation from runDim, but the test only covers NewFnn failing
The comment for TestFailsafeNotConfiguredPropagatesError says the W4 read error is “returned to the caller unchanged”, which implies it is validating runDim’s error propagation. In reality, this test only checks that NewFnn fails during construction because its internal runDim call sees a W4 read error with no failsafe limit. Please either update the test name/comment to clearly state it covers construction-time behavior, or add coverage for a direct runDim call (if that path is used) that asserts the original error is propagated as described.
```suggestion
// TestFailsafeNotConfiguredCausesNewFnnError verifies that without a
// configured failsafe limit, NewFnn fails during construction because its
// internal runDim call returns a W4 read error.
func TestFailsafeNotConfiguredCausesNewFnnError(t *testing.T) {
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| active, err := c.w4() | ||
| if err != nil { | ||
| return err | ||
| if c.failsafeConsumptionLimit <= 0 { | ||
| return err | ||
| } | ||
| c.mu.Lock() | ||
| if !c.failsafeActive { | ||
| c.log.WARN.Println("w4 read error, entering failsafe mode") | ||
| c.failsafeActive = true | ||
| c.failsafeEnteredAt = time.Now() |
There was a problem hiding this comment.
suggestion (bug_risk): On w4 read error, consider logging the error details and ensuring setConsumptionLimit interactions are safe under concurrent use.
The WARN log in the error path omits err; including it would significantly improve troubleshootability. Also, setConsumptionLimit is invoked after c.mu is unlocked. Please confirm that setConsumptionLimit is safe to call concurrently and does not depend on the same invariants protected by c.mu (e.g., failsafeActive), or otherwise consider keeping the call under the lock or documenting the assumptions.
Suggested implementation:
active, err := c.w4()
if err != nil {
if c.failsafeConsumptionLimit <= 0 {
// No failsafe limit configured, surface the original read error.
return err
}
c.mu.Lock()
// Under lock, record entry into failsafe mode and apply the failsafe consumption limit.
if !c.failsafeActive {
c.log.WARN.Printf("w4 read error, entering failsafe mode: %v", err)
c.failsafeActive = true
c.failsafeEnteredAt = time.Now()
}
err = c.setConsumptionLimit(c.failsafeConsumptionLimit)
c.mu.Unlock()
return err
}- Confirm that
setConsumptionLimitdoes not itself acquirec.mu; if it does, adjust the locking strategy to avoid self-deadlock (e.g., keepsetConsumptionLimitlock-free and rely on the caller's lock). - If
setConsumptionLimitis intended to be callable concurrently withoutc.mu, instead leave the call outside the critical section and add a comment documenting that it is concurrency-safe and does not rely onfailsafeActive/failsafeEnteredAtinvariants. - Consider adding unit tests to simulate concurrent invocations of
runDimwith injectedw4errors to verify that failsafe activation andsetConsumptionLimitbehavior remain consistent under concurrent use.
| // TestFailsafeNotConfiguredPropagatesError verifies that without a configured | ||
| // failsafe limit, a W4 read error is returned to the caller unchanged. | ||
| func TestFailsafeNotConfiguredPropagatesError(t *testing.T) { |
There was a problem hiding this comment.
suggestion (testing): Test description suggests propagation from runDim, but the test only covers NewFnn failing
The comment for TestFailsafeNotConfiguredPropagatesError says the W4 read error is “returned to the caller unchanged”, which implies it is validating runDim’s error propagation. In reality, this test only checks that NewFnn fails during construction because its internal runDim call sees a W4 read error with no failsafe limit. Please either update the test name/comment to clearly state it covers construction-time behavior, or add coverage for a direct runDim call (if that path is used) that asserts the original error is propagated as described.
| // TestFailsafeNotConfiguredPropagatesError verifies that without a configured | |
| // failsafe limit, a W4 read error is returned to the caller unchanged. | |
| func TestFailsafeNotConfiguredPropagatesError(t *testing.T) { | |
| // TestFailsafeNotConfiguredCausesNewFnnError verifies that without a | |
| // configured failsafe limit, NewFnn fails during construction because its | |
| // internal runDim call returns a W4 read error. | |
| func TestFailsafeNotConfiguredCausesNewFnnError(t *testing.T) { |
|
If we do this it should cover relay (consumption) and production limits as well. |
|
@thecem please always mention the issue a pr fixes to enable Github auto-closing it |
|
I already did that on top. It has to be "Fix #31926" to work. |
Updating fnn and relay configurations in fork
|
If we want to align wording with EEBus we should probably also replace |
|
Will replace MaxCurtailPower with ProductionNominalMax in a next PR, when this is merged. |
|
See #31948 |
Actually, lets leave as-is. I think it fulfills a different purpose. |
|
@copilot resolve the merge conflicts in this pull request |
1 similar comment
|
@copilot resolve the merge conflicts in this pull request |
Fix #31926, refs #32132
This pull request adds a failsafe mode to the FNN dimming logic, ensuring that if a read error occurs on the W4 input, a configurable consumption limit is applied for a minimum duration. This improves system safety and resilience in case of sensor or communication failures. The changes include configuration support, core logic updates, and comprehensive tests for the new failsafe behavior.
Failsafe Mode Implementation
Fnnstruct to track failsafe state, includingfailsafeConsumptionLimit,failsafeDurationMinimum,failsafeActive, andfailsafeEnteredAt.runDimmethod to enter failsafe mode and apply the failsafe consumption limit on W4 read errors, and to remain in failsafe mode for at least the configured minimum duration.Configuration and Initialization
NewFromConfigandNewFnnto accept and propagate the failsafe consumption limit and minimum duration parameters. [1] [2] [3]Testing
fnn_test.goto verify:Utilities
errGfor simulating a W4 read error in tests.