Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ The following devices have been tested and confirmed as working. If your device
- **Wall Switches**: S500D, S505, S505D
- **Bulbs**: L510B, L510E, L530E, L630
- **Light Strips**: L900-10, L900-5, L920-5, L930-5
- **Hubs**: H100
- **Cameras**: C210, TC65
- **Hubs**: H100, H200
- **Hub-Connected Devices<sup>\*\*\*</sup>**: S200B, S200D, T100, T110, T300, T310, T315

<!--SUPPORTED_END-->
Expand Down
11 changes: 11 additions & 0 deletions SUPPORTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,23 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
- **L930-5**
- Hardware: 1.0 (US) / Firmware: 1.1.2

### Cameras

- **C210**
- Hardware: 2.0 (EU) / Firmware: 1.4.2
- Hardware: 2.0 (EU) / Firmware: 1.4.3
- **TC65**
- Hardware: 1.0 / Firmware: 1.3.9

### Hubs

- **H100**
- Hardware: 1.0 (EU) / Firmware: 1.2.3
- Hardware: 1.0 (EU) / Firmware: 1.5.10
- Hardware: 1.0 (EU) / Firmware: 1.5.5
- **H200**
- Hardware: 1.0 (EU) / Firmware: 1.3.2
- Hardware: 1.0 (US) / Firmware: 1.3.6

### Hub-Connected Devices

Expand Down
15 changes: 7 additions & 8 deletions devtools/dump_devinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)
from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from kasa.smart import SmartChildDevice
from kasa.smartcamera import SmartCamera

Call = namedtuple("Call", "module method")
FixtureResult = namedtuple("FixtureResult", "filename, folder, data")
Expand Down Expand Up @@ -973,14 +974,12 @@ async def get_smart_fixtures(
copy_folder = SMART_FOLDER
else:
# smart camera protocol
basic_info = final["getDeviceInfo"]["device_info"]["basic_info"]
hw_version = basic_info["hw_version"]
sw_version = basic_info["sw_version"]
model = basic_info["device_model"]
region = basic_info.get("region")
sw_version = sw_version.split(" ", maxsplit=1)[0]
if region is not None:
model = f"{model}({region})"
model_info = SmartCamera._get_device_info(final, discovery_info)
model = model_info.long_name
hw_version = model_info.hardware_version
sw_version = model_info.firmare_version
if model_info.region is not None:
model = f"{model}({model_info.region})"
copy_folder = SMARTCAMERA_FOLDER

save_filename = f"{model}_{hw_version}_{sw_version}.json"
Expand Down
29 changes: 28 additions & 1 deletion devtools/generate_supported.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

from kasa.device_factory import _get_device_type_from_sys_info
from kasa.device_type import DeviceType
from kasa.smart.smartdevice import SmartDevice
from kasa.smart import SmartDevice
from kasa.smartcamera import SmartCamera


class SupportedVersion(NamedTuple):
Expand All @@ -32,6 +33,7 @@ class SupportedVersion(NamedTuple):
DeviceType.Fan: "Wall Switches",
DeviceType.Bulb: "Bulbs",
DeviceType.LightStrip: "Light Strips",
DeviceType.Camera: "Cameras",
DeviceType.Hub: "Hubs",
DeviceType.Sensor: "Hub-Connected Devices",
DeviceType.Thermostat: "Hub-Connected Devices",
Expand All @@ -43,6 +45,7 @@ class SupportedVersion(NamedTuple):

IOT_FOLDER = "tests/fixtures/"
SMART_FOLDER = "tests/fixtures/smart/"
SMARTCAMERA_FOLDER = "tests/fixtures/smartcamera/"


def generate_supported(args):
Expand All @@ -58,6 +61,7 @@ def generate_supported(args):

_get_iot_supported(supported)
_get_smart_supported(supported)
_get_smartcamera_supported(supported)

readme_updated = _update_supported_file(
README_FILENAME, _supported_summary(supported), print_diffs
Expand Down Expand Up @@ -234,6 +238,29 @@ def _get_smart_supported(supported):
)


def _get_smartcamera_supported(supported):
for file in Path(SMARTCAMERA_FOLDER).glob("**/*.json"):
with file.open() as f:
fixture_data = json.load(f)

model_info = SmartCamera._get_device_info(
fixture_data, fixture_data.get("discovery_result")
)

supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[model_info.device_type]

stype = supported[model_info.brand].setdefault(supported_type, {})
smodel = stype.setdefault(model_info.long_name, [])
smodel.append(
SupportedVersion(
region=model_info.region,
hw=model_info.hardware_version,
fw=model_info.firmare_version,
auth=model_info.requires_auth,
)
)


def _get_iot_supported(supported):
for file in Path(IOT_FOLDER).glob("*.json"):
with file.open() as f:
Expand Down
16 changes: 16 additions & 0 deletions kasa/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,22 @@ class WifiNetwork:
_LOGGER = logging.getLogger(__name__)


@dataclass
class _DeviceInfo:
"""Device Model Information."""

short_name: str
long_name: str
brand: str
device_family: str
device_type: DeviceType
hardware_version: str
firmare_version: str
firmware_build: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we want to go that detailed here?

How do you envison short_name/long_name/brand to be used for non-smartcameras?

Copy link
Collaborator Author

@sdb9696 sdb9696 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's the smart devices I'm thinking of with short_name long_name. The model in the device info is shorter than in the discovery info, e.g. L530 vs L530E.

In terms of the detail I'd like to replace the duplicated device specific logic that is sitting in dump_devinfo and generate_supported with this same implementation in iot and smart devices. The detail does not need to be exposed as part of the public api, that's up for discussion and I don't feel strongly.

N.B. The firmware_build is included so that when this is a property on the devices (public or private) the existing fw_version property that includes the build can get it from the DeviceInfo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me 👍

requires_auth: bool
region: str | None


class Device(ABC):
"""Common device interface.

Expand Down
25 changes: 25 additions & 0 deletions kasa/smartcamera/smartcamera.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
from typing import Any

from ..device import _DeviceInfo
from ..device_type import DeviceType
from ..module import Module
from ..protocols.smartcameraprotocol import _ChildCameraProtocolWrapper
Expand All @@ -29,6 +30,30 @@
return DeviceType.Hub
return DeviceType.Camera

@staticmethod
def _get_device_info(
info: dict[str, Any], discovery_info: dict[str, Any] | None
) -> _DeviceInfo:
"""Get model information for a device."""
basic_info = info["getDeviceInfo"]["device_info"]["basic_info"]
short_name = basic_info["device_model"]
long_name = discovery_info["device_model"] if discovery_info else short_name
device_type = SmartCamera._get_device_type_from_sysinfo(basic_info)
fw_version_full = basic_info["sw_version"]
firmare_version, firmware_build = fw_version_full.split(" ", maxsplit=1)
return _DeviceInfo(

Check warning on line 44 in kasa/smartcamera/smartcamera.py

View check run for this annotation

Codecov / codecov/patch

kasa/smartcamera/smartcamera.py#L38-L44

Added lines #L38 - L44 were not covered by tests
short_name=basic_info["device_model"],
long_name=long_name,
brand="tapo",
device_family=basic_info["device_type"],
device_type=device_type,
hardware_version=basic_info["hw_version"],
firmare_version=firmare_version,
firmware_build=firmware_build,
requires_auth=True,
region=basic_info.get("region"),
)

def _update_internal_info(self, info_resp: dict) -> None:
"""Update the internal device info."""
info = self._try_get_response(info_resp, "getDeviceInfo")
Expand Down
Loading