Skip to content
Open
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: 3 additions & 0 deletions devtools/helpers/smartcamrequests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
{"getLdc": {"image": {"name": ["switch", "common"]}}},
{"getLastAlarmInfo": {"system": {"name": ["last_alarm_info"]}}},
{"getLedStatus": {"led": {"name": ["config"]}}},
{"getFloodlightCapability": {"floodlight": {"name": ["capability"]}}},
{"getFloodlightConfig": {"floodlight": {"name": ["config"]}}},
{"getFloodlightStatus": {"floodlight": {"get_floodlight_status": {}}}},
{"getTargetTrackConfig": {"target_track": {"name": ["target_track_info"]}}},
{"getPresetConfig": {"preset": {"name": ["preset"]}}},
{"getFirmwareUpdateStatus": {"cloud_config": {"name": "upgrade_status"}}},
Expand Down
2 changes: 2 additions & 0 deletions kasa/smartcam/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .childdevice import ChildDevice
from .childsetup import ChildSetup
from .device import DeviceModule
from .floodlight import Floodlight
from .glassdetection import GlassDetection
from .homekit import HomeKit
from .led import Led
Expand All @@ -32,6 +33,7 @@
"ChildDevice",
"ChildSetup",
"DeviceModule",
"Floodlight",
"GlassDetection",
"Led",
"LineCrossingDetection",
Expand Down
119 changes: 119 additions & 0 deletions kasa/smartcam/modules/floodlight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""Module for floodlight controls."""

from __future__ import annotations

from typing import Annotated

from ...exceptions import KasaException
from ...feature import Feature
from ...interfaces.light import LightState
from ...module import FeatureAttribute
from ...smart.smartmodule import allow_update_after
from ..smartcammodule import SmartCamModule


class Floodlight(SmartCamModule):
Copy link
Member

Choose a reason for hiding this comment

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

If you would implement the Light interface (see how smart module does it), it would make interfacing with lights consistent across the different platforms.

"""Implementation of floodlight controls."""

REQUIRED_COMPONENT = "floodlight"

_light_state: LightState

def _initialize_features(self) -> None:
"""Initialize features."""
device = self._device
data = self.data
config = data["getFloodlightConfig"]["floodlight"]["config"]
status = data["getFloodlightStatus"]
capability = data["getFloodlightCapability"]["floodlight"]["capability"]
if (
config is not None
and status is not None
and capability is not None
and "intensity_level" in config
and "intensity_level_max" in capability
and "min_intensity" in capability
):
Comment on lines +29 to +36
Copy link
Member

Choose a reason for hiding this comment

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

Instead of having multiple conditions and parsing data out from dicts, please consider creating a container class and using mashumaro's functionaity for parsing.

self._add_feature(
Feature(
device,
id="brightness",
name="Brightness",
container=self,
attribute_getter="brightness",
attribute_setter="set_brightness",
range_getter=lambda: (
int(capability["min_intensity"]),
int(capability["intensity_level_max"]),
),
type=Feature.Type.Number,
category=Feature.Category.Primary,
)
)
if status is not None:
self._add_feature(
Feature(
self._device,
id="floodlight_state",
name="Floodlight state",
container=self,
attribute_getter="is_on",
attribute_setter="set_state",
type=Feature.Type.Switch,
category=Feature.Category.Primary,
)
)

def query(self) -> dict:
"""Query to execute during the update cycle."""
return {
"getFloodlightConfig": {"floodlight": {"name": "config"}},
"getFloodlightCapability": {"floodlight": {"name": "capability"}},
"getFloodlightStatus": {"floodlight": {"get_floodlight_status": {}}},
}

@property
def is_on(self) -> bool:
"""Return whether the device is on."""
return int(self.data["getFloodlightStatus"]["status"]) == 1

@allow_update_after
async def set_state(self, on: bool) -> Annotated[dict, FeatureAttribute()]:
"""Set whether the floodlight is on."""
params = {
"floodlight": {
"manual_floodlight_op": {"action": "start" if on else "stop"}
}
}
return await self.call("manualFloodlightOp", params)

@property
def brightness(self) -> Annotated[int, FeatureAttribute()]:
"""Return the current brightness."""
data = self.data
if not self._device.modules["Floodlight"].has_feature(
"brightness"
): # pragma: no cover
raise KasaException("Floodlight is not dimmable.")
return int(
data["getFloodlightConfig"]["floodlight"]["config"]["intensity_level"]
)

@allow_update_after
async def set_brightness(
self, brightness: int, *, transition: int | None = None
) -> Annotated[dict, FeatureAttribute()]:
"""Set the brightness in percentage.

Note, transition is not supported and will be ignored.

:param int brightness: brightness in percent
:param int transition: transition in milliseconds.
"""
if not self._device.modules["Floodlight"].has_feature(
"brightness"
): # pragma: no cover
raise KasaException("Floodlight is not dimmable.")

params = {"floodlight": {"config": {"intensity_level": str(brightness)}}}
return await self.call("setFloodlightConfig", params)
2 changes: 2 additions & 0 deletions kasa/smartcam/smartcammodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class SmartCamModule(SmartModule):
"devicemodule"
)

SmartCamFloodlight: Final[ModuleName[modules.Floodlight]] = ModuleName("floodlight")

#: Module name to be queried
QUERY_MODULE_NAME: str
#: Section name or names to be queried
Expand Down
Loading