Skip to content

Commit

Permalink
Working on music scenes
Browse files Browse the repository at this point in the history
  • Loading branch information
Galorhallen committed Oct 20, 2024
1 parent 20012d1 commit c813500
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ repos:
rev: "v1.12.0"
hooks:
- id: mypy
exclude: ^examples/
exclude: ^example/
11 changes: 5 additions & 6 deletions example/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ async def print_status(controller: GoveeController, device: GoveeDevice):
while True:
if not device or not device.capabilities:
continue
for seg in range(1, 1 + device.capabilities.segments_count):
await device.set_segment_color(seg, (255, 0, 0))
await asyncio.sleep(0.1)
for seg in range(1, 1 + device.capabilities.segments_count):
await device.set_segment_color(seg, (0, 0, 255))
await asyncio.sleep(0.1)
for music in device.capabilities.available_musics:
print(f"Music: {music}")
await device.set_music(music)
await asyncio.sleep(10)

await asyncio.sleep(1)


Expand Down
11 changes: 8 additions & 3 deletions src/govee_local_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from .controller import GoveeController
from .device import GoveeDevice
from .light_capabilities import GoveeLightFeatures
from .light_capabilities import GoveeLightFeatures, GoveeLightCapabilities


__all__ = ["GoveeController", "GoveeDevice", "GoveeLightFeatures"]
__all__ = [
"GoveeController",
"GoveeDevice",
"GoveeLightFeatures",
"GoveeLightCapabilities",
]

