async def initialize(self) -> None: self.media_player = self.args["media_player"] await self.check_domain(self.media_player) volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS) self.volume_stepper = MinMaxStepper(0, 1, volume_steps) self.volume_level = 0.0 await super().initialize()
def initialize(self): super().initialize() self.light = self.get_light(self.args["light"]) manual_steps = self.args.get("manual_steps", DEFAULT_MANUAL_STEPS) automatic_steps = self.args.get("automatic_steps", DEFAULT_AUTOMATIC_STEPS) color_stepper = CircularStepper(0, len(self.colors) - 1, len(self.colors)) self.manual_steppers = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(1, 255, manual_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(153, 500, manual_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.automatic_steppers = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(1, 255, automatic_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(153, 500, automatic_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.smooth_power_on = self.args.get("smooth_power_on", self.supports_smooth_power_on())
def test_minmax_stepper_step(minmax, value, steps, direction, expected_value, expected_exceeded): stepper = MinMaxStepper(*minmax, steps) # SUT new_value, exceeded = stepper.step(value, direction) # Checks assert new_value == expected_value assert exceeded == expected_exceeded
async def test_on_min(sut, mocker): attribute = "test_attribute" min_ = 1 on_patch = mocker.patch.object(sut, "on") stepper = MinMaxStepper(min_, 10, 10) stepper.previous_direction = Stepper.UP sut.manual_steppers = {attribute: stepper} # SUT await sut.on_min(attribute) # Checks on_patch.assert_called_once_with(**{attribute: min_}) assert stepper.previous_direction == Stepper.DOWN
async def test_on_full(sut, mocker): attribute = "test_attribute" max_ = 10 on_patch = mocker.patch.object(sut, "on") stepper = MinMaxStepper(1, max_, 10) stepper.previous_direction = Stepper.TOGGLE_DOWN sut.automatic_steppers = {attribute: stepper} # SUT await sut.on_full(attribute) # Checks on_patch.assert_called_once_with(**{attribute: max_}) assert stepper.previous_direction == Stepper.TOGGLE_UP
async def test_on_min(sut, mocker): attribute = "test_attribute" min_ = 1 on_patch = mocker.patch.object(sut, "on") stepper = MinMaxStepper(min_, 10, 10) stepper.previous_direction = Stepper.TOGGLE_UP sut.automatic_steppers = {attribute: stepper} # SUT await sut.on_min(attribute, light_on=False) # Checks on_patch.assert_called_once_with(light_on=False, **{attribute: min_}) assert stepper.previous_direction == Stepper.TOGGLE_DOWN
async def test_click( sut, monkeypatch, mocker, attribute_input, direction_input, light_state, smooth_power_on, expected_calls, ): value_attribute = 10 async def fake_get_entity_state(*args, **kwargs): return light_state async def fake_get_value_attribute(*args, **kwargs): return value_attribute def fake_get_attribute(*args, **kwargs): return attribute_input monkeypatch.setattr(sut, "get_entity_state", fake_get_entity_state) monkeypatch.setattr(sut, "get_value_attribute", fake_get_value_attribute) monkeypatch.setattr(sut, "get_attribute", fake_get_attribute) change_light_state_patch = mocker.patch.object(sut, "change_light_state") sut.smooth_power_on = smooth_power_on stepper = MinMaxStepper(1, 10, 10) sut.manual_steppers = {attribute_input: stepper} # SUT await sut.click(attribute_input, direction_input) # Checks assert change_light_state_patch.call_count == expected_calls
def test_minmax_stepper_get_direction( minmax, value, direction, previous_direction, expected_direction, expected_new_previous_direction, ): stepper = MinMaxStepper(*minmax, 10) stepper.previous_direction = previous_direction # SUT new_direction = stepper.get_direction(value, direction) # Checks assert new_direction == expected_direction assert stepper.previous_direction == expected_new_previous_direction
async def initialize(self): self.light = self.get_light(self.args["light"]) await self.check_domain(self.light["name"]) manual_steps = self.args.get("manual_steps", DEFAULT_MANUAL_STEPS) automatic_steps = self.args.get("automatic_steps", DEFAULT_AUTOMATIC_STEPS) self.min_brightness = self.args.get("min_brightness", DEFAULT_MIN_BRIGHTNESS) self.max_brightness = self.args.get("max_brightness", DEFAULT_MAX_BRIGHTNESS) self.min_color_temp = self.args.get("min_color_temp", DEFAULT_MIN_COLOR_TEMP) self.max_color_temp = self.args.get("max_color_temp", DEFAULT_MAX_COLOR_TEMP) self.transition = self.args.get("transition", DEFAULT_TRANSITION) color_stepper = CircularStepper(0, len(self.colors) - 1, len(self.colors)) self.manual_steppers = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(self.min_brightness, self.max_brightness, manual_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(self.min_color_temp, self.max_color_temp, manual_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.automatic_steppers = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(self.min_brightness, self.max_brightness, automatic_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(self.min_color_temp, self.max_color_temp, automatic_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.smooth_power_on = self.args.get("smooth_power_on", self.supports_smooth_power_on()) self.add_transition = self.args.get("add_transition", True) bitfield = await self.get_entity_state(self.light["name"], attribute="supported_features") self.supported_features = light_features.decode(bitfield) await super().initialize()
async def initialize(self) -> None: self.light = self.get_light(self.args["light"]) await self.check_domain(self.light["name"]) manual_steps = self.args.get("manual_steps", DEFAULT_MANUAL_STEPS) automatic_steps = self.args.get("automatic_steps", DEFAULT_AUTOMATIC_STEPS) self.min_brightness = self.args.get("min_brightness", DEFAULT_MIN_BRIGHTNESS) self.max_brightness = self.args.get("max_brightness", DEFAULT_MAX_BRIGHTNESS) self.min_color_temp = self.args.get("min_color_temp", DEFAULT_MIN_COLOR_TEMP) self.max_color_temp = self.args.get("max_color_temp", DEFAULT_MAX_COLOR_TEMP) self.transition = self.args.get("transition", DEFAULT_TRANSITION) color_stepper = CircularStepper(0, len(self.colors) - 1, len(self.colors)) self.manual_steppers: Dict[str, Stepper] = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(self.min_brightness, self.max_brightness, manual_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(self.min_color_temp, self.max_color_temp, manual_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.automatic_steppers: Dict[str, Stepper] = { LightController.ATTRIBUTE_BRIGHTNESS: MinMaxStepper(self.min_brightness, self.max_brightness, automatic_steps), LightController.ATTRIBUTE_COLOR_TEMP: MinMaxStepper(self.min_color_temp, self.max_color_temp, automatic_steps), LightController.ATTRIBUTE_XY_COLOR: color_stepper, } self.smooth_power_on = self.args.get("smooth_power_on", self.supports_smooth_power_on()) self.add_transition = self.args.get("add_transition", True) self.add_transition_turn_toggle = self.args.get( "add_transition_turn_toggle", True) self.supported_features = LightSupport(self.light["name"], self) await super().initialize()
async def test_on_full(sut, mocker): attribute = "test_attribute" max_ = 10 change_light_state_patch = mocker.patch.object(sut, "change_light_state") stepper = MinMaxStepper(1, max_, 10) sut.manual_steppers = {attribute: stepper} await sut.on_full(attribute) change_light_state_patch.assert_called_once_with(max_, attribute, Stepper.UP, stepper)
async def test_set_value(sut, mocker, min_max, fraction, expected_value): attribute = "test_attribute" on_patch = mocker.patch.object(sut, "on") stepper = MinMaxStepper(min_max[0], min_max[1], 1) sut.automatic_steppers = {attribute: stepper} # SUT await sut.set_value(attribute, fraction) # Checks on_patch.assert_called_once_with(**{attribute: expected_value})
async def test_hold_loop(sut, mocker): attribute = "test_attribute" direction = Stepper.UP sut.value_attribute = 10 change_light_state_patch = mocker.patch.object(sut, "change_light_state") stepper = MinMaxStepper(1, 10, 10) sut.automatic_steppers = {attribute: stepper} await sut.hold_loop(attribute, direction) change_light_state_patch.assert_called_once_with(sut.value_attribute, attribute, direction, stepper)
async def test_on_full(sut, mocker): attribute = "test_attribute" max_ = 10 on_patch = mocker.patch.object(sut, "on") stepper = MinMaxStepper(1, max_, 10) sut.automatic_steppers = {attribute: stepper} # SUT await sut.on_full(attribute, light_on=False) # Checks on_patch.assert_called_once_with(light_on=False, **{attribute: max_})
async def test_hold( sut, monkeypatch, mocker, attribute_input, direction_input, previous_direction, light_state, smooth_power_on, expected_calls, expected_direction, ): value_attribute = 10 async def fake_get_entity_state(*args, **kwargs): return light_state async def fake_get_value_attribute(*args, **kwargs): return value_attribute def fake_get_attribute(*args, **kwargs): return attribute_input monkeypatch.setattr(sut, "get_entity_state", fake_get_entity_state) monkeypatch.setattr(sut, "get_value_attribute", fake_get_value_attribute) monkeypatch.setattr(sut, "get_attribute", fake_get_attribute) sut.smooth_power_on = smooth_power_on stepper = MinMaxStepper(1, 10, 10) stepper.previous_direction = previous_direction sut.automatic_steppers = {attribute_input: stepper} super_hold_patch = mocker.patch.object(ReleaseHoldController, "hold") # SUT await sut.hold(attribute_input, direction_input) # Checks assert super_hold_patch.call_count == expected_calls if expected_calls > 0: super_hold_patch.assert_called_with(attribute_input, expected_direction)
async def test_hold_loop(sut, mocker, value_attribute): attribute = "test_attribute" direction = Stepper.UP sut.value_attribute = value_attribute change_light_state_patch = mocker.patch.object(sut, "change_light_state") stepper = MinMaxStepper(1, 10, 10) sut.automatic_steppers = {attribute: stepper} # SUT exceeded = await sut.hold_loop(attribute, direction) if value_attribute is None: assert exceeded == True else: change_light_state_patch.assert_called_once_with( sut.value_attribute, attribute, direction, stepper, "hold")
class MediaPlayerController(TypeController, ReleaseHoldController): async def initialize(self) -> None: self.media_player = self.args["media_player"] await self.check_domain(self.media_player) volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS) self.volume_stepper = MinMaxStepper(0, 1, volume_steps) self.volume_level = 0.0 self.supported_features = MediaPlayerSupport(self.media_player, self) await super().initialize() def get_domain(self) -> str: return "media_player" def get_type_actions_mapping(self) -> TypeActionsMapping: return { MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, Stepper.DOWN), MediaPlayer.HOLD_VOLUME_UP: (self.hold, Stepper.UP), MediaPlayer.CLICK_VOLUME_DOWN: self.volume_down, MediaPlayer.CLICK_VOLUME_UP: self.volume_up, MediaPlayer.RELEASE: self.release, MediaPlayer.PLAY_PAUSE: self.play_pause, MediaPlayer.NEXT_TRACK: self.next_track, MediaPlayer.PREVIOUS_TRACK: self.previous_track, MediaPlayer.NEXT_SOURCE: (self.change_source_list, Stepper.UP), MediaPlayer.PREVIOUS_SOURCE: (self.change_source_list, Stepper.DOWN), } @action async def change_source_list(self, direction: str) -> None: entity_states = await self.get_entity_state(self.media_player, attribute="all") entity_attributes = entity_states["attributes"] source_list = entity_attributes.get("source_list") if len(source_list) == 0 or source_list is None: self.log( f"⚠️ There is no `source_list` parameter in `{self.media_player}`", level="WARNING", ascii_encode=False, ) return source = entity_attributes.get("source") if source is None: new_index_source = 0 else: index_source = source_list.index(source) source_stepper = CircularStepper(0, len(source_list) - 1, len(source_list)) new_index_source, _ = source_stepper.step(index_source, direction) await self.call_service( "media_player/select_source", entity_id=self.media_player, source=source_list[new_index_source], ) @action async def play_pause(self) -> None: await self.call_service("media_player/media_play_pause", entity_id=self.media_player) @action async def previous_track(self) -> None: await self.call_service("media_player/media_previous_track", entity_id=self.media_player) @action async def next_track(self) -> None: await self.call_service("media_player/media_next_track", entity_id=self.media_player) @action async def volume_up(self) -> None: await self.prepare_volume_change() await self.volume_change(Stepper.UP) @action async def volume_down(self) -> None: await self.prepare_volume_change() await self.volume_change(Stepper.DOWN) @action async def hold(self, direction: str) -> None: await self.prepare_volume_change() await super().hold(direction) async def prepare_volume_change(self) -> None: volume_level = await self.get_entity_state(self.media_player, attribute="volume_level") if volume_level is not None: self.volume_level = volume_level async def volume_change(self, direction: str) -> bool: if await self.supported_features.is_supported( MediaPlayerSupport.VOLUME_SET): self.volume_level, exceeded = self.volume_stepper.step( self.volume_level, direction) await self.call_service( "media_player/volume_set", entity_id=self.media_player, volume_level=self.volume_level, ) return exceeded else: if direction == Stepper.UP: await self.call_service("media_player/volume_up", entity_id=self.media_player) else: await self.call_service("media_player/volume_down", entity_id=self.media_player) return False async def hold_loop(self, direction: str) -> bool: # type: ignore return await self.volume_change(direction) def default_delay(self) -> int: return 500
class MediaPlayerController(ReleaseHoldController): def initialize(self): super().initialize() self.media_player = self.args["media_player"] volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS) self.volume_stepper = MinMaxStepper(0, 1, volume_steps) self.volume_level = 0 def get_type_actions_mapping(self): return { MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, Stepper.DOWN), MediaPlayer.HOLD_VOLUME_UP: (self.hold, Stepper.UP), MediaPlayer.CLICK_VOLUME_DOWN: self.volume_down, MediaPlayer.CLICK_VOLUME_UP: self.volume_up, MediaPlayer.RELEASE: self.release, MediaPlayer.PLAY_PAUSE: self.play_pause, MediaPlayer.NEXT_TRACK: self.next_track, MediaPlayer.PREVIOUS_TRACK: self.previous_track, MediaPlayer.NEXT_SOURCE: (self.change_source_list, Stepper.UP), MediaPlayer.PREVIOUS_SOURCE: (self.change_source_list, Stepper.DOWN), } @action async def change_source_list(self, direction): entity_states = await self.get_entity_state(self.media_player, attribute="all") entity_attributes = entity_states["attributes"] source_list = entity_attributes.get("source_list") if len(source_list) == 0 or source_list is None: self.log( "There is no 'source_list' parameter in this media player", level="WARNING", ) return source = entity_attributes.get("source") if source is None: new_index_source = 0 else: index_source = source_list.index(source) source_stepper = CircularStepper(0, len(source_list) - 1, len(source_list)) new_index_source, _ = source_stepper.step(index_source, direction) self.call_service( "media_player/select_source", entity_id=self.media_player, source=source_list[new_index_source], ) @action async def play_pause(self): self.call_service("media_player/media_play_pause", entity_id=self.media_player) @action async def previous_track(self): self.call_service("media_player/media_previous_track", entity_id=self.media_player) @action async def next_track(self): self.call_service("media_player/media_next_track", entity_id=self.media_player) @action async def volume_up(self): await self.prepare_volume_change() await self.volume_change(Stepper.UP) @action async def volume_down(self): await self.prepare_volume_change() await self.volume_change(Stepper.DOWN) @action async def hold(self, direction): await self.prepare_volume_change() await super().hold(direction) async def prepare_volume_change(self): volume_level = await self.get_entity_state(self.media_player, attribute="volume_level") if volume_level is not None: self.volume_level = volume_level async def volume_change(self, direction): self.volume_level, exceeded = self.volume_stepper.step( self.volume_level, direction) self.call_service( "media_player/volume_set", entity_id=self.media_player, volume_level=self.volume_level, ) return exceeded async def hold_loop(self, direction): return await self.volume_change(direction) def default_delay(self): return 500
def initialize(self): super().initialize() self.media_player = self.args["media_player"] volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS) self.volume_stepper = MinMaxStepper(0, 1, volume_steps) self.volume_level = 0
class MediaPlayerController(ReleaseHoldController): def initialize(self): super().initialize() self.media_player = self.args["media_player"] volume_steps = self.args.get("volume_steps", DEFAULT_VOLUME_STEPS) self.volume_stepper = MinMaxStepper(0, 1, volume_steps) self.volume_level = 0 def get_type_actions_mapping(self): return { MediaPlayer.HOLD_VOLUME_DOWN: (self.hold, Stepper.DOWN), MediaPlayer.HOLD_VOLUME_UP: (self.hold, Stepper.UP), MediaPlayer.CLICK_VOLUME_DOWN: self.volume_down, MediaPlayer.CLICK_VOLUME_UP: self.volume_up, MediaPlayer.RELEASE: self.release, MediaPlayer.PLAY_PAUSE: self.play_pause, MediaPlayer.NEXT_TRACK: self.next_track, MediaPlayer.PREVIOUS_TRACK: self.previous_track, } @action async def play_pause(self): self.call_service("media_player/media_play_pause", entity_id=self.media_player) @action async def previous_track(self): self.call_service("media_player/media_previous_track", entity_id=self.media_player) @action async def next_track(self): self.call_service("media_player/media_next_track", entity_id=self.media_player) @action async def volume_up(self): await self.prepare_volume_change() await self.volume_change(Stepper.UP) @action async def volume_down(self): await self.prepare_volume_change() await self.volume_change(Stepper.DOWN) @action async def hold(self, direction): await self.prepare_volume_change() await super().hold(direction) async def prepare_volume_change(self): volume_level = await self.get_entity_state(self.media_player, attribute="volume_level") if volume_level is not None: self.volume_level = volume_level async def volume_change(self, direction): self.volume_level, exceeded = self.volume_stepper.step( self.volume_level, direction) self.call_service( "media_player/volume_set", entity_id=self.media_player, volume_level=self.volume_level, ) return exceeded async def hold_loop(self, direction): return await self.volume_change(direction) def default_delay(self): return 500
await sut.get_value_attribute(attribute_input) else: output = await sut.get_value_attribute(attribute_input) # Checks assert output == float(expected_output) @pytest.mark.parametrize( "old, attribute, direction, stepper, light_state, smooth_power_on, expected_stop, expected_value_attribute", [ ( 50, LightController.ATTRIBUTE_BRIGHTNESS, Stepper.UP, MinMaxStepper(1, 255, 254), "on", False, False, 51, ), ( 0, "xy_color", Stepper.UP, CircularStepper(0, 30, 30), "on", False, False, 0, ),