Skip to content

Commit e578b65

Browse files
author
Tom Lasswell
committed
feat: Add DreamView (Movie Mode) toggle support
Add switch entity for devices with dreamViewToggle capability, such as Govee Immersion TV backlights. Uses the standard toggle API pattern with devices.capabilities.toggle type. Closes #6
1 parent 456844e commit e578b65

9 files changed

Lines changed: 168 additions & 2 deletions

File tree

custom_components/govee/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
"cryptography>=41.0.0"
1717
],
1818
"ssdp": [],
19-
"version": "2026.1.54",
19+
"version": "2026.1.55",
2020
"zeroconf": []
2121
}

custom_components/govee/models/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
SegmentColorCommand,
1818
ToggleCommand,
1919
WorkModeCommand,
20+
create_dreamview_command,
2021
create_night_light_command,
2122
)
2223
from .device import (
@@ -51,5 +52,6 @@
5152
"WorkModeCommand",
5253
"ModeCommand",
5354
"MusicModeCommand",
55+
"create_dreamview_command",
5456
"create_night_light_command",
5557
]

custom_components/govee/models/commands.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
INSTANCE_COLOR_RGB,
2525
INSTANCE_COLOR_TEMP,
2626
INSTANCE_DIY,
27+
INSTANCE_DREAMVIEW,
2728
INSTANCE_MUSIC_MODE,
2829
INSTANCE_NIGHT_LIGHT,
2930
INSTANCE_OSCILLATION,
@@ -233,6 +234,11 @@ def create_night_light_command(enabled: bool) -> ToggleCommand:
233234
return ToggleCommand(toggle_instance=INSTANCE_NIGHT_LIGHT, enabled=enabled)
234235

235236

237+
def create_dreamview_command(enabled: bool) -> ToggleCommand:
238+
"""Create a command to toggle DreamView (Movie Mode)."""
239+
return ToggleCommand(toggle_instance=INSTANCE_DREAMVIEW, enabled=enabled)
240+
241+
236242
@dataclass(frozen=True)
237243
class OscillationCommand(DeviceCommand):
238244
"""Command to toggle fan oscillation."""

custom_components/govee/models/device.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
INSTANCE_WORK_MODE = "workMode"
4646
INSTANCE_HDMI_SOURCE = "hdmiSource"
4747
INSTANCE_MUSIC_MODE = "musicMode"
48+
INSTANCE_DREAMVIEW = "dreamViewToggle"
4849

4950

5051
@dataclass(frozen=True)
@@ -193,6 +194,11 @@ def is_oscillation(self) -> bool:
193194
"""Check if this is an oscillation toggle (for fans)."""
194195
return self.type == CAPABILITY_TOGGLE and self.instance == INSTANCE_OSCILLATION
195196

197+
@property
198+
def is_dreamview(self) -> bool:
199+
"""Check if this is a DreamView toggle (Movie Mode)."""
200+
return self.type == CAPABILITY_TOGGLE and self.instance == INSTANCE_DREAMVIEW
201+
196202
@property
197203
def is_work_mode(self) -> bool:
198204
"""Check if this is a work mode capability (for fans)."""
@@ -294,6 +300,11 @@ def supports_oscillation(self) -> bool:
294300
"""Check if device supports oscillation (fans)."""
295301
return any(cap.is_oscillation for cap in self.capabilities)
296302

303+
@property
304+
def supports_dreamview(self) -> bool:
305+
"""Check if device supports DreamView (Movie Mode) toggle."""
306+
return any(cap.is_dreamview for cap in self.capabilities)
307+
297308
@property
298309
def supports_work_mode(self) -> bool:
299310
"""Check if device supports work mode (fans)."""

