Skip to content

Commit

Permalink
fix: add save way to parse float and int (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
c0un7-z3r0 authored Jun 19, 2024
1 parent 2468fa1 commit 3707f34
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 15 deletions.
27 changes: 12 additions & 15 deletions custom_components/phoniebox/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
from .data_coordinator import DataCoordinator
from .entity import PhonieboxEntity
from .services import async_register_custom_services
from .utils import bool_to_string, string_to_bool
from .utils import bool_to_string, parse_float_save, parse_int_save, string_to_bool


async def async_setup_entry(
Expand Down Expand Up @@ -129,27 +129,26 @@ async def async_set_attributes(self, msg: ReceiveMessage) -> None: # noqa: PLR0
new_value = str(msg.payload)

if changed_attribute_name == PHONIEBOX_ATTR_VOLUME:
self._attr_volume_level = float(new_value) / 100.0
self._attr_volume_level = parse_float_save(new_value) / 100.0
elif changed_attribute_name == PHONIEBOX_ATTR_STATE:
self._attr_state = PHONIEBOX_STATE_TO_HA[new_value]
elif changed_attribute_name == PHONIEBOX_ATTR_MUTE:
self._attr_is_volume_muted = string_to_bool(new_value)
elif changed_attribute_name == PHONIEBOX_ATTR_RANDOM:
self._attr_shuffle = string_to_bool(new_value)
elif changed_attribute_name == PHONIEBOX_ATTR_MAX_VOLUME:
self._max_volume = int(new_value)
self._max_volume = parse_int_save(new_value, 100)
elif changed_attribute_name == PHONIEBOX_ATTR_DURATION:
self._attr_media_duration = sum(
x * int(t)
x * parse_int_save(t)
for x, t in zip([3600, 60, 1], new_value.split(":"), strict=False)
)
elif changed_attribute_name == PHONIEBOX_ATTR_TRACK:
track_number = new_value.split(sep="/", maxsplit=1)[0]

self._attr_media_track = int(track_number)
self._attr_media_track = parse_int_save(track_number)
elif changed_attribute_name == PHONIEBOX_ATTR_ELAPSED:
self._attr_media_position = sum(
x * int(t)
x * parse_int_save(t)
for x, t in zip([3600, 60, 1], new_value.split(":"), strict=False)
)
elif changed_attribute_name == PHONIEBOX_ATTR_ARTIST:
Expand All @@ -161,14 +160,12 @@ async def async_set_attributes(self, msg: ReceiveMessage) -> None: # noqa: PLR0
elif changed_attribute_name == PHONIEBOX_ATTR_ALBUM_ARTIST:
self._attr_media_album_artist = new_value
elif changed_attribute_name == PHONIEBOX_ATTR_VOLUME_STEPS:
self._vol_steps = int(new_value)
self._vol_steps = parse_int_save(new_value)
elif changed_attribute_name == PHONIEBOX_ATTR_REPEAT:
if new_value == "true":
# is bad but phoniebox will only say if repeat is on or off
self._attr_repeat = REPEAT_MODE_ONE
else:
self._attr_repeat = REPEAT_MODE_OFF

# is bad but phoniebox will only say if repeat is on or off
self._attr_repeat = (
REPEAT_MODE_ONE if new_value == "true" else REPEAT_MODE_OFF
)
self.schedule_update_ha_state(force_refresh=True)

async def update_device_state(self, msg: ReceiveMessage) -> None:
Expand Down Expand Up @@ -264,7 +261,7 @@ async def async_turn_off(self) -> None:
async def async_set_volume_level(self, volume: float) -> None:
"""Set volume level, range 0..1."""
await self.mqtt_client.async_publish_cmd(
PHONIEBOX_CMD_SET_VOLUME, int(volume * 100)
PHONIEBOX_CMD_SET_VOLUME, parse_int_save(volume * 100)
)

async def async_set_volume_steps(self, volume_steps: int) -> None:
Expand Down
18 changes: 18 additions & 0 deletions custom_components/phoniebox/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Utility functions."""

from typing import Any


def string_to_bool(value: str) -> bool:
"""Boolean string to boolean converter."""
Expand All @@ -13,3 +15,19 @@ def bool_to_string(value: bool) -> str: # noqa: FBT001
if value:
return "true"
return "false"


def parse_float_save(val: Any, fallback: float = 0.0) -> float:
"""Try to parse value to float or return fallback."""
try:
return float(val)
except ValueError:
return fallback


def parse_int_save(val: Any, fallback: int = 0) -> int:
"""Try to parse value to int or return fallback."""
try:
return int(val)
except ValueError:
return fallback
18 changes: 18 additions & 0 deletions tests/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -825,3 +825,21 @@ async def test_repeat(
phoniebox_state = hass.states.get("media_player.phoniebox_test_box")
assert phoniebox_state is not None
assert phoniebox_state.attributes.get(ATTR_MEDIA_REPEAT) == REPEAT_MODE_OFF


async def test_bugfix_parse_value(
hass: HomeAssistant, mock_phoniebox: MockConfigEntry, config: dict
) -> None:
"""
Test parsing of values in mediaplayer.
https://github.com/c0un7-z3r0/hass-phoniebox/issues/45
"""
phoniebox_state = hass.states.get("media_player.phoniebox_test_box")
assert phoniebox_state is not None

async_fire_mqtt_message(hass, "test_phoniebox/attribute/volume", "-")
await hass.async_block_till_done()
phoniebox_state = hass.states.get("media_player.phoniebox_test_box")
assert phoniebox_state is not None
assert phoniebox_state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0.0
88 changes: 88 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""Tests for the utils."""

from custom_components.phoniebox.utils import (
bool_to_string,
parse_float_save,
parse_int_save,
string_to_bool,
)


def test_parse_float_save() -> None:
"""Test parsing of float."""
val = parse_float_save("10")
assert val == 10.0
assert isinstance(val, float)
val = parse_float_save(val="-10", fallback=2.3)
assert val == -10.0
assert isinstance(val, float)
val = parse_float_save(val="1a0")
assert val == 0
assert isinstance(val, float)


def test_parse_float_save_fallback() -> None:
"""Test parsing of float failing."""
val = parse_float_save("-1a0")
assert val == 0.0
assert isinstance(val, float)
val = parse_float_save(val="-10a", fallback=2.3)
assert val == 2.3
assert isinstance(val, float)
val = parse_float_save(val="-10a", fallback=-1.2)
assert val == -1.2
assert isinstance(val, float)


def test_parse_int_save() -> None:
"""Test parsing of int."""
val = parse_int_save("10")
assert val == 10
assert isinstance(val, int)

val = parse_int_save(val="-10", fallback=2)
assert val == -10
assert isinstance(val, int)

val = parse_int_save(val="1a0")
assert val == 0
assert isinstance(val, int)


def test_parse_int_save_fallback() -> None:
"""Test parsing of int failing."""
val = parse_int_save("-1a0")
assert val == 0.0
assert isinstance(val, int)
val = parse_int_save(val="-10a", fallback=2)
assert val == 2
assert isinstance(val, int)
val = parse_int_save(val="-10a", fallback=-1)
assert val == -1
assert isinstance(val, int)


def test_bool_to_string() -> None:
"""Test the util function to stringify bool."""
val = bool_to_string(True)
assert isinstance(val, str)
assert val == "true"

val = bool_to_string(False)
assert isinstance(val, str)
assert val == "false"


def test_string_to_bool() -> None:
"""Test the util function to parse string to boolean."""
val = string_to_bool("true")
assert isinstance(val, bool)
assert val is True

val = string_to_bool("false")
assert isinstance(val, bool)
assert val is False

val = string_to_bool("any thing else")
assert isinstance(val, bool)
assert val is False

0 comments on commit 3707f34

Please sign in to comment.