Repository: XKNX/xknx
Pull request #1669 (2025-03-31 14:09:03)

Fix remaining routing busy time calculation
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->
Current logic calculated the elapsed time instead of the remaining time.


Fixes N/A

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code quality improvements to existing code or addition of tests

## Checklist

- [ ] The documentation has been adjusted accordingly
- [ ] Tests have been added that prove the fix is effective or that the feature works
- [ ] The changes are documented in the changelog (docs/changelog.md)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/XKNX/xknx/pull/1669?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 96.94%. Comparing base [(`60e0523`)](https://app.codecov.io/gh/XKNX/xknx/commit/60e0523513afbe773711bf9b2d37cc55aa7d467c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) to head [(`98d1ed5`)](https://app.codecov.io/gh/XKNX/xknx/commit/98d1ed5051083d8c6933004e6cdf6e69e6bb2ec5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/XKNX/xknx/pull/1669/graphs/tree.svg?width=650&height=150&src=pr&token=irWbIygS84&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)](https://app.codecov.io/gh/XKNX/xknx/pull/1669?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@           Coverage Diff           @@
##             main    #1669   +/-   ##
=======================================
  Coverage   96.93%   96.94%           
=======================================
  Files         160      160           
  Lines       10735    10736    +1     
=======================================
+ Hits        10406    10408    +2     
+ Misses        329      328    -1     
```

| [Files with missing lines](https://app.codecov.io/gh/XKNX/xknx/pull/1669?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/io/routing.py](https://app.codecov.io/gh/XKNX/xknx/pull/1669?src=pr&el=tree&filepath=xknx%2Fio%2Frouting.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9pby9yb3V0aW5nLnB5) | `92.96% <100.00%> (+0.84%)` | :arrow_up: |

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 44a9cc0f..24404ffa 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -6,6 +6,10 @@ nav_order: 2
 
 # Changelog
 
+### Routing
+
+- Fix routing flow control wait time update on multiple RoutingBusy frames.
+
 # 3.6.0 DPT helpers and timezone 2025-02-19
 
 ### Devices
diff --git a/xknx/io/routing.py b/xknx/io/routing.py
index 3b2a191e..3c5d293d 100644
--- a/xknx/io/routing.py
+++ b/xknx/io/routing.py
@@ -104,7 +104,8 @@ class _RoutingFlowControl:
                 self._received_busy_frames,
             )
             # discard frame if wait time is lower than remaining time
-            remaining_ms = (now - self._wait_start_time) * 1000
+            elapsed_ms = (now - self._wait_start_time) * 1000
+            remaining_ms = self._wait_time_ms - elapsed_ms
             if remaining_ms >= routing_busy.wait_time:
                 return
         self._wait_time_ms = routing_busy.wait_time


Test patch:
diff --git a/test/io_tests/routing_test.py b/test/io_tests/routing_test.py
index 1ec575a6..efddd574 100644
--- a/test/io_tests/routing_test.py
+++ b/test/io_tests/routing_test.py
@@ -9,6 +9,7 @@ from xknx.io import Routing
 from xknx.io.routing import (
     BUSY_DECREMENT_TIME,
     BUSY_INCREMENT_COOLDOWN,
+    BUSY_RANDOM_TIME_FACTOR,
     BUSY_SLOWDURATION_TIME_FACTOR,
     ROUTING_INDICATION_WAIT_TIME,
     _RoutingFlowControl,
@@ -158,16 +159,25 @@ class TestFlowControl:
         # not counting one frame due to cooldown time
         await time_travel(BUSY_INCREMENT_COOLDOWN)
         flow_control.handle_routing_busy(RoutingBusy(wait_time=test_wait_time_ms // 2))
+        assert flow_control._received_busy_frames == 1
+        assert flow_control._wait_time_ms == test_wait_time_ms
+        # doesn't count in same 10 ms window but updates wait time
         flow_control.handle_routing_busy(RoutingBusy(wait_time=test_wait_time_ms * 2))
         assert flow_control._received_busy_frames == 1
+        assert flow_control._wait_time_ms == test_wait_time_ms * 2
         # add second busy frame after cooldown has passed
         await time_travel(BUSY_INCREMENT_COOLDOWN)
         flow_control.handle_routing_busy(RoutingBusy(wait_time=test_wait_time_ms // 2))
+        assert flow_control._wait_time_ms == test_wait_time_ms * 2
         assert flow_control._received_busy_frames == 2
 
         task = asyncio.create_task(test_send())
         assert mock.call_count == 0
-        await time_travel(test_wait_time_ms * 2 / 1000 + 2 * 0.5)  # add random time
+        await time_travel(test_wait_time_ms * 2 / 1000 - BUSY_INCREMENT_COOLDOWN)
+        assert mock.call_count == 0  # not yet resolved, wait for additional random time
+        await time_travel(
+            flow_control._received_busy_frames * BUSY_RANDOM_TIME_FACTOR * random_mock()
+        )
         assert mock.call_count == 1
         assert task.done()
         # slowduration