-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add HA-compatible device and state class mappings #1346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
511e0e4
feat: add HA-compatible device and state class mappings for BSB-LAN u…
liudger 4f52b1e
feat: enhance EntityInfo with device and state class suggestions for …
liudger 5016441
feat: add comprehensive tests for suggested device and state classes …
liudger 81100ef
feat: refactor tests for suggested device and state classes in Entity…
liudger 88c4ba4
feat: enhance unit mappings and device class suggestions for non-nume…
liudger c7c4185
feat: enhance tests for suggested device and state classes to include…
liudger 3c530fc
refactor: improve comments for HA-compatible device and state class m…
liudger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| """Tests for EntityInfo HA integration properties (device class, state class).""" | ||
|
|
||
| import pytest | ||
|
|
||
| from bsblan.models import DataType, EntityInfo | ||
|
|
||
| # -- suggested_device_class ------------------------------------------------ | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("unit", "expected"), | ||
| [ | ||
| ("°C", "temperature"), | ||
| ("°F", "temperature"), | ||
| ("°C", "temperature"), | ||
| ("°F", "temperature"), | ||
| ("°C", "temperature"), | ||
| ("°F", "temperature"), | ||
| ("kWh", "energy"), | ||
| ("Wh", "energy"), | ||
| ("MWh", "energy"), | ||
| ("kW", "power"), | ||
| ("W", "power"), | ||
| ("bar", "pressure"), | ||
| ("Pa", "pressure"), | ||
| ("hPa", "pressure"), | ||
| ("V", "voltage"), | ||
| ("A", "current"), | ||
| ("mA", "current"), | ||
| ("Hz", "frequency"), | ||
| ("l/min", "volume_flow_rate"), | ||
| ("l/h", "volume_flow_rate"), | ||
| ("h", "duration"), | ||
| ("min", "duration"), | ||
| ("s", "duration"), | ||
| ("%", "power_factor"), | ||
| ], | ||
| ) | ||
| def test_suggested_device_class(unit: str, expected: str) -> None: | ||
| """Test suggested_device_class maps unit to correct HA device class.""" | ||
| entity = EntityInfo( | ||
| name="Test", | ||
| value="42", | ||
| unit=unit, | ||
| desc="", | ||
| data_type=DataType.PLAIN_NUMBER, | ||
| ) | ||
| assert entity.suggested_device_class == expected | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("unit", "data_type"), | ||
| [ | ||
| ("", DataType.ENUM), | ||
| ("bbl", DataType.PLAIN_NUMBER), | ||
| ("unknown", DataType.PLAIN_NUMBER), | ||
| ("°C", DataType.ENUM), | ||
| ("kWh", DataType.ENUM), | ||
| ("°C", DataType.TIME), | ||
| ("°C", DataType.WEEKDAY), | ||
| ("°C", DataType.STRING), | ||
| ], | ||
| ) | ||
| def test_suggested_device_class_none(unit: str, data_type: int) -> None: | ||
| """Test suggested_device_class returns None for unmapped or non-numeric types.""" | ||
| entity = EntityInfo( | ||
| name="Test", | ||
| value="42", | ||
| unit=unit, | ||
| desc="", | ||
| data_type=data_type, | ||
| ) | ||
| assert entity.suggested_device_class is None | ||
|
|
||
|
|
||
| # -- suggested_state_class ------------------------------------------------- | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("unit", "expected"), | ||
| [ | ||
| ("kWh", "total_increasing"), | ||
| ("MWh", "total_increasing"), | ||
| ("Wh", "total_increasing"), | ||
| ("°C", "measurement"), | ||
| ("°F", "measurement"), | ||
| ("°C", "measurement"), | ||
| ("°C", "measurement"), | ||
| ("kW", "measurement"), | ||
| ("W", "measurement"), | ||
| ("bar", "measurement"), | ||
| ("Pa", "measurement"), | ||
| ("V", "measurement"), | ||
| ("A", "measurement"), | ||
| ("Hz", "measurement"), | ||
| ("l/min", "measurement"), | ||
| ("%", "measurement"), | ||
| ("h", "measurement"), | ||
| ("min", "measurement"), | ||
| ("s", "measurement"), | ||
| ], | ||
| ) | ||
| def test_suggested_state_class(unit: str, expected: str) -> None: | ||
| """Test suggested_state_class maps unit to correct HA state class.""" | ||
| entity = EntityInfo( | ||
| name="Test", | ||
| value="42", | ||
| unit=unit, | ||
| desc="", | ||
| data_type=DataType.PLAIN_NUMBER, | ||
| ) | ||
| assert entity.suggested_state_class == expected | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("unit", "data_type"), | ||
| [ | ||
| ("", DataType.ENUM), | ||
| ("bbl", DataType.PLAIN_NUMBER), | ||
| ("kWh", DataType.ENUM), | ||
| ("°C", DataType.TIME), | ||
| ("°C", DataType.WEEKDAY), | ||
| ("°C", DataType.STRING), | ||
| ], | ||
| ) | ||
| def test_suggested_state_class_none(unit: str, data_type: int) -> None: | ||
| """Test suggested_state_class returns None for unmapped or non-numeric types.""" | ||
| entity = EntityInfo( | ||
| name="Test", | ||
| value="42", | ||
| unit=unit, | ||
| desc="", | ||
| data_type=data_type, | ||
| ) | ||
| assert entity.suggested_state_class is None | ||
|
|
||
|
|
||
| # -- dataType_name / dataType_family fields -------------------------------- | ||
|
|
||
|
|
||
| def test_data_type_name_and_family_default() -> None: | ||
| """Test data_type_name and data_type_family default to empty string.""" | ||
| entity = EntityInfo( | ||
| name="Test", | ||
| value="42", | ||
| unit="kWh", | ||
| desc="", | ||
| data_type=DataType.PLAIN_NUMBER, | ||
| ) | ||
| assert entity.data_type_name == "" | ||
| assert entity.data_type_family == "" | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("type_name", "type_family"), | ||
| [ | ||
| ("TEMP", "VALS"), | ||
| ("ENUM", "ENUM"), | ||
| ], | ||
| ) | ||
| def test_data_type_name_from_json(type_name: str, type_family: str) -> None: | ||
| """Test data_type_name/family populated from JSON response.""" | ||
| entity = EntityInfo.from_dict( | ||
| { | ||
| "name": "Test", | ||
| "dataType_name": type_name, | ||
| "dataType_family": type_family, | ||
| "error": 0, | ||
| "value": "18.0", | ||
| "desc": "", | ||
| "dataType": 0, | ||
| "readonly": 0, | ||
| "unit": "°C", | ||
| } | ||
| ) | ||
| assert entity.data_type_name == type_name | ||
| assert entity.data_type_family == type_family | ||
|
|
||
|
|
||
| # -- Backwards compatibility ----------------------------------------------- | ||
|
|
||
|
|
||
| def test_backwards_compat_no_data_type_name_in_json() -> None: | ||
| """Test EntityInfo works when JSON has no dataType_name/family.""" | ||
| entity = EntityInfo.from_dict( | ||
| { | ||
| "name": "Outside temp", | ||
| "error": 0, | ||
| "value": "7.6", | ||
| "desc": "", | ||
| "dataType": 0, | ||
| "readonly": 0, | ||
| "unit": "°C", | ||
| } | ||
| ) | ||
| assert entity.data_type_name == "" | ||
| assert entity.data_type_family == "" | ||
| assert entity.suggested_device_class == "temperature" | ||
| assert entity.suggested_state_class == "measurement" | ||
|
|
||
|
|
||
| def test_backwards_compat_existing_fields_unchanged() -> None: | ||
| """Test that existing fields still work identically.""" | ||
| entity = EntityInfo( | ||
| name="Test Temp", | ||
| value="22.5", | ||
| unit="°C", | ||
| desc="", | ||
| data_type=DataType.PLAIN_NUMBER, | ||
| ) | ||
| # Existing behavior: value converted to float for temperature | ||
| assert entity.value == 22.5 | ||
| assert entity.name == "Test Temp" | ||
| assert entity.unit == "°C" | ||
| assert entity.data_type == DataType.PLAIN_NUMBER | ||
| assert entity.error == 0 | ||
| assert entity.readonly == 0 | ||
| assert entity.readwrite == 0 | ||
| assert entity.precision is None | ||
| assert entity.enum_description is None |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.