Repository: XKNX/xknx (python)

Issue:
Cover update callback stops in some special case
Hello!

My intention is to use `device_updated_cb` callback for Cover to process some logic during its movement etc.. 
I faced the problem when callback stops working in some special case, for example:

Pretend the blind initial position is 0% and we need to move it to 90%
1) start moving the blind to some position `await cover.set_position(90)`
2) send Stop during movement `cover.stop()` 
3) then continue to move it to the previously given position `await cover.set_position(90)`
Here the `device_updated_cb` stops 
4) if then or instead to send the blind to any other position but except 90%, the callback gets back to work, for example, sending to any of 0-89 or 91-100% .

Not sure if I'm doing something not correctly.
Here is the reduced example:

`
async def cover_update_cb(cover: Cover):
    print('Something inside cover update callback')
    
cover: Cover = None

async def test():
    global cover
    await asyncio.sleep(1)
    await cover.set_position(90)
    await asyncio.sleep(4)
    print('STOP command')
    await cover.stop()
    await asyncio.sleep(2)
    print(f'Continue to move to 90')
    await cover.set_position(90) 
    #  here cover_update_cb stops. If instead set any different position from a position that the blind started moving from at the beginning (in our case 90) then callback will work. 
     
async def main():
    global cover
    xknx = XKNX(daemon_mode=True)     

    cover = Cover(
        xknx,
        'Cover',
        group_address_stop = "1/1/2",
        group_address_position = "1/1/3",
        group_address_position_state = "1/1/4",
        device_updated_cb=cover_update_cb
    )
    
    loop = asyncio.get_running_loop()
    loop.create_task(test())
        
    await xknx.start()
    await xknx.stop()    
    
asyncio.run(main())

`



 


Pull request:
Fix for Cover device_updated_cb 
<!--
  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.
-->
Added `always_callback=True` to Cover's `position_target.process()` to avoid some cases when a callback was not triggered when cover was sent to the same target position after stop command.
E.g. move to 50% position -> stop -> move to 50% position
This led to the situation that the `device_updated_cb` was not triggered.

Fixes #1189

## 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
- [x] The changes are documented in the changelog (docs/changelog.md)

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 03b50890..5834d7d3 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -17,6 +17,7 @@ nav_order: 2
 
 - Parse Data Secure credentials form Keyring from non-IP-Secure interfaces.
 - Parse Data Secure credentials from Keyrings exported for specific interfaces.
+- Fix callback for Cover target position when called with same value consecutively.
 - Fix Windows TCP transport bug when using IP Secure Tunnelling.
 
 ### Protocol
diff --git a/xknx/devices/cover.py b/xknx/devices/cover.py
index 750913e1..489c1364 100644
--- a/xknx/devices/cover.py
+++ b/xknx/devices/cover.py
@@ -360,7 +360,7 @@ class Cover(Device):
                 await self._stop_position_update()
 
         await self.position_current.process(telegram, always_callback=True)
-        await self.position_target.process(telegram)
+        await self.position_target.process(telegram, always_callback=True)
         await self.angle.process(telegram)
         await self.locked.process(telegram)
 


Test patch:
diff --git a/test/devices_tests/cover_test.py b/test/devices_tests/cover_test.py
index 2617622e..9dbe67ad 100644
--- a/test/devices_tests/cover_test.py
+++ b/test/devices_tests/cover_test.py
@@ -830,6 +830,9 @@ class TestCover:
             ("1/2/1", DPTBinary(1), "long"),
             ("1/2/2", DPTBinary(1), "short"),
             ("1/2/4", DPTArray(42), "position"),
+            ("1/2/3", DPTBinary(1), "stop"),
+            # call position with same value again to make sure `always_callback` is set for target position
+            ("1/2/4", DPTArray(42), "position"),
             ("1/2/5", DPTArray(42), "position state"),
             ("1/2/6", DPTArray(42), "angle"),
             ("1/2/7", DPTArray(51), "angle state"),