Fix SMARTCAM Time module and update tests#1659
Fix SMARTCAM Time module and update tests#1659ZeliardM wants to merge 13 commits intopython-kasa:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes the SmartCam Time module's set_time method to properly set the device time manually. The fix addresses three key issues: formatting local_time in UTC (not machine local time), removing microseconds from the timestamp format, and switching timing_mode from "ntp" to "manual" so devices accept manual time updates.
Changes:
- Fixed time formatting to use UTC without microseconds (format: 'YYYY-MM-DD HH:MM:SS')
- Added logic to set timing_mode to "manual" before updating clock_status (for devices that support it)
- Combined zone_id and timing_mode updates into a single API call for efficiency
- Added comprehensive unit tests covering all device types (cameras with timing_mode, hubs without timing_mode)
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| kasa/smartcam/modules/time.py | Rewrote set_time method to format time in UTC, set timing_mode to manual, and handle devices without timing_mode field |
| tests/test_common_modules.py | Added three comprehensive unit tests verifying UTC formatting, ZoneInfo handling, and H200 hub compatibility |
| tests/fakeprotocol_smartcam.py | Fixed variable shadowing issue by renaming nested 'section' variable to 'section_data' |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1659 +/- ##
=======================================
Coverage 93.22% 93.23%
=======================================
Files 157 157
Lines 9815 9825 +10
Branches 1003 1004 +1
=======================================
+ Hits 9150 9160 +10
Misses 472 472
Partials 193 193 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Still working on the correct handling of the time zone information here. |
|
@rytilahti After further investigation, there appears no way for set_time to directly set the clock time on SMARTCAM devices. The set_time commands have been updated to correctly handle the timezone now and removed the sending of the seconds_from_1970 through the setTimezone function since it does nothing. Confirmed that this is all handled correctly now and that if the device is connected to the internet, it will pull the correct clock time via NTP. This should be good to go. |
| await time_mod.set_time(test_time) | ||
| await dev.update() | ||
| assert time_mod.time == test_time | ||
| if isinstance(dev, SmartCamDevice): |
There was a problem hiding this comment.
I do really like clean code, so adding a separate test for handling the special behavior would be a better approach.
There was a problem hiding this comment.
Re-worked the tests here to work with smartcam devices only for testing this.
There was a problem hiding this comment.
It would be nicer to add a separate test for the special "logs a warning" case that to make it explicit, and skip this test for smartcam devices.
This way we can cover both paths without having a need for conditionals in tests which are most of the time a bad idea in tests.
|
@rytilahti Yea, I forgot to push the commit, lol I re-worked it yet again. Give it another once over and let me know if this looks cleaner. I made the changes to the fake_smartcamprotocol.py for the multiple calls issue in the tests. Then, modified the tests correctly in the test_smartcamdevice.py, added instance checks in the test_cli.py and test_common_modules.py to handle the specifics that the smartcam/modules/time.py only sets the timezone and not the time and modified those tests accordingly and to catch the warning in the test_common_module.py. This looks cleaner to me now as well but let me know if I'm still missing it. |
a1ea53b to
aef9930
Compare
rytilahti
left a comment
There was a problem hiding this comment.
While I was reviewing (see the comments inline) and reading the PR description, I started to give second thoughts on whether we really want to do this, i.e., what's the use case?
If the reason for this is to allow changing the time zone, perhaps we should add an explicit method for doing that & just raise an exception about setting the time is not possible? This way, the interface would keep working on as expected which would keep our tests clean.
What do you think, @ZeliardM?
| assert time_mod.time != test_time | ||
|
|
||
| await time_mod.set_time(test_time) | ||
| caplog.clear() |
There was a problem hiding this comment.
No need to clear() when the log shouldn't have captured anything yet.
| await time_mod.set_time(test_time) | ||
| await dev.update() | ||
| assert time_mod.time == test_time | ||
| if isinstance(dev, SmartCamDevice): |
There was a problem hiding this comment.
It would be nicer to add a separate test for the special "logs a warning" case that to make it explicit, and skip this test for smartcam devices.
This way we can cover both paths without having a need for conditionals in tests which are most of the time a bad idea in tests.
| if isinstance(dev, SmartCamDevice): | ||
| assert time_mod.timezone == new_time.tzinfo | ||
| else: | ||
| assert time_mod.time == new_time |
There was a problem hiding this comment.
See my comment above; it's better to add separate tests for the special cases than adding conditonals with isinstance.
| @staticmethod | ||
| def _format_utc_offset(offset: timedelta | None) -> str: | ||
| """Format a timedelta offset as UTC+HH:MM/UTC-HH:MM.""" | ||
| if offset is None: | ||
| offset = timedelta(0) | ||
|
|
||
| total_seconds = int(offset.total_seconds()) | ||
| sign = "+" if total_seconds >= 0 else "-" | ||
| total_seconds = abs(total_seconds) | ||
| hours, remainder = divmod(total_seconds, 3600) | ||
| minutes = remainder // 60 | ||
| return f"UTC{sign}{hours:02d}:{minutes:02d}" |
There was a problem hiding this comment.
Please add tests for this function to the relevant test_time.py file with a parametrized set of test cases.
| if isinstance(dev, SmartCamDevice): | ||
| assert time_mod.timezone.utcoffset(time_mod.time) == initial_timezone.utcoffset( | ||
| time_mod.time | ||
| ) | ||
| else: | ||
| assert time_mod.time == dt.replace(tzinfo=time_mod.timezone) |
There was a problem hiding this comment.
There should be no need for special handling in the cli tests. Or it should be covered with a separate, explicit test.
|
@rytilahti The reason I kept it all as the same paths as the other device types instead of separating out of an explicit method is because the other device types, IOT and SMART, all handle time and timezone under the same method and interface. SMARTCAM is the only one I have seen that cannot do an explicit set time function. So the modifications were to keep it all the same. Is what you're asking if I think we should separate timezone and time for all device types? Now, given that setting the time is not an option and the timezone should be NTP it really could be irrelevant to have this at all for SMARTCAM and just raise the error. Another thing is for people who do not use the App to set up the camera and set things up from scratch with python-kasa and don't allow the camera out to the internet then at that point NTP wouldn't work to set the time or timezone and they would need the option to at least set the timezone but still setting the time is not available. So if it were me, I would probably just raise the error and move on. Either use case, this doesn't really offer much in the way of functionality that would benefit the user in a meaningful way. |
This will update the SMARTCAM Time module so that manually setting the time will function correctly. An important note is that this turns off NTP time updating.