Skip to content

Client

The main entry point for interacting with BSB-LAN devices.

BSBLAN

bsblan.BSBLAN dataclass

BSBLAN(
    config: BSBLANConfig,
    session: ClientSession | None = None,
    _close_session: bool = False,
    _firmware_version: str | None = None,
    _api_version: str | None = None,
    _api_data: APIConfig | None = None,
    _initialized: bool = False,
    _temperature_unit: str = "°C",
    _circuit_temp_ranges: dict[
        int, dict[str, float | None]
    ] = dict(),
    _circuit_temp_initialized: set[int] = set(),
    _hot_water_param_cache: dict[str, str] = dict(),
    _validated_hot_water_groups: set[str] = set(),
    _section_locks: dict[str, Lock] = dict(),
    _hot_water_group_locks: dict[str, Lock] = dict(),
)

Main class for handling connections with BSBLAN.

initialize async

initialize() -> None

Initialize the BSBLAN client.

This performs minimal initialization for fast startup. Section validation is deferred until actually needed (lazy loading).

Source code in src/bsblan/bsblan.py
async def initialize(self) -> None:
    """Initialize the BSBLAN client.

    This performs minimal initialization for fast startup.
    Section validation is deferred until actually needed (lazy loading).
    """
    if not self._initialized:
        await self._fetch_firmware_version()
        await self._setup_api_validator()
        self._initialized = True

get_available_circuits async

get_available_circuits() -> list[int]

Detect which heating circuits are available on the device.

Uses a two-step probe for each circuit (1, 2): 1. Query the operating mode parameter — the response must be non-empty and contain actual data. 2. Query the status parameter (8000/8001) — an inactive circuit returns value="0" with desc="---".

A circuit is only considered available when both checks pass.

This is useful for integration setup flows (e.g., Home Assistant config flow) to discover how many circuits the user's controller supports.

Returns:

Type Description
list[int]

list[int]: Sorted list of available circuit numbers (e.g., [1, 2]).

Example

async with BSBLAN(config) as client: circuits = await client.get_available_circuits() # circuits == [1, 2] for a dual-circuit controller

Source code in src/bsblan/bsblan.py
async def get_available_circuits(self) -> list[int]:
    """Detect which heating circuits are available on the device.

    Uses a two-step probe for each circuit (1, 2):
    1. Query the operating mode parameter — the response must be
       non-empty and contain actual data.
    2. Query the status parameter (8000/8001) — an inactive
       circuit returns ``value="0"`` with ``desc="---"``.

    A circuit is only considered available when both checks pass.

    This is useful for integration setup flows (e.g., Home Assistant
    config flow) to discover how many circuits the user's controller
    supports.

    Returns:
        list[int]: Sorted list of available circuit numbers (e.g., [1, 2]).

    Example:
        async with BSBLAN(config) as client:
            circuits = await client.get_available_circuits()
            # circuits == [1, 2] for a dual-circuit controller

    """
    available: list[int] = []
    for circuit, param_id in CircuitConfig.PROBE_PARAMS.items():
        try:
            response = await self._request(
                params={"Parameter": param_id},
            )
            # A circuit exists if the response contains the param_id key
            # with actual data (not an empty dict)
            if not response.get(param_id):
                continue

            # Secondary check: query the status parameter.
            # Inactive circuits either:
            # - return value="0" and desc="---"
            # - return an empty dict {} (param not supported)
            status_id = CircuitConfig.STATUS_PARAMS[circuit]
            status_resp = await self._request(
                params={"Parameter": status_id},
            )
            status_data = status_resp.get(status_id, {})

            # Empty response means the parameter doesn't exist
            if not status_data or not isinstance(status_data, dict):
                logger.debug(
                    "Circuit %d has no status data (not supported)",
                    circuit,
                )
                continue

            # value="0" + desc="---" means inactive
            if (
                status_data.get("desc") == CircuitConfig.INACTIVE_MARKER
                and str(status_data.get("value", "")) == "0"
            ):
                logger.debug(
                    "Circuit %d has status '---' (inactive)",
                    circuit,
                )
                continue

            available.append(circuit)
        except BSBLANError:
            logger.debug(
                "Circuit %d not available (request failed)",
                circuit,
            )
    return sorted(available)

