Return on_since only when its available and the device is on (#48)

* moves on_since property to smartdevice class, as it is not plug only
* returns None if the value is not available (some bulbs), or if the device is off
This commit is contained in:
Teemu R 2020-04-24 16:47:57 +02:00 committed by GitHub
parent fd560442a2
commit 0c71957aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 26 deletions

View File

@ -15,7 +15,7 @@ import functools
import inspect import inspect
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
@ -554,6 +554,23 @@ class SmartDevice:
""" """
raise NotImplementedError("Device subclass needs to implement this.") raise NotImplementedError("Device subclass needs to implement this.")
@property # type: ignore
@requires_update
def on_since(self) -> Optional[datetime]:
"""Return pretty-printed on-time, if available.
Returns None if the device is turned off or does not report it.
"""
if "on_time" not in self.sys_info:
return None
if self.is_off:
return None
on_time = self.sys_info["on_time"]
return datetime.now() - timedelta(seconds=on_time)
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def state_information(self) -> Dict[str, Any]: def state_information(self) -> Dict[str, Any]:

View File

@ -1,5 +1,4 @@
"""Module for plugs.""" """Module for plugs."""
import datetime
import logging import logging
from typing import Any, Dict from typing import Any, Dict
@ -81,18 +80,6 @@ class SmartPlug(SmartDevice):
await self._query_helper("system", "set_led_off", {"off": int(not state)}) await self._query_helper("system", "set_led_off", {"off": int(not state)})
await self.update() await self.update()
@property # type: ignore
@requires_update
def on_since(self) -> datetime.datetime:
"""Return pretty-printed on-time.
:return: datetime for on since
:rtype: datetime
"""
on_time = self.sys_info["on_time"]
return datetime.datetime.now() - datetime.timedelta(seconds=on_time)
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def state_information(self) -> Dict[str, Any]: def state_information(self) -> Dict[str, Any]:

View File

@ -2,9 +2,9 @@
.. todo:: describe how this interfaces with single plugs. .. todo:: describe how this interfaces with single plugs.
""" """
import datetime
import logging import logging
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timedelta
from typing import Any, DefaultDict, Dict, List, Optional from typing import Any, DefaultDict, Dict, List, Optional
from kasa.smartdevice import ( from kasa.smartdevice import (
@ -111,9 +111,12 @@ class SmartStrip(SmartDevice):
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def on_since(self) -> datetime.datetime: def on_since(self) -> Optional[datetime]:
"""Return the maximum on-time of all outlets.""" """Return the maximum on-time of all outlets."""
return max(plug.on_since for plug in self.plugs) if self.is_off:
return None
return max(plug.on_since for plug in self.plugs if plug.on_since is not None)
@property # type: ignore @property # type: ignore
@requires_update @requires_update
@ -317,16 +320,19 @@ class SmartStripPlug(SmartPlug):
@property # type: ignore @property # type: ignore
@requires_update @requires_update
def on_since(self) -> datetime.datetime: def on_since(self) -> Optional[datetime]:
"""Return pretty-printed on-time. """Return pretty-printed on-time.
:return: datetime for on since :return: datetime for on since
:rtype: datetime :rtype: datetime
""" """
if self.is_off:
return None
info = self._get_child_info() info = self._get_child_info()
on_time = info["on_time"] on_time = info["on_time"]
return datetime.datetime.now() - datetime.timedelta(seconds=on_time) return datetime.now() - timedelta(seconds=on_time)
@property # type: ignore @property # type: ignore
@requires_update @requires_update

View File

@ -98,7 +98,6 @@ def dev(request):
if ip: if ip:
d = asyncio.run(Discover.discover_single(ip)) d = asyncio.run(Discover.discover_single(ip))
asyncio.run(d.update()) asyncio.run(d.update())
print(d.model)
if d.model in file: if d.model in file:
return d return d
return return

View File

@ -1,5 +1,5 @@
import asyncio import asyncio
import datetime from datetime import datetime
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
@ -230,9 +230,16 @@ async def test_led(dev):
await dev.set_led(original) await dev.set_led(original)
@plug @turn_on
async def test_on_since(dev): async def test_on_since(dev, turn_on):
assert isinstance(dev.on_since, datetime.datetime) await handle_turn_on(dev, turn_on)
orig_state = dev.is_on
if "on_time" not in dev.sys_info and not dev.is_strip:
assert dev.on_since is None
elif orig_state:
assert isinstance(dev.on_since, datetime)
else:
assert dev.on_since is None
async def test_icon(dev): async def test_icon(dev):
@ -240,7 +247,7 @@ async def test_icon(dev):
async def test_time(dev): async def test_time(dev):
assert isinstance(await dev.get_time(), datetime.datetime) assert isinstance(await dev.get_time(), datetime)
# TODO check setting? # TODO check setting?
@ -421,8 +428,20 @@ async def test_children_alias(dev):
@strip @strip
async def test_children_on_since(dev): async def test_children_on_since(dev):
on_sinces = []
for plug in dev.plugs: for plug in dev.plugs:
assert plug.on_since if plug.is_on:
on_sinces.append(plug.on_since)
assert isinstance(plug.on_since, datetime)
else:
assert plug.on_since is None
if dev.is_off:
assert dev.on_since is None
# TODO: testing this would require some mocking utcnow which is not
# very straightforward.
# else:
# assert dev.on_since == max(on_sinces)
@strip @strip