Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,40 @@ if __name__ == "__main__":
asyncio.run(main())
```

### Reading Custom Parameters

You can also read individual parameters directly by their ID or name for more flexibility:

```python
import asyncio
from bsblan import BSBLAN, BSBLANConfig

async def main():
config = BSBLANConfig(host="10.0.2.60")

async with BSBLAN(config) as bsblan:
await bsblan.initialize()

# Read a single parameter by ID
temp = await bsblan.read_parameter("8740")
if temp:
print(f"Temperature: {temp.value} {temp.unit}")

# Read a single parameter by name
hvac_mode = await bsblan.read_parameter_by_name("hvac_mode")
if hvac_mode:
print(f"HVAC Mode: {hvac_mode.desc}")

asyncio.run(main())
```

This is useful when you want to:
- Fetch only specific parameters you need
- Work with custom or non-standard parameters
- Build custom sensor implementations

See `examples/custom_parameters.py` for more detailed examples.

## Changelog & Releases

This repository keeps a change log using [GitHub's releases][releases]
Expand Down
81 changes: 81 additions & 0 deletions examples/custom_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Example demonstrating custom sensor/parameter reading.

This example shows how to use the new read_parameter() and
read_parameter_by_name() methods to fetch individual custom parameters.
"""

import asyncio
import os

from bsblan import BSBLAN, BSBLANConfig


async def main() -> None:
"""Demonstrate custom parameter reading."""
# Initialize the client from environment variables or defaults
config = BSBLANConfig(
host=os.getenv("BSBLAN_HOST", "192.168.1.100"),
username=os.getenv("BSBLAN_USER"),
password=os.getenv("BSBLAN_PASS"),
)

async with BSBLAN(config) as client:
# Initialize the API
await client.initialize()

print("=" * 60)
print("Reading Custom Parameters")
print("=" * 60)

# Example 1: Read a single parameter by ID
print("\n1. Reading parameter by ID (8740 - current temperature)")
temp = await client.read_parameter("8740")
if temp:
print(f" Value: {temp.value} {temp.unit}")
print(f" Name: {temp.name}")
print(f" Description: {temp.desc}")
else:
print(" Parameter not found or not supported")

# Example 2: Read a single parameter by name
print("\n2. Reading parameter by name (current_temperature)")
temp = await client.read_parameter_by_name("current_temperature")
if temp:
print(f" Value: {temp.value} {temp.unit}")
print(f" Data Type: {temp.data_type}")
else:
print(" Parameter not found or not supported")

# Example 3: Read HVAC mode (ENUM type)
print("\n3. Reading HVAC mode (700 - operating mode)")
hvac_mode = await client.read_parameter("700")
if hvac_mode:
print(f" Value: {hvac_mode.value}")
print(f" Description: {hvac_mode.desc}")
print(f" Unit: {hvac_mode.unit}")
else:
print(" Parameter not found or not supported")

# Example 4: Read multiple custom parameters
print("\n4. Reading multiple custom parameters")
param_ids = ["8740", "8700", "700"] # temperature, outside temp, mode
for param_id in param_ids:
param = await client.read_parameter(param_id)
if param:
print(f" {param_id}: {param.value} {param.unit} ({param.name})")

# Example 5: Check if parameter exists before using it
print("\n5. Safe parameter reading with existence check")
outside_temp = await client.read_parameter_by_name("outside_temperature")
if outside_temp and outside_temp.value != "---":
print(f" Outside temperature: {outside_temp.value} {outside_temp.unit}")
else:
print(" Outside temperature sensor not available")

print("\n" + "=" * 60)
print("Examples completed!")
print("=" * 60)


if __name__ == "__main__":
asyncio.run(main())
79 changes: 79 additions & 0 deletions src/bsblan/bsblan.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
NO_PARAMETER_NAMES_ERROR_MSG,
NO_SCHEDULE_ERROR_MSG,
NO_STATE_ERROR_MSG,
PARAMETER_ID_EMPTY_ERROR_MSG,
PARAMETER_NAME_EMPTY_ERROR_MSG,
PARAMETER_NAMES_NOT_RESOLVED_ERROR_MSG,
SESSION_NOT_INITIALIZED_ERROR_MSG,
SETTABLE_HOT_WATER_PARAMS,
Expand Down Expand Up @@ -1547,3 +1549,80 @@ async def read_parameters_by_name(
for param_id, entity_info in id_results.items()
if param_id in id_to_name
}

async def read_parameter(
self,
parameter_id: str,
) -> EntityInfo | None:
"""Read a single parameter by its BSB-LAN parameter ID.

This is a convenience method for reading a single custom parameter.
Returns the parameter data as an EntityInfo object with correct type
and format, or None if the parameter is not found or invalid.

Example:
# Fetch only current temperature
temp = await client.read_parameter("8740")
if temp:
print(f"Temperature: {temp.value} {temp.unit}")

Args:
parameter_id: The BSB-LAN parameter ID to fetch (e.g., "700").

Returns:
EntityInfo | None: The parameter data as EntityInfo object,
or None if not found or invalid.

Raises:
BSBLANError: If parameter_id is empty or request fails.

"""
if not parameter_id:
raise BSBLANError(PARAMETER_ID_EMPTY_ERROR_MSG)

result = await self.read_parameters([parameter_id])
return result.get(parameter_id)

async def read_parameter_by_name(
self,
parameter_name: str,
) -> EntityInfo | None:
"""Read a single parameter by its name.

This is a convenience method for reading a single custom parameter
by its name. Returns the parameter data as an EntityInfo object with
correct type and format, or None if the parameter is not found or invalid.

Example:
# Fetch only current temperature by name
temp = await client.read_parameter_by_name("current_temperature")
if temp:
print(f"Temperature: {temp.value} {temp.unit}")

Args:
parameter_name: The parameter name to fetch
(e.g., "current_temperature").

Returns:
EntityInfo | None: The parameter data as EntityInfo object,
or None if not found or invalid.

Raises:
BSBLANError: If parameter_name is empty, the client is not initialized,
or the parameter name cannot be resolved.

"""
if not parameter_name:
raise BSBLANError(PARAMETER_NAME_EMPTY_ERROR_MSG)

if not self._api_data:
raise BSBLANError(API_DATA_NOT_INITIALIZED_ERROR_MSG)

# Resolve name to ID
param_id = self.get_parameter_id(parameter_name)

if param_id is None:
msg = f"{PARAMETER_NAMES_NOT_RESOLVED_ERROR_MSG}: {parameter_name}"
raise BSBLANError(msg)

return await self.read_parameter(param_id)
2 changes: 2 additions & 0 deletions src/bsblan/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,8 @@ def get_hvac_action_category(status_code: int) -> HVACActionCategory:
# Error messages for low-level parameter access
NO_PARAMETER_IDS_ERROR_MSG: Final[str] = "No parameter IDs provided"
NO_PARAMETER_NAMES_ERROR_MSG: Final[str] = "No parameter names provided"
PARAMETER_ID_EMPTY_ERROR_MSG: Final[str] = "Parameter ID cannot be empty"
PARAMETER_NAME_EMPTY_ERROR_MSG: Final[str] = "Parameter name cannot be empty"
PARAMETER_NAMES_NOT_RESOLVED_ERROR_MSG: Final[str] = (
"Could not resolve any parameter names"
)
Expand Down
Loading