Repository: XKNX/xknx
Pull request #993 (2022-07-24 22:28:18)

Add device restart procedure
<!--
  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.
-->
Add connection oriented device restart procedure.

Fixes # (issue)

## 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.
-->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Checklist:

- [ ] The documentation has been adjusted accordingly
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog (docs/changelog.md)


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 36748ca1..c92cf62a 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -6,16 +6,11 @@ nav_order: 2
 
 # Changelog
 
-## 0.21.5 Secure discovery bugfix 2022-06-28
-
-### Bugfix
-
-- Fix GatewayDescriptor parsing when SearchResponseExtended DIBs are in unexpected order
-
 ### Management
 
 - Add support for creating point-to-point connections to do device management
 - Add `nm_individual_address_check` procedure to check if an individual address is in use on the network
+- Add `dm_restart` procedure to request a basic restart of a device
 - Remove PayloadReader class. Management procedure functions should be used to request data from individual devices.
 
 ### Internals
@@ -23,6 +18,12 @@ nav_order: 2
 - Optionally return a list of Telegrams to be sent to an incoming request as reply. This is used for incoming device management requests. Callbacks for incoming requests (in Interface subclasses) are now handled in an asyncio Task.
 - Incoming L_DATA.req frames are confirmed (L_DATA.con) and replies / acks are sent as L_DATA.ind
 
+## 0.21.5 Secure discovery bugfix 2022-06-28
+
+### Bugfix
+
+- Fix GatewayDescriptor parsing when SearchResponseExtended DIBs are in unexpected order
+
 ## 0.21.4 Fan out 2022-06-07
 
 ### Devices
diff --git a/xknx/management/procedures.py b/xknx/management/procedures.py
index 97f75dd9..d3c2c966 100644
--- a/xknx/management/procedures.py
+++ b/xknx/management/procedures.py
@@ -5,7 +5,7 @@ import logging
 from typing import TYPE_CHECKING
 
 from xknx.exceptions import ManagementConnectionRefused, ManagementConnectionTimeout
-from xknx.telegram import apci
+from xknx.telegram import Telegram, apci, tpci
 from xknx.telegram.address import IndividualAddress, IndividualAddressableType
 
 if TYPE_CHECKING:
@@ -14,6 +14,23 @@ if TYPE_CHECKING:
 logger = logging.getLogger("xknx.management.procedures")
 
 
+async def dm_restart(xknx: XKNX, individual_address: IndividualAddressableType) -> None:
+    """Restart the device."""
+    async with xknx.management.connection(
+        address=IndividualAddress(individual_address)
+    ) as connection:
+        logger.debug("Requesting a Basic Restart of %s.", individual_address)
+        # A_Restart will not be ACKed by the device, so it is manually sent to avoid timeout and retry
+        seq_num = next(connection.sequence_number)
+        telegram = Telegram(
+            destination_address=connection.address,
+            source_address=xknx.current_address,
+            payload=apci.Restart(),
+            tpci=tpci.TDataConnected(sequence_number=seq_num),
+        )
+        await xknx.knxip_interface.send_telegram(telegram)
+
+
 async def nm_individual_address_check(
     xknx: XKNX, individual_address: IndividualAddressableType
 ) -> bool:
diff --git a/xknx/telegram/apci.py b/xknx/telegram/apci.py
index 0a945594..f5b0abad 100644
--- a/xknx/telegram/apci.py
+++ b/xknx/telegram/apci.py
@@ -712,6 +712,9 @@ class Restart(APCI):
     Does not take any payload.
     """
 
+    # Requests a Basic Restart of the communication partner.
+    # Master reset is not implemented yet.
+
     CODE = APCIService.RESTART
 
     def calculated_length(self) -> int:


Test patch:
diff --git a/test/management_tests/procedures_test.py b/test/management_tests/procedures_test.py
index 678ac46a..f4b76685 100644
--- a/test/management_tests/procedures_test.py
+++ b/test/management_tests/procedures_test.py
@@ -7,6 +7,30 @@ from xknx.management import procedures
 from xknx.telegram import IndividualAddress, Telegram, TelegramDirection, apci, tpci
 
 
+@patch("xknx.io.knxip_interface.KNXIPInterface", autospec=True)
+async def test_dm_restart(_if_mock):
+    """Test dm_restart."""
+    xknx = XKNX()
+    individual_address = IndividualAddress("4.0.10")
+
+    connect = Telegram(destination_address=individual_address, tpci=tpci.TConnect())
+    restart = Telegram(
+        destination_address=individual_address,
+        tpci=tpci.TDataConnected(0),
+        payload=apci.Restart(),
+    )
+    disconnect = Telegram(
+        destination_address=individual_address,
+        tpci=tpci.TDisconnect(),
+    )
+    await procedures.dm_restart(xknx, individual_address)
+    assert xknx.knxip_interface.send_telegram.call_args_list == [
+        call(connect),
+        call(restart),
+        call(disconnect),
+    ]
+
+
 @patch("xknx.io.knxip_interface.KNXIPInterface", autospec=True)
 async def test_nm_individual_address_check_success(_if_mock):
     """Test nm_individual_address_check."""