state async

state(
    include: list[str] | None = None, circuit: int = 1
) -> State

Get the current state from BSBLAN device.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all state parameters. Valid names include: hvac_mode, target_temperature, hvac_action, hvac_mode_changeover, current_temperature, room1_temp_setpoint_boost.

None
circuit int

The heating circuit number (1 or 2). Defaults to 1. Circuit 2 uses separate parameter IDs but returns the same State model with the same field names.

1

Returns:

Name Type Description
State State

The current state of the BSBLAN device.

Note

The hvac_mode.value is returned as a raw integer from the device: 0=off, 1=auto, 2=eco, 3=heat.

Example

Fetch only hvac_mode and current_temperature

state = await client.state(include=["hvac_mode", "current_temperature"])

Fetch state for heating circuit 2

state_hc2 = await client.state(circuit=2)

Source code in src/bsblan/bsblan.py
async def state(
    self,
    include: list[str] | None = None,
    circuit: int = 1,
) -> State:
    """Get the current state from BSBLAN device.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all state parameters. Valid names include:
            hvac_mode, target_temperature, hvac_action,
            hvac_mode_changeover, current_temperature,
            room1_temp_setpoint_boost.
        circuit: The heating circuit number (1 or 2). Defaults to 1.
            Circuit 2 uses separate parameter IDs but returns the
            same State model with the same field names.

    Returns:
        State: The current state of the BSBLAN device.

    Note:
        The hvac_mode.value is returned as a raw integer from the device:
        0=off, 1=auto, 2=eco, 3=heat.

    Example:
        # Fetch only hvac_mode and current_temperature
        state = await client.state(include=["hvac_mode", "current_temperature"])

        # Fetch state for heating circuit 2
        state_hc2 = await client.state(circuit=2)

    """
    self._validate_circuit(circuit)
    section: SectionLiteral = cast(
        "SectionLiteral", CircuitConfig.HEATING_SECTIONS[circuit]
    )
    return await self._fetch_section_data(section, State, include)

sensor async

sensor(include: list[str] | None = None) -> Sensor

Get the sensor information from BSBLAN device.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all sensor parameters. Valid names include: outside_temperature, current_temperature.

None

Returns:

Name Type Description
Sensor Sensor

The sensor information from the BSBLAN device.

Example

Fetch only outside_temperature

sensor = await client.sensor(include=["outside_temperature"])

Source code in src/bsblan/bsblan.py
async def sensor(self, include: list[str] | None = None) -> Sensor:
    """Get the sensor information from BSBLAN device.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all sensor parameters. Valid names include:
            outside_temperature, current_temperature.

    Returns:
        Sensor: The sensor information from the BSBLAN device.

    Example:
        # Fetch only outside_temperature
        sensor = await client.sensor(include=["outside_temperature"])

    """
    return await self._fetch_section_data("sensor", Sensor, include)

static_values async

static_values(
    include: list[str] | None = None, circuit: int = 1
) -> StaticState

Get the static information from BSBLAN device.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all static parameters. Valid names include: min_temp, max_temp.

None
circuit int

The heating circuit number (1 or 2). Defaults to 1.

1

Returns:

Name Type Description
StaticState StaticState

The static information from the BSBLAN device.

Example

Fetch only min_temp

static = await client.static_values(include=["min_temp"])

Fetch static values for heating circuit 2

static_hc2 = await client.static_values(circuit=2)