__version__ = "1.5.4"
__version__ = "2.0.0a0"
41 changes: 37 additions & 4 deletions src/govee_local_api/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from .message import (
BrightnessMessage,
ColorMessage,
SceneMessages,
MusicMessage,
GoveeMessage,
MessageResponseFactory,
OnOffMessage,
Expand Down Expand Up @@ -215,14 +217,14 @@ def send_update_message(self, device: GoveeDevice | None = None) -> None:
async def turn_on_off(self, device: GoveeDevice, status: bool) -> None:
self._send_message(OnOffMessage(status), device)

async def set_segment_color(
async def set_segment_rgb_color(
self, device: GoveeDevice, segment: int, rgb: tuple[int, int, int]
) -> None:
if not device.capabilities:
self._logger.warning("Capabilities not available for device %s", device)
return

if GoveeLightFeatures.SEGMENT_CONTROL not in device.capabilities.features:
if device.capabilities.features & GoveeLightFeatures.SEGMENT_CONTROL == 0:
self._logger.warning(
"Segment control is not supported by device %s", device
)
Expand All @@ -244,6 +246,38 @@ async def set_segment_color(
print(f"Sending message {message} to device {device}")
self._send_message(message, device)

async def set_scene(self, device: GoveeDevice, scene: str) -> None:
if (
not device.capabilities
or device.capabilities.features & GoveeLightFeatures.SCENES == 0
):
self._logger.warning("Scenes are not supported by device %s", device)
return

scene_code: bytes | None = device.capabilities.scenes.get(scene.lower(), None)
if not scene_code:
self._logger.warning(
"Scene %s is not available for device %s", scene, device
)
return
self._send_message(SceneMessages(scene_code), device)

async def set_music(self, device: GoveeDevice, music: str) -> None:
if (
not device.capabilities
or device.capabilities.features & GoveeLightFeatures.MUSIC == 0
):
self._logger.warning("Music is not supported by device %s", device)
return

music_code: bytes | None = device.capabilities.musics.get(music.lower(), None)
if not music_code:
self._logger.warning(
"Music %s is not available for device %s", music, device
)
return
self._send_message(MusicMessage(music_code), device)

async def set_brightness(self, device: GoveeDevice, brightness: int) -> None:
self._send_message(BrightnessMessage(brightness), device)

Expand Down Expand Up @@ -343,8 +377,7 @@ def _send_update_message(self, device: GoveeDevice):
def _handle_status_update_response(self, message: StatusResponse, addr):
print("Status update received from %s: %s", addr, message)
ip = addr[0]
device = self.get_device_by_ip(ip)
if device:
if device := self.get_device_by_ip(ip):
device.update(message)

async def _handle_scan_response(self, message: ScanResponse) -> None:
Expand Down
26 changes: 19 additions & 7 deletions src/govee_local_api/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ def brightness(self) -> int:
def temperature_color(self) -> int:
return self._temperature_color

def get_segment_color(self, segment: int) -> tuple[int, int, int]:
return self._segments_color[segment - 1]

@property
def segments_color(self) -> list[tuple[int, int, int]]:
return self._segments_color
Expand All @@ -95,11 +92,15 @@ async def turn_on(self) -> None:
await self._controller.turn_on_off(self, True)
self._is_on = True

async def set_segment_color(
self, segment: int, color: tuple[int, int, int]
async def set_segment_rgb_color(
self, segment: int, red: int, green: int, blue: int
) -> None:
self._segments_color[segment - 1] = color
await self._controller.set_segment_color(self, segment, color)
rgb: tuple[int, int, int] = (red, green, blue)
self._segments_color[segment - 1] = rgb
await self._controller.set_segment_rgb_color(self, segment, rgb)

def get_segment_rgb_color(self, segment: int) -> tuple[int, int, int]:
return self._segments_color[segment - 1]

async def turn_off(self) -> None:
await self._controller.turn_on_off(self, False)
Expand All @@ -113,11 +114,18 @@ async def set_rgb_color(self, red: int, green: int, blue: int) -> None:
rgb = (red, green, blue)
await self._controller.set_color(self, rgb=rgb, temperature=None)
self._rgb_color = rgb
self._set_segments_color(rgb)

async def set_temperature(self, temperature: int) -> None:
await self._controller.set_color(self, temperature=temperature, rgb=None)
self._temperature_color = temperature

async def set_scene(self, scene: str) -> None:
await self._controller.set_scene(self, scene)

async def set_music(self, music: str) -> None:
await self._controller.set_music(self, music)

def update(self, message: StatusResponse) -> None:
self._is_on = message.is_on
self._brightness = message.brightness
Expand All @@ -142,6 +150,10 @@ def as_dict(self) -> dict[str, Any]:
"colorTemperature": self._temperature_color,
}

def _set_segments_color(self, color: tuple[int, int, int]) -> None:
for i in range(len(self._segments_color)):
self._segments_color[i] = color

def __str__(self) -> str:
result = f"<GoveeDevice ip={self.ip}, fingerprint={self.fingerprint}, sku={self.sku}, lastseen={self._lastseen}, is_on={self._is_on}"
return result + (
Expand Down
155 changes: 101 additions & 54 deletions src/govee_local_api/light_capabilities.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
from enum import Enum, auto
from enum import IntFlag


class GoveeLightFeatures(Enum):
class GoveeLightFeatures(IntFlag):
"""Govee Lights capabilities."""

COLOR_RGB = auto()
COLOR_KELVIN_TEMPERATURE = auto()
BRIGHTNESS = auto()
SEGMENT_CONTROL = auto()
COLOR_RGB = 0x01
COLOR_KELVIN_TEMPERATURE = 0x02
BRIGHTNESS = 0x04
SEGMENT_CONTROL = 0x08
SCENES = 0x10
MUSIC = 0x20


class GoveeLightColorMode(Enum):
"""Govee Lights color mode."""

MANUAL = auto()
MUSIC = auto()
SCENE = auto()


COMMON_FEATURES = {
GoveeLightFeatures.COLOR_RGB,
GoveeLightFeatures.COLOR_KELVIN_TEMPERATURE,
GoveeLightFeatures.BRIGHTNESS,
}

# Devices with only brightness as a feature
BRIGHTNESS_ONLY = {GoveeLightFeatures.BRIGHTNESS}
COMMON_FEATURES: GoveeLightFeatures = (
GoveeLightFeatures.COLOR_RGB
| GoveeLightFeatures.COLOR_KELVIN_TEMPERATURE
| GoveeLightFeatures.BRIGHTNESS
)


class GoveeLightCapabilities:
def __init__(
self,
features: set[GoveeLightFeatures],
features: GoveeLightFeatures,
segments: list[bytes] = [],
scenes: dict[str, str] = {},
scenes: dict[str, bytes] = {},
musics: dict[str, bytes] = {},
) -> None:
self.features = features
self.segments = segments
self.scenes = scenes
self.segments = (
segments if features & GoveeLightFeatures.SEGMENT_CONTROL else []
)
self.scenes = scenes if features & GoveeLightFeatures.SCENES else {}
self.musics = musics if features & GoveeLightFeatures.MUSIC else {}

@property
def segments_count(self) -> int:
return len(self.segments)

@property
def available_scenes(self) -> list[str]:
return list(self.scenes.keys())

@property
def available_musics(self) -> list[str]:
return list(self.musics.keys())

def __repr__(self) -> str:
return f"GoveeLightCapabilities(features={self.features!r}, segments={self.segments!r}, scenes={self.scenes!r})"

Expand All @@ -68,25 +71,69 @@ def __str__(self) -> str:
b"\x00\x40", # 15
]

SCENE_CODES: dict[int, bytes] = {
SCENE_CODES: dict[str, bytes] = {
"sunrise": b"\x00",
"sunset": b"\x01",
"movie": b"\x04",
"dating": b"\x05",
"romantic": b"\x07",
"twinkle": b"\x08",
"candlelight": b"\x09",
"snowflake": b"\x0f",
"energetic": b"\x10",
"breathe": b"\x0a",
"crossing": b"\x14",
"rainbow": b"\x15",
}

MUSIC_MODES: dict[str, bytes] = {
"energetic": b"\x00",
"rhythm": b"\x03",
}

def create_with_capabilities(rgb: bool, kelvin: bool, brightness: bool, segments: int, scenes: bool) -> GoveeLightCapabilities:
features = set()

def create_with_capabilities(
rgb: bool,
temperature: bool,
brightness: bool,
segments: int,
scenes: bool,
music: bool,
) -> GoveeLightCapabilities:
features: GoveeLightFeatures = GoveeLightFeatures(0)
segments_codes = []

if rgb:
features.add(GoveeLightFeatures.COLOR_RGB)
if kelvin:
features.add(GoveeLightFeatures.COLOR_KELVIN_TEMPERATURE)
features = features | GoveeLightFeatures.COLOR_RGB
if temperature:
features = features | GoveeLightFeatures.COLOR_KELVIN_TEMPERATURE
if brightness:
features.add(GoveeLightFeatures.BRIGHTNESS)
features = features | GoveeLightFeatures.BRIGHTNESS
if segments > 0:
features.add(GoveeLightFeatures.SEGMENT_CONTROL)
segments_codes = SEGMENT_CODES[:segments]
return GoveeLightCapabilities(features, segments_codes, {})

COMMON_CAPABILITIES = create_with_capabilities(True, True, True, 0, True)
features = features | GoveeLightFeatures.SEGMENT_CONTROL
segments_codes = SEGMENT_CODES[:segments]

if scenes:
features = features | GoveeLightFeatures.SCENES
if music:
features = features | GoveeLightFeatures.MUSIC

return GoveeLightCapabilities(
features,
segments_codes,
SCENE_CODES if scenes else {},
MUSIC_MODES if music else {},
)


COMMON_CAPABILITIES = create_with_capabilities(
rgb=True,
temperature=True,
brightness=True,
segments=0,
scenes=True,
music=True,
)

GOVEE_LIGHT_CAPABILITIES: dict[str, GoveeLightCapabilities] = {
# Models with common features
Expand All @@ -110,24 +157,24 @@ def create_with_capabilities(rgb: bool, kelvin: bool, brightness: bool, segments
"H6110": COMMON_CAPABILITIES,
"H6117": COMMON_CAPABILITIES,
"H6159": COMMON_CAPABILITIES,
"H615A": create_with_capabilities(True, True, True, 0, True),
"H615B": create_with_capabilities(True, True, True, 0, True),
"H615C": create_with_capabilities(True, True, True, 0, True),
"H615D": create_with_capabilities(True, True, True, 0, True),
"H615E": create_with_capabilities(True, True, True, 0, True),
"H615A": create_with_capabilities(True, True, True, 0, True, True),
"H615B": create_with_capabilities(True, True, True, 0, True, True),
"H615C": create_with_capabilities(True, True, True, 0, True, True),
"H615D": create_with_capabilities(True, True, True, 0, True, True),
"H615E": create_with_capabilities(True, True, True, 0, True, True),
"H6163": COMMON_CAPABILITIES,
"H6168": COMMON_CAPABILITIES,
"H6172": COMMON_CAPABILITIES,
"H6173": COMMON_CAPABILITIES,
"H618A": create_with_capabilities(True, True, True, 15, True),
"H618C": create_with_capabilities(True, True, True, 15, True),
"H618E": create_with_capabilities(True, True, True, 15, True),
"H618F": create_with_capabilities(True, True, True, 15, True),
"H619A": create_with_capabilities(True, True, True, 10, True),
"H619B": create_with_capabilities(True, True, True, 10, True),
"H619C": create_with_capabilities(True, True, True, 10, True),
"H619D": create_with_capabilities(True, True, True, 10, True),
"H619E": create_with_capabilities(True, True, True, 10, True),
"H618A": create_with_capabilities(True, True, True, 15, True, True),
"H618C": create_with_capabilities(True, True, True, 15, True, True),
"H618E": create_with_capabilities(True, True, True, 15, True, True),
"H618F": create_with_capabilities(True, True, True, 15, True, True),
"H619A": create_with_capabilities(True, True, True, 10, True, True),
"H619B": create_with_capabilities(True, True, True, 10, True, True),
"H619C": create_with_capabilities(True, True, True, 10, True, True),
"H619D": create_with_capabilities(True, True, True, 10, True, True),
"H619E": create_with_capabilities(True, True, True, 10, True, True),
"H619Z": COMMON_CAPABILITIES,
"H61A0": COMMON_CAPABILITIES,
"H61A1": COMMON_CAPABILITIES,
Expand Down Expand Up @@ -186,6 +233,6 @@ def create_with_capabilities(rgb: bool, kelvin: bool, brightness: bool, segments
"H70B1": COMMON_CAPABILITIES,
"H70A1": COMMON_CAPABILITIES,
# Models with only brightness
"H7012": create_with_capabilities(False, False, True, 0, False),
"H7013": create_with_capabilities(False, False, True, 0, False),
"H7012": create_with_capabilities(False, False, True, 0, False, False),
"H7013": create_with_capabilities(False, False, True, 0, False, False),
}
Loading

0 comments on commit c813500

Please sign in to comment.