async def get_bulbtype(self) -> BulbType: """Return the bulb type as BulbType object.""" if self.bulbtype is not None: return self.bulbtype bulb_config = await self.getBulbConfig() result = bulb_config["result"] white_range = await self.getExtendedWhiteRange() white_to_color_ratio = None white_channels = None if "drvConf" in result: # For old FW < 1.22 white_to_color_ratio, white_channels = result["drvConf"] module_name = result.get( "moduleName") # Not present in 1.8.0 firmware! fw_version = result.get("fwVersion") type_id = result.get("typeId") model_result = self.modelConfig["result"] if self.modelConfig else {} white_channels = model_result.get("nowc", white_channels) white_to_color_ratio = model_result.get("wcr", white_to_color_ratio) self.bulbtype = BulbType.from_data( module_name, white_range, fw_version, white_channels, white_to_color_ratio, type_id, ) return self.bulbtype
async def get_bulbtype(self) -> BulbType: """Retrun the bulb type as BulbType object.""" if self.bulbtype is None: bulb_config = await self.getBulbConfig() if "moduleName" in bulb_config["result"]: _bulbtype = bulb_config["result"]["moduleName"] # set the minimum features for dimmable bulbs (DW bulbs) # define the kelvin range _kelvin = await self.getExtendedWhiteRange() _bulb = BulbType( bulb_type=BulbClass.DW, name=_bulbtype, features=Features(brightness=True, color=False, effect=False, color_tmp=False), kelvin_range=None, ) try: # parse the features from name _identifier = _bulbtype.split("_")[1] # Throw exception if index can not be found except IndexError: raise WizLightNotKnownBulb( "The bulb type can not be determined!") # go an try to map extensions to the BulbTyp object # Color support # TODO: Wokaround - In bulb firmware version 1.22.0 the k-range was removed. if "RGB" in _identifier: if _kelvin: _bulb.kelvin_range = KelvinRange(min=_kelvin[0], max=_kelvin[1]) else: _bulb.kelvin_range = KelvinRange(min=2700, max=6500) _bulb.bulb_type = BulbClass.RGB _bulb.features.color = True # RGB supports effects and tuneabel white _bulb.features.effect = True _bulb.features.color_tmp = True # Non RGB but tunable white bulb if "TW" in _identifier: if _kelvin: _bulb.kelvin_range = KelvinRange(min=_kelvin[0], max=_kelvin[1]) else: _bulb.kelvin_range = KelvinRange(min=2700, max=6500) _bulb.bulb_type = BulbClass.TW _bulb.features.color_tmp = True # RGB supports effects but only "some" # TODO: Improve the mapping to supported effects _bulb.features.effect = True self.bulbtype = _bulb return _bulb raise WizLightNotKnownBulb("The bulb features can not be mapped!")
async def test_model_description_light_strip(light_strip: wizlight) -> None: """Test fetching the model description for a light strip.""" bulb_type = await light_strip.get_bulbtype() assert bulb_type == BulbType( features=Features( color=True, color_tmp=True, effect=True, brightness=True, dual_head=False ), name="ESP03_SHRGB3_01ABI", kelvin_range=KelvinRange(max=6500, min=2700), bulb_type=BulbClass.RGB, fw_version="1.16.64", white_channels=1, white_to_color_ratio=20, )
async def test_model_description_rgbww_bulb(rgbww_bulb: wizlight) -> None: """Test fetching the model description rgbww bulb.""" bulb_type = await rgbww_bulb.get_bulbtype() assert bulb_type == BulbType( features=Features( color=True, color_tmp=True, effect=True, brightness=True, dual_head=False ), name="ESP01_SHRGB1C_31", kelvin_range=KelvinRange(max=6500, min=2700), bulb_type=BulbClass.RGB, fw_version="1.17.1", white_channels=2, white_to_color_ratio=20, )
async def test_model_description_dimmable_bulb(dimmable_bulb: wizlight) -> None: """Test fetching the model description dimmable bulb.""" bulb_type = await dimmable_bulb.get_bulbtype() assert bulb_type == BulbType( features=Features( color=False, color_tmp=False, effect=True, brightness=True, dual_head=False ), name=None, kelvin_range=None, bulb_type=BulbClass.DW, fw_version="1.8.0", white_channels=1, white_to_color_ratio=20, )
async def test_model_description_wall_switch(wall_switch: wizlight) -> None: """Test fetching the model description wall switch.""" bulb_type = await wall_switch.get_bulbtype() assert bulb_type == BulbType( features=Features( color=False, color_tmp=False, effect=False, brightness=True, dual_head=False ), name="ESP01_DIMTRIACS_01", kelvin_range=KelvinRange(max=2700, min=2700), bulb_type=BulbClass.DW, fw_version="1.16.68", white_channels=1, white_to_color_ratio=20, )
async def test_model_description_squire(squire: wizlight) -> None: """Test fetching the model description for a squire.""" bulb_type = await squire.get_bulbtype() assert bulb_type == BulbType( features=Features( color=True, color_tmp=True, effect=True, brightness=True, dual_head=True ), name="ESP20_DHRGB_01B", kelvin_range=KelvinRange(max=6500, min=2200), bulb_type=BulbClass.RGB, fw_version="1.21.40", white_channels=2, white_to_color_ratio=20, )
async def test_fw_version(correct_bulb: wizlight) -> None: """Test fetching the firmware version.""" bulb_type = await correct_bulb.get_bulbtype() assert bulb_type == BulbType( features=Features( color=True, color_tmp=True, effect=True, brightness=True, dual_head=False ), name="ESP01_SHRGB_03", kelvin_range=KelvinRange(max=6500, min=2200), bulb_type=BulbClass.RGB, fw_version="1.25.0", white_channels=1, white_to_color_ratio=30, ) assert correct_bulb.mac == "a8bb5006033d"
async def test_model_description_dimmable_bulb( turnable_bulb: wizlight) -> None: """Test fetching the model description dimmable bulb.""" bulb_type = await turnable_bulb.get_bulbtype() assert bulb_type == BulbType( features=Features(color=False, color_tmp=True, effect=True, brightness=True, dual_head=False), name="ESP14_SHTW1C_01", kelvin_range=KelvinRange(max=6500, min=2700), bulb_type=BulbClass.TW, fw_version="1.18.0", white_channels=1, white_to_color_ratio=20, )
async def get_bulbtype(self) -> BulbType: """Retrun the bulb type as BulbType object.""" if self.bulbtype is None: bulb_config = await self.getBulbConfig() if "moduleName" in bulb_config["result"]: _bulbtype = bulb_config["result"]["moduleName"] # set the minimum features for dimmable bulbs (DW bulbs) # define the kelvin range _kelvin = await self.getExtendedWhiteRange() # use only first and last entry - [2200,2700,6500,6500] _kelvin = _kelvin[:: len(_kelvin) - 1] _bulb = BulbType( bulb_type=BulbClass.DW, name=_bulbtype, features=Features( brightness=True, color=False, effect=False, color_tmp=False ), kelvin_range=None, ) try: # parse the features from name _identifier = _bulbtype.split("_")[1] # Throw exception if index can not be found except IndexError: raise WizLightNotKnownBulb("The bulb type can not be determined!") # try to map extensions to the BulbTyp object # Color support if "RGB" in _identifier: _bulb.kelvin_range = KelvinRange(min=_kelvin[0], max=_kelvin[1]) _bulb.bulb_type = BulbClass.RGB _bulb.features.color = True # RGB supports effects and CCT _bulb.features.effect = True _bulb.features.color_tmp = True # Tunable white bulb; no RGB if "TW" in _identifier: _bulb.kelvin_range = KelvinRange(min=_kelvin[0], max=_kelvin[1]) _bulb.kelvin_range = KelvinRange(min=2700, max=6500) _bulb.bulb_type = BulbClass.TW _bulb.features.color_tmp = True # TW supports some effects only # TODO: Improve the mapping to supported effects _bulb.features.effect = True self.bulbtype = _bulb return _bulb raise WizLightNotKnownBulb("The bulb features can not be mapped!")
async def test_model_description_socket(socket: wizlight) -> None: """Test fetching the model description of a socket is None.""" bulb_type = await socket.get_bulbtype() assert bulb_type == BulbType( features=Features( color=False, color_tmp=False, effect=False, brightness=False, dual_head=False, ), name="ESP10_SOCKET_06", kelvin_range=KelvinRange(max=6500, min=2700), bulb_type=BulbClass.SOCKET, fw_version="1.16.71", white_channels=2, white_to_color_ratio=20, )
async def test_model_description_unknown_bulb( unknown_bulb: wizlight, caplog: pytest.LogCaptureFixture) -> None: """Test fetching the model description for an unknown bulb.""" bulb_type = await unknown_bulb.get_bulbtype() assert bulb_type == BulbType( features=Features(color=False, color_tmp=False, effect=True, brightness=True, dual_head=False), name=None, kelvin_range=None, bulb_type=BulbClass.DW, fw_version="1.8.0", white_channels=1, white_to_color_ratio=20, ) assert ( "Unknown typeId: 1, please report what kind of bulb this is at https://github.com/sbidy/pywizlight/issues/new" in caplog.text)
async def test_push_updates(socket_push: wizlight) -> None: """Test push updates.""" bulb_type = await socket_push.get_bulbtype() assert bulb_type == BulbType( features=Features( color=False, color_tmp=False, effect=False, brightness=False, dual_head=False, ), name="ESP10_SOCKET_06", kelvin_range=KelvinRange(max=2700, min=2700), bulb_type=BulbClass.SOCKET, fw_version="1.25.0", white_channels=2, white_to_color_ratio=20, ) last_data = PilotParser({}) data_event = asyncio.Event() def _on_push(data: PilotParser) -> None: nonlocal last_data last_data = data data_event.set() with patch("pywizlight.push_manager.LISTEN_PORT", 0): assert await socket_push.start_push(_on_push) is True push_manager = PushManager().get() push_port = push_manager.push_transport.get_extra_info("sockname")[1] push_in_transport_proto = await asyncio.get_event_loop( ).create_datagram_endpoint( lambda: WizProtocol(on_response=lambda resp, addr: None), remote_addr=("127.0.0.1", push_port), ) push_transport = cast(asyncio.DatagramTransport, push_in_transport_proto[0]) params = { "mac": "a8bb5006033d", "rssi": -71, "src": "hb", "mqttCd": 255, "ts": 1644593327, "pc": 660, "state": False, "sceneId": 0, "temp": 6500, "dimming": 100, } push_transport.sendto( to_wiz_json({ "method": "syncPilot", "env": "pro", "params": params, }).encode(), ("127.0.0.1", push_port), ) await asyncio.wait_for(data_event.wait(), timeout=1) assert last_data.pilotResult == params update = await socket_push.updateState() assert update is not None assert update.pilotResult == params assert await socket_push.get_power() == 0.660 diagnostics = socket_push.diagnostics assert diagnostics["bulb_type"]["bulb_type"] == "SOCKET" assert diagnostics["history"]["last_error"] is None assert diagnostics["push_running"] is True assert (diagnostics["history"]["push"]["syncPilot"]["params"]["mac"] == "a8bb5006033d") assert diagnostics["push_manager"]["running"] is True assert diagnostics["push_manager"]["fail_reason"] is None push_transport.close()
async def test_discovery_by_firstbeat( socket_push: wizlight, caplog: pytest.LogCaptureFixture) -> None: """Test discovery from first beat.""" bulb_type = await socket_push.get_bulbtype() assert bulb_type == BulbType( features=Features( color=False, color_tmp=False, effect=False, brightness=False, dual_head=False, ), name="ESP10_SOCKET_06", kelvin_range=KelvinRange(max=2700, min=2700), bulb_type=BulbClass.SOCKET, fw_version="1.25.0", white_channels=2, white_to_color_ratio=20, ) last_discovery: Optional[DiscoveredBulb] = None discovery_event = asyncio.Event() def _on_discovery(discovery: DiscoveredBulb) -> None: nonlocal last_discovery last_discovery = discovery discovery_event.set() with patch("pywizlight.push_manager.LISTEN_PORT", 0): assert await socket_push.start_push(lambda data: None) is True assert socket_push.mac is not None socket_push.set_discovery_callback(_on_discovery) push_manager = PushManager().get() push_port = push_manager.push_transport.get_extra_info("sockname")[1] push_in_transport_proto = await asyncio.get_event_loop( ).create_datagram_endpoint( lambda: WizProtocol(on_response=lambda resp, addr: None), remote_addr=("127.0.0.1", push_port), ) push_transport = cast(asyncio.DatagramTransport, push_in_transport_proto[0]) push_transport.sendto( b"test", ("127.0.0.1", push_port), ) push_transport.sendto( b"GARBAGE", ("127.0.0.1", push_port), ) push_transport.sendto( to_wiz_json({ "method": "firstBeat", "env": "pro", "params": { "mac": socket_push.mac }, }).encode(), ("127.0.0.1", push_port), ) await asyncio.wait_for(discovery_event.wait(), timeout=1) assert last_discovery is not None assert last_discovery == DiscoveredBulb("127.0.0.1", socket_push.mac) push_transport.close() assert "GARBAGE" in caplog.text