Source code in src/bsblan/bsblan.py
async def static_values(
    self,
    include: list[str] | None = None,
    circuit: int = 1,
) -> StaticState:
    """Get the static information from BSBLAN device.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all static parameters. Valid names include:
            min_temp, max_temp.
        circuit: The heating circuit number (1 or 2). Defaults to 1.

    Returns:
        StaticState: The static information from the BSBLAN device.

    Example:
        # Fetch only min_temp
        static = await client.static_values(include=["min_temp"])

        # Fetch static values for heating circuit 2
        static_hc2 = await client.static_values(circuit=2)

    """
    self._validate_circuit(circuit)
    section: SectionLiteral = cast(
        "SectionLiteral", CircuitConfig.STATIC_SECTIONS[circuit]
    )
    return await self._fetch_section_data(section, StaticState, include)

device async

device() -> Device

Get BSBLAN device info.

Returns:

Name Type Description
Device Device

The BSBLAN device information.

Source code in src/bsblan/bsblan.py
async def device(self) -> Device:
    """Get BSBLAN device info.

    Returns:
        Device: The BSBLAN device information.

    """
    device_info = await self._request(base_path="/JI")
    return Device.model_validate(device_info)

info async

info(include: list[str] | None = None) -> Info

Get information about the current heating system config.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all info parameters. Valid names include: device_identification, controller_family, controller_variant.

None

Returns:

Name Type Description
Info Info

The information about the current heating system config.

Example

Fetch only device_identification

info = await client.info(include=["device_identification"])

Source code in src/bsblan/bsblan.py
async def info(self, include: list[str] | None = None) -> Info:
    """Get information about the current heating system config.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all info parameters. Valid names include:
            device_identification, controller_family, controller_variant.

    Returns:
        Info: The information about the current heating system config.

    Example:
        # Fetch only device_identification
        info = await client.info(include=["device_identification"])

    """
    return await self._fetch_section_data("device", Info, include)

time async

time() -> DeviceTime

Get the current time from the BSB-LAN device.

Returns:

Name Type Description
DeviceTime DeviceTime

The current time information from the BSB-LAN device.

Source code in src/bsblan/bsblan.py
async def time(self) -> DeviceTime:
    """Get the current time from the BSB-LAN device.

    Returns:
        DeviceTime: The current time information from the BSB-LAN device.

    """
    # Get only parameter 0 for time
    data = await self._request(params={"Parameter": "0"})
    # Create the data dictionary in the expected format
    time_data = {"time": data["0"]}
    return DeviceTime.model_validate(time_data)

set_time async

set_time(time_value: str) -> None

Set the time on the BSB-LAN device.

Parameters:

Name Type Description Default
time_value str

The time value to set in format "DD.MM.YYYY HH:MM:SS" (e.g., "13.08.2025 10:25:55").

required

Raises:

Type Description
BSBLANInvalidParameterError

If the time format is invalid.

Source code in src/bsblan/bsblan.py
async def set_time(self, time_value: str) -> None:
    """Set the time on the BSB-LAN device.

    Args:
        time_value (str): The time value to set in format "DD.MM.YYYY HH:MM:SS"
            (e.g., "13.08.2025 10:25:55").

    Raises:
        BSBLANInvalidParameterError: If the time format is invalid.

    """
    self._validate_time_format(time_value)
    state: dict[str, object] = {
        "Parameter": "0",
        "Value": time_value,
        "Type": "1",
    }
    response = await self._request(base_path="/JS", data=state)
    logger.debug("Response for setting time: %s", response)

thermostat async

thermostat(
    target_temperature: str | None = None,
    hvac_mode: int | None = None,
    circuit: int = 1,
) -> None

Change the state of the thermostat through BSB-Lan.

Parameters:

Name Type Description Default
target_temperature str | None

The target temperature to set.

None
hvac_mode int | None

The HVAC mode to set as raw integer value. Valid values: 0=off, 1=auto, 2=eco, 3=heat.

None
circuit int

The heating circuit number (1 or 2). Defaults to 1.

1
Example

Set HC1 temperature

await client.thermostat(target_temperature="21.0")

Set HC2 mode

await client.thermostat(hvac_mode=1, circuit=2)

