2024-07-23 18:13:52 +00:00
|
|
|
"""Module for cli light control commands."""
|
|
|
|
|
|
|
|
import asyncclick as click
|
|
|
|
|
|
|
|
from kasa import (
|
|
|
|
Device,
|
|
|
|
Module,
|
|
|
|
)
|
|
|
|
from kasa.iot import (
|
|
|
|
IotBulb,
|
|
|
|
)
|
|
|
|
|
|
|
|
from .common import echo, error, pass_dev_or_child
|
|
|
|
|
|
|
|
|
|
|
|
@click.group()
|
|
|
|
@pass_dev_or_child
|
2024-11-10 18:55:13 +00:00
|
|
|
def light(dev) -> None:
|
2024-07-23 18:13:52 +00:00
|
|
|
"""Commands to control light settings."""
|
|
|
|
|
|
|
|
|
|
|
|
@light.command()
|
|
|
|
@click.argument("brightness", type=click.IntRange(0, 100), default=None, required=False)
|
|
|
|
@click.option("--transition", type=int, required=False)
|
|
|
|
@pass_dev_or_child
|
|
|
|
async def brightness(dev: Device, brightness: int, transition: int):
|
|
|
|
"""Get or set brightness."""
|
2024-12-11 15:53:35 +00:00
|
|
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature(
|
|
|
|
"brightness"
|
|
|
|
):
|
2024-07-23 18:13:52 +00:00
|
|
|
error("This device does not support brightness.")
|
|
|
|
return
|
|
|
|
|
|
|
|
if brightness is None:
|
|
|
|
echo(f"Brightness: {light.brightness}")
|
|
|
|
return light.brightness
|
|
|
|
else:
|
|
|
|
echo(f"Setting brightness to {brightness}")
|
|
|
|
return await light.set_brightness(brightness, transition=transition)
|
|
|
|
|
|
|
|
|
|
|
|
@light.command()
|
|
|
|
@click.argument(
|
|
|
|
"temperature", type=click.IntRange(2500, 9000), default=None, required=False
|
|
|
|
)
|
|
|
|
@click.option("--transition", type=int, required=False)
|
|
|
|
@pass_dev_or_child
|
|
|
|
async def temperature(dev: Device, temperature: int, transition: int):
|
|
|
|
"""Get or set color temperature."""
|
2024-12-11 15:53:35 +00:00
|
|
|
if not (light := dev.modules.get(Module.Light)) or not (
|
|
|
|
color_temp_feat := light.get_feature("color_temp")
|
|
|
|
):
|
2024-07-23 18:13:52 +00:00
|
|
|
error("Device does not support color temperature")
|
|
|
|
return
|
|
|
|
|
|
|
|
if temperature is None:
|
|
|
|
echo(f"Color temperature: {light.color_temp}")
|
2024-12-11 15:53:35 +00:00
|
|
|
valid_temperature_range = color_temp_feat.range
|
2024-07-23 18:13:52 +00:00
|
|
|
if valid_temperature_range != (0, 0):
|
|
|
|
echo("(min: {}, max: {})".format(*valid_temperature_range))
|
|
|
|
else:
|
|
|
|
echo(
|
|
|
|
"Temperature range unknown, please open a github issue"
|
|
|
|
f" or a pull request for model '{dev.model}'"
|
|
|
|
)
|
2024-12-11 15:53:35 +00:00
|
|
|
return color_temp_feat.range
|
2024-07-23 18:13:52 +00:00
|
|
|
else:
|
|
|
|
echo(f"Setting color temperature to {temperature}")
|
|
|
|
return await light.set_color_temp(temperature, transition=transition)
|
|
|
|
|
|
|
|
|
|
|
|
@light.command()
|
|
|
|
@click.argument("effect", type=click.STRING, default=None, required=False)
|
|
|
|
@click.pass_context
|
|
|
|
@pass_dev_or_child
|
|
|
|
async def effect(dev: Device, ctx, effect):
|
|
|
|
"""Set an effect."""
|
|
|
|
if not (light_effect := dev.modules.get(Module.LightEffect)):
|
|
|
|
error("Device does not support effects")
|
|
|
|
return
|
|
|
|
if effect is None:
|
|
|
|
echo(
|
|
|
|
f"Light effect: {light_effect.effect}\n"
|
|
|
|
+ f"Available Effects: {light_effect.effect_list}"
|
|
|
|
)
|
|
|
|
return light_effect.effect
|
|
|
|
|
|
|
|
if effect not in light_effect.effect_list:
|
|
|
|
raise click.BadArgumentUsage(
|
|
|
|
f"Effect must be one of: {light_effect.effect_list}", ctx
|
|
|
|
)
|
|
|
|
|
|
|
|
echo(f"Setting Effect: {effect}")
|
|
|
|
return await light_effect.set_effect(effect)
|
|
|
|
|
|
|
|
|
|
|
|
@light.command()
|
|
|
|
@click.argument("h", type=click.IntRange(0, 360), default=None, required=False)
|
|
|
|
@click.argument("s", type=click.IntRange(0, 100), default=None, required=False)
|
|
|
|
@click.argument("v", type=click.IntRange(0, 100), default=None, required=False)
|
|
|
|
@click.option("--transition", type=int, required=False)
|
|
|
|
@click.pass_context
|
|
|
|
@pass_dev_or_child
|
|
|
|
async def hsv(dev: Device, ctx, h, s, v, transition):
|
|
|
|
"""Get or set color in HSV."""
|
2024-12-11 15:53:35 +00:00
|
|
|
if not (light := dev.modules.get(Module.Light)) or not light.has_feature("hsv"):
|
2024-07-23 18:13:52 +00:00
|
|
|
error("Device does not support colors")
|
|
|
|
return
|
|
|
|
|
|
|
|
if h is None and s is None and v is None:
|
|
|
|
echo(f"Current HSV: {light.hsv}")
|
|
|
|
return light.hsv
|
|
|
|
elif s is None or v is None:
|
|
|
|
raise click.BadArgumentUsage("Setting a color requires 3 values.", ctx)
|
|
|
|
else:
|
|
|
|
echo(f"Setting HSV: {h} {s} {v}")
|
|
|
|
return await light.set_hsv(h, s, v, transition=transition)
|
|
|
|
|
|
|
|
|
|
|
|
@light.group(invoke_without_command=True)
|
|
|
|
@pass_dev_or_child
|
|
|
|
@click.pass_context
|
|
|
|
async def presets(ctx, dev):
|
|
|
|
"""List and modify bulb setting presets."""
|
|
|
|
if ctx.invoked_subcommand is None:
|
|
|
|
return await ctx.invoke(presets_list)
|
|
|
|
|
|
|
|
|
|
|
|
@presets.command(name="list")
|
|
|
|
@pass_dev_or_child
|
|
|
|
def presets_list(dev: Device):
|
|
|
|
"""List presets."""
|
|
|
|
if not (light_preset := dev.modules.get(Module.LightPreset)):
|
2024-11-21 18:10:18 +00:00
|
|
|
error("Device does not support light presets")
|
2024-07-23 18:13:52 +00:00
|
|
|
return
|
|
|
|
|
2024-11-09 17:03:06 +00:00
|
|
|
for idx, preset in enumerate(light_preset.preset_states_list):
|
|
|
|
echo(
|
2024-11-21 18:10:18 +00:00
|
|
|
f"[{idx}] Hue: {preset.hue or '':3} "
|
|
|
|
f"Saturation: {preset.saturation or '':3} "
|
|
|
|
f"Brightness/Value: {preset.brightness or '':3} "
|
|
|
|
f"Temp: {preset.color_temp or '':4}"
|
2024-11-09 17:03:06 +00:00
|
|
|
)
|
2024-07-23 18:13:52 +00:00
|
|
|
|
|
|
|
return light_preset.preset_states_list
|
|
|
|
|
|
|
|
|
|
|
|
@presets.command(name="modify")
|
|
|
|
@click.argument("index", type=int)
|
2024-11-21 18:10:18 +00:00
|
|
|
@click.option("--brightness", type=int, required=False, default=None)
|
|
|
|
@click.option("--hue", type=int, required=False, default=None)
|
|
|
|
@click.option("--saturation", type=int, required=False, default=None)
|
|
|
|
@click.option("--temperature", type=int, required=False, default=None)
|
2024-07-23 18:13:52 +00:00
|
|
|
@pass_dev_or_child
|
|
|
|
async def presets_modify(dev: Device, index, brightness, hue, saturation, temperature):
|
|
|
|
"""Modify a preset."""
|
2024-11-21 18:10:18 +00:00
|
|
|
if not (light_preset := dev.modules.get(Module.LightPreset)):
|
|
|
|
error("Device does not support light presets")
|
|
|
|
return
|
|
|
|
|
|
|
|
max_index = len(light_preset.preset_states_list) - 1
|
|
|
|
if index > len(light_preset.preset_states_list) - 1:
|
|
|
|
error(f"Invalid index, must be between 0 and {max_index}")
|
2024-07-23 18:13:52 +00:00
|
|
|
return
|
|
|
|
|
2024-11-21 18:10:18 +00:00
|
|
|
if all([val is None for val in {brightness, hue, saturation, temperature}]):
|
|
|
|
error("Need to supply at least one option to modify.")
|
|
|
|
return
|
|
|
|
|
|
|
|
# Preset names have `Not set`` as the first value
|
|
|
|
preset_name = light_preset.preset_list[index + 1]
|
|
|
|
preset = light_preset.preset_states_list[index]
|
|
|
|
|
|
|
|
echo(f"Preset {preset_name} currently: {preset}")
|
|
|
|
|
|
|
|
if brightness is not None and preset.brightness is not None:
|
2024-07-23 18:13:52 +00:00
|
|
|
preset.brightness = brightness
|
2024-11-21 18:10:18 +00:00
|
|
|
if hue is not None and preset.hue is not None:
|
2024-07-23 18:13:52 +00:00
|
|
|
preset.hue = hue
|
2024-11-21 18:10:18 +00:00
|
|
|
if saturation is not None and preset.saturation is not None:
|
2024-07-23 18:13:52 +00:00
|
|
|
preset.saturation = saturation
|
2024-11-21 18:10:18 +00:00
|
|
|
if temperature is not None and preset.temperature is not None:
|
2024-07-23 18:13:52 +00:00
|
|
|
preset.color_temp = temperature
|
|
|
|
|
2024-11-21 18:10:18 +00:00
|
|
|
echo(f"Updating preset {preset_name} to: {preset}")
|
2024-07-23 18:13:52 +00:00
|
|
|
|
2024-11-21 18:10:18 +00:00
|
|
|
return await light_preset.save_preset(preset_name, preset)
|
2024-07-23 18:13:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
@light.command()
|
|
|
|
@pass_dev_or_child
|
|
|
|
@click.option("--type", type=click.Choice(["soft", "hard"], case_sensitive=False))
|
|
|
|
@click.option("--last", is_flag=True)
|
|
|
|
@click.option("--preset", type=int)
|
|
|
|
async def turn_on_behavior(dev: Device, type, last, preset):
|
|
|
|
"""Modify bulb turn-on behavior."""
|
2024-11-21 18:40:13 +00:00
|
|
|
if dev.device_type is not Device.Type.Bulb or not isinstance(dev, IotBulb):
|
2024-07-23 18:13:52 +00:00
|
|
|
error("Presets only supported on iot bulbs")
|
|
|
|
return
|
|
|
|
settings = await dev.get_turn_on_behavior()
|
|
|
|
echo(f"Current turn on behavior: {settings}")
|
|
|
|
|
|
|
|
# Return if we are not setting the value
|
|
|
|
if not type and not last and not preset:
|
|
|
|
return settings
|
|
|
|
|
|
|
|
# If we are setting the value, the type has to be specified
|
|
|
|
if (last or preset) and type is None:
|
|
|
|
echo("To set the behavior, you need to define --type")
|
|
|
|
return
|
|
|
|
|
|
|
|
behavior = getattr(settings, type)
|
|
|
|
|
|
|
|
if last:
|
|
|
|
echo(f"Going to set {type} to last")
|
|
|
|
behavior.preset = None
|
|
|
|
elif preset is not None:
|
|
|
|
echo(f"Going to set {type} to preset {preset}")
|
|
|
|
behavior.preset = preset
|
|
|
|
|
|
|
|
return await dev.set_turn_on_behavior(settings)
|