custom_components/govee/strings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109
},
110110
"govee_music_mode": {
111111
"name": "Music Mode"
112+
},
113+
"govee_dreamview": {
114+
"name": "DreamView"
112115
}
113116
},
114117
"sensor": {

custom_components/govee/switch.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .coordinator import GoveeCoordinator
1919
from .entity import GoveeEntity
20-
from .models import GoveeDevice, MusicModeCommand, PowerCommand, create_night_light_command
20+
from .models import GoveeDevice, MusicModeCommand, PowerCommand, create_dreamview_command, create_night_light_command
2121

2222
_LOGGER = logging.getLogger(__name__)
2323

@@ -53,6 +53,11 @@ async def async_setup_entry(
5353
entities.append(GoveeMusicModeSwitchEntity(coordinator, device, use_rest_api=False))
5454
_LOGGER.debug("Created BLE music mode switch entity for %s", device.name)
5555

56+
# Create switch for DreamView (Movie Mode) toggle
57+
if device.supports_dreamview:
58+
entities.append(GoveeDreamViewSwitchEntity(coordinator, device))
59+
_LOGGER.debug("Created DreamView switch entity for %s", device.name)
60+
5661
async_add_entities(entities)
5762
_LOGGER.debug("Set up %d Govee switch entities", len(entities))
5863

@@ -283,3 +288,56 @@ async def async_turn_off(self, **kwargs: Any) -> None:
283288
if success:
284289
self._is_on = False
285290
self.async_write_ha_state()
291+
292+
293+
class GoveeDreamViewSwitchEntity(GoveeEntity, SwitchEntity):
294+
"""Govee DreamView (Movie Mode) toggle switch entity.
295+
296+
Controls DreamView mode for devices that support it (e.g., Immersion TV backlights).
297+
Uses optimistic state since API may not return DreamView status.
298+
"""
299+
300+
_attr_translation_key = "govee_dreamview"
301+
_attr_icon = "mdi:movie-open"
302+
303+
def __init__(
304+
self,
305+
coordinator: GoveeCoordinator,
306+
device: GoveeDevice,
307+
) -> None:
308+
"""Initialize the DreamView switch entity."""
309+
super().__init__(coordinator, device)
310+
311+
# Unique ID for DreamView switch
312+
self._attr_unique_id = f"{device.device_id}_dreamview"
313+
314+
# Name as "DreamView"
315+
self._attr_name = "DreamView"
316+
317+
# Optimistic state
318+
self._is_on = False
319+
320+
@property
321+
def is_on(self) -> bool:
322+
"""Return True if DreamView is on."""
323+
return self._is_on
324+
325+
async def async_turn_on(self, **kwargs: Any) -> None:
326+
"""Turn DreamView on."""
327+
success = await self.coordinator.async_control_device(
328+
self._device_id,
329+
create_dreamview_command(enabled=True),
330+
)
331+
if success:
332+
self._is_on = True
333+
self.async_write_ha_state()
334+
335+
async def async_turn_off(self, **kwargs: Any) -> None:
336+
"""Turn DreamView off."""
337+
success = await self.coordinator.async_control_device(
338+
self._device_id,
339+
create_dreamview_command(enabled=False),
340+
)
341+
if success:
342+
self._is_on = False
343+
self.async_write_ha_state()

custom_components/govee/translations/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109
},
110110
"govee_music_mode": {
111111
"name": "Music Mode"
112+
},
113+
"govee_dreamview": {
114+
"name": "DreamView"
112115
}
113116
},
114117
"sensor": {

tests/conftest.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
INSTANCE_BRIGHTNESS,
3131
INSTANCE_COLOR_RGB,
3232
INSTANCE_COLOR_TEMP,
33+
INSTANCE_DREAMVIEW,
3334
INSTANCE_HDMI_SOURCE,
3435
INSTANCE_OSCILLATION,
3536
INSTANCE_POWER,
@@ -516,3 +517,41 @@ def api_hdmi_state_response() -> dict[str, Any]:
516517
},
517518
],
518519
}
520+
521+
522+
@pytest.fixture
523+
def dreamview_capabilities() -> tuple[GoveeCapability, ...]:
524+
"""Create capabilities for a DreamView-enabled device (e.g., H6199 Immersion)."""
525+
return (
526+
GoveeCapability(
527+
type=CAPABILITY_ON_OFF,
528+
instance=INSTANCE_POWER,
529+
parameters={},
530+
),
531+
GoveeCapability(
532+
type=CAPABILITY_RANGE,
533+
instance=INSTANCE_BRIGHTNESS,
534+
parameters={"range": {"min": 0, "max": 100}},
535+
),
536+
GoveeCapability(
537+
type=CAPABILITY_TOGGLE,
538+
instance=INSTANCE_DREAMVIEW,
539+
parameters={
540+
"dataType": "ENUM",
541+
"options": [{"name": "on", "value": 1}, {"name": "off", "value": 0}],
542+
},
543+
),
544+
)
545+
546+
547+
@pytest.fixture
548+
def mock_dreamview_device(dreamview_capabilities) -> GoveeDevice:
549+
"""Create a mock DreamView-enabled device (e.g., H6199 Immersion)."""
550+
return GoveeDevice(
551+
device_id="AA:BB:CC:DD:EE:FF:00:66",
552+
sku="H6199",
553+
name="TV Backlight Immersion",
554+
device_type=DEVICE_TYPE_LIGHT,
555+
capabilities=dreamview_capabilities,
556+
is_group=False,
557+
)