Source code in src/bsblan/bsblan.py
async def thermostat(
    self,
    target_temperature: str | None = None,
    hvac_mode: int | None = None,
    circuit: int = 1,
) -> None:
    """Change the state of the thermostat through BSB-Lan.

    Args:
        target_temperature (str | None): The target temperature to set.
        hvac_mode (int | None): The HVAC mode to set as raw integer value.
            Valid values: 0=off, 1=auto, 2=eco, 3=heat.
        circuit: The heating circuit number (1 or 2). Defaults to 1.

    Example:
        # Set HC1 temperature
        await client.thermostat(target_temperature="21.0")

        # Set HC2 mode
        await client.thermostat(hvac_mode=1, circuit=2)

    """
    self._validate_circuit(circuit)
    await self._initialize_temperature_range(circuit)

    self._validate_single_parameter(
        target_temperature,
        hvac_mode,
        error_msg=ErrorMsg.MULTI_PARAMETER,
    )

    state = await self._prepare_thermostat_state(
        target_temperature,
        hvac_mode,
        circuit,
    )
    await self._set_device_state(state)

hot_water_state async

hot_water_state(
    include: list[str] | None = None,
) -> HotWaterState

Get essential hot water state for frequent polling.

This method returns only the most important hot water parameters that are typically checked frequently for monitoring purposes. This reduces API calls and improves performance for regular polling.

Uses granular lazy loading - only validates the 5 essential params, not all 29 hot water parameters.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all essential hot water parameters. Valid names include: operating_mode, nominal_setpoint, release, dhw_actual_value_top_temperature, state_dhw_pump.

None

Returns:

Name Type Description
HotWaterState HotWaterState

Essential hot water state information.

Example

Fetch only operating_mode and nominal_setpoint

state = await client.hot_water_state( include=["operating_mode", "nominal_setpoint"] )

Source code in src/bsblan/bsblan.py
async def hot_water_state(self, include: list[str] | None = None) -> HotWaterState:
    """Get essential hot water state for frequent polling.

    This method returns only the most important hot water parameters
    that are typically checked frequently for monitoring purposes.
    This reduces API calls and improves performance for regular polling.

    Uses granular lazy loading - only validates the 5 essential params,
    not all 29 hot water parameters.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all essential hot water parameters. Valid names include:
            operating_mode, nominal_setpoint, release,
            dhw_actual_value_top_temperature, state_dhw_pump.

    Returns:
        HotWaterState: Essential hot water state information.

    Example:
        # Fetch only operating_mode and nominal_setpoint
        state = await client.hot_water_state(
            include=["operating_mode", "nominal_setpoint"]
        )

    """
    return await self._fetch_hot_water_data(
        param_filter=HotWaterParams.ESSENTIAL,
        model_class=HotWaterState,
        error_msg="No essential hot water parameters available",
        group_name="essential",
        include=include,
    )

hot_water_config async

hot_water_config(
    include: list[str] | None = None,
) -> HotWaterConfig

Get hot water configuration and advanced settings.

This method returns configuration parameters that are typically set once and checked less frequently.

Uses granular lazy loading - only validates the 16 config params.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all config hot water parameters. Valid names include: eco_mode_selection, nominal_setpoint_max, reduced_setpoint, dhw_charging_priority, operating_mode_changeover, legionella_function, legionella_function_setpoint, legionella_function_periodicity, legionella_function_day, legionella_function_time, legionella_function_dwelling_time, legionella_circulation_pump, legionella_circulation_temp_diff, dhw_circulation_pump_release, dhw_circulation_pump_cycling, dhw_circulation_setpoint.

None

Returns:

Name Type Description
HotWaterConfig HotWaterConfig

Hot water configuration information.

Example

Fetch only legionella settings

config = await client.hot_water_config( include=["legionella_function", "legionella_function_setpoint"] )

Source code in src/bsblan/bsblan.py
async def hot_water_config(
    self, include: list[str] | None = None
) -> HotWaterConfig:
    """Get hot water configuration and advanced settings.

    This method returns configuration parameters that are typically
    set once and checked less frequently.

    Uses granular lazy loading - only validates the 16 config params.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all config hot water parameters. Valid names include:
            eco_mode_selection, nominal_setpoint_max, reduced_setpoint,
            dhw_charging_priority, operating_mode_changeover,
            legionella_function, legionella_function_setpoint,
            legionella_function_periodicity, legionella_function_day,
            legionella_function_time, legionella_function_dwelling_time,
            legionella_circulation_pump, legionella_circulation_temp_diff,
            dhw_circulation_pump_release, dhw_circulation_pump_cycling,
            dhw_circulation_setpoint.

    Returns:
        HotWaterConfig: Hot water configuration information.

    Example:
        # Fetch only legionella settings
        config = await client.hot_water_config(
            include=["legionella_function", "legionella_function_setpoint"]
        )

    """
    return await self._fetch_hot_water_data(
        param_filter=HotWaterParams.CONFIG,
        model_class=HotWaterConfig,
        error_msg="No hot water configuration parameters available",
        group_name="config",
        include=include,
    )

hot_water_schedule async

hot_water_schedule(
    include: list[str] | None = None,
) -> HotWaterSchedule

Get hot water time program schedules.

This method returns time program settings that are typically configured once and rarely changed.

Uses granular lazy loading - only validates the 8 schedule params.

Parameters:

Name Type Description Default
include list[str] | None

Optional list of parameter names to fetch. If None, fetches all schedule parameters. Valid names include: dhw_time_program_monday, dhw_time_program_tuesday, dhw_time_program_wednesday, dhw_time_program_thursday, dhw_time_program_friday, dhw_time_program_saturday, dhw_time_program_sunday, dhw_time_program_standard_values.

None

Returns:

Name Type Description
HotWaterSchedule HotWaterSchedule

Hot water schedule information.

Example

Fetch only Monday's schedule

schedule = await client.hot_water_schedule( include=["dhw_time_program_monday"] )

Source code in src/bsblan/bsblan.py
async def hot_water_schedule(
    self, include: list[str] | None = None
) -> HotWaterSchedule:
    """Get hot water time program schedules.

    This method returns time program settings that are typically
    configured once and rarely changed.

    Uses granular lazy loading - only validates the 8 schedule params.

    Args:
        include: Optional list of parameter names to fetch. If None,
            fetches all schedule parameters. Valid names include:
            dhw_time_program_monday, dhw_time_program_tuesday,
            dhw_time_program_wednesday, dhw_time_program_thursday,
            dhw_time_program_friday, dhw_time_program_saturday,
            dhw_time_program_sunday, dhw_time_program_standard_values.

    Returns:
        HotWaterSchedule: Hot water schedule information.

    Example:
        # Fetch only Monday's schedule
        schedule = await client.hot_water_schedule(
            include=["dhw_time_program_monday"]
        )

    """
    return await self._fetch_hot_water_data(
        param_filter=HotWaterParams.SCHEDULE,
        model_class=HotWaterSchedule,
        error_msg="No hot water schedule parameters available",
        group_name="schedule",
        include=include,
    )

set_hot_water async

set_hot_water(params: SetHotWaterParam) -> None

Change the state of the hot water system through BSB-Lan.

Only one parameter should be set at a time (BSB-LAN API limitation).

Example

params = SetHotWaterParam(nominal_setpoint=55.0) await client.set_hot_water(params)

Parameters:

Name Type Description Default
params SetHotWaterParam

SetHotWaterParam object containing the parameter to set.

required

Raises:

Type Description
BSBLANError

If multiple parameters are set or no parameter is set.