tests/test_models.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
OscillationCommand,
1919
WorkModeCommand,
2020
ModeCommand,
21+
create_dreamview_command,
2122
)
2223
from custom_components.govee.models.device import (
2324
CAPABILITY_ON_OFF,
@@ -35,6 +36,7 @@
3536
INSTANCE_OSCILLATION,
3637
INSTANCE_WORK_MODE,
3738
INSTANCE_HDMI_SOURCE,
39+
INSTANCE_DREAMVIEW,
3840
)
3941

4042

@@ -158,6 +160,21 @@ def test_is_hdmi_source(self):
158160
assert cap.is_hdmi_source is True
159161
assert cap.is_work_mode is False
160162

163+
def test_is_dreamview(self):
164+
"""Test DreamView toggle capability detection."""
165+
cap = GoveeCapability(
166+
type=CAPABILITY_TOGGLE,
167+
instance=INSTANCE_DREAMVIEW,
168+
parameters={
169+
"dataType": "ENUM",
170+
"options": [{"name": "on", "value": 1}, {"name": "off", "value": 0}],
171+
},
172+
)
173+
assert cap.is_dreamview is True
174+
assert cap.is_toggle is True
175+
assert cap.is_night_light is False
176+
assert cap.is_oscillation is False
177+
161178
def test_immutable(self):
162179
"""Test that GoveeCapability is immutable (frozen)."""
163180
cap = GoveeCapability(type=CAPABILITY_ON_OFF, instance=INSTANCE_POWER, parameters={})
@@ -257,6 +274,14 @@ def test_get_hdmi_source_options_no_support(self, mock_light_device):
257274
options = mock_light_device.get_hdmi_source_options()
258275
assert options == []
259276

277+
def test_supports_dreamview(self, mock_dreamview_device):
278+
"""Test DreamView support detection."""
279+
assert mock_dreamview_device.supports_dreamview is True
280+
281+
def test_no_dreamview_support(self, mock_light_device):
282+
"""Test that regular lights don't have DreamView support."""
283+
assert mock_light_device.supports_dreamview is False
284+
260285
def test_from_api_response(self, api_device_response):
261286
"""Test creating device from API response."""
262287
device = GoveeDevice.from_api_response(api_device_response)
@@ -541,3 +566,22 @@ def test_mode_command_immutable(self):
541566
cmd = ModeCommand(mode_instance="hdmiSource", value=1)
542567
with pytest.raises(AttributeError):
543568
cmd.value = 2
569+
570+
def test_dreamview_command_on(self):
571+
"""Test create_dreamview_command for turning DreamView on."""
572+
cmd = create_dreamview_command(enabled=True)
573+
assert cmd.toggle_instance == "dreamViewToggle"
574+
assert cmd.enabled is True
575+
assert cmd.get_value() == 1
576+
payload = cmd.to_api_payload()
577+
assert payload["type"] == "devices.capabilities.toggle"
578+
assert payload["instance"] == "dreamViewToggle"
579+
assert payload["value"] == 1
580+
581+
def test_dreamview_command_off(self):
582+
"""Test create_dreamview_command for turning DreamView off."""
583+
cmd = create_dreamview_command(enabled=False)
584+
assert cmd.enabled is False
585+
assert cmd.get_value() == 0
586+
payload = cmd.to_api_payload()
587+
assert payload["value"] == 0

0 commit comments

Comments
 (0)