Source code in src/bsblan/bsblan.py
async def set_hot_water(self, params: SetHotWaterParam) -> None:
    """Change the state of the hot water system through BSB-Lan.

    Only one parameter should be set at a time (BSB-LAN API limitation).

    Example:
        params = SetHotWaterParam(nominal_setpoint=55.0)
        await client.set_hot_water(params)

    Args:
        params: SetHotWaterParam object containing the parameter to set.

    Raises:
        BSBLANError: If multiple parameters are set or no parameter is set.

    """
    # Validate only one parameter is being set
    time_program_params: list[str] = []
    if params.dhw_time_programs:
        programs = params.dhw_time_programs
        time_program_params.extend(
            prog
            for prog in [
                programs.monday,
                programs.tuesday,
                programs.wednesday,
                programs.thursday,
                programs.friday,
                programs.saturday,
                programs.sunday,
                programs.standard_values,
            ]
            if prog
        )

    self._validate_single_parameter(
        params.nominal_setpoint,
        params.reduced_setpoint,
        params.nominal_setpoint_max,
        params.operating_mode,
        params.eco_mode_selection,
        params.dhw_charging_priority,
        params.legionella_function_setpoint,
        params.legionella_function_periodicity,
        params.legionella_function_day,
        params.legionella_function_time,
        params.legionella_function_dwelling_time,
        params.operating_mode_changeover,
        *time_program_params,
        error_msg=ErrorMsg.MULTI_PARAMETER,
    )

    state = self._prepare_hot_water_state(params)
    await self._set_device_state(state)

set_hot_water_schedule async

set_hot_water_schedule(schedule: DHWSchedule) -> None

Set hot water time program schedules.

This method allows setting weekly DHW schedules using a type-safe interface with TimeSlot and DaySchedule objects.

Example

schedule = DHWSchedule( monday=DaySchedule(slots=[ TimeSlot(time(6, 0), time(8, 0)), TimeSlot(time(17, 0), time(21, 0)), ]), tuesday=DaySchedule(slots=[ TimeSlot(time(6, 0), time(8, 0)), ]) ) await client.set_hot_water_schedule(schedule)

Parameters:

Name Type Description Default
schedule DHWSchedule

DHWSchedule object containing the weekly schedule.

required

Raises:

Type Description
BSBLANError

If no schedule is provided.

Source code in src/bsblan/bsblan.py
async def set_hot_water_schedule(self, schedule: DHWSchedule) -> None:
    """Set hot water time program schedules.

    This method allows setting weekly DHW schedules using a type-safe
    interface with TimeSlot and DaySchedule objects.

    Example:
        schedule = DHWSchedule(
            monday=DaySchedule(slots=[
                TimeSlot(time(6, 0), time(8, 0)),
                TimeSlot(time(17, 0), time(21, 0)),
            ]),
            tuesday=DaySchedule(slots=[
                TimeSlot(time(6, 0), time(8, 0)),
            ])
        )
        await client.set_hot_water_schedule(schedule)

    Args:
        schedule: DHWSchedule object containing the weekly schedule.

    Raises:
        BSBLANError: If no schedule is provided.

    """
    if not schedule.has_any_schedule():
        raise BSBLANError(ErrorMsg.NO_SCHEDULE)

    # Invert DHW_TIME_PROGRAM_PARAMS to get day_name -> param_id mapping
    # Exclude standard_values as it's not a day of the week
    day_param_map = {
        v: k
        for k, v in HotWaterParams.TIME_PROGRAMS.items()
        if v != "standard_values"
    }

    for day_name, param_id in day_param_map.items():
        day_schedule: DaySchedule | None = getattr(schedule, day_name)
        if day_schedule is not None:
            state = {
                "Parameter": param_id,
                "Value": day_schedule.to_bsblan_format(),
                "Type": "1",
            }
            await self._set_device_state(state)

BSBLANConfig

bsblan.BSBLANConfig dataclass

BSBLANConfig(
    host: str,
    username: str | None = None,
    password: str | None = None,
    passkey: str | None = None,
    port: int = 80,
    request_timeout: int = 10,
)

Configuration for BSBLAN.