Skip to content

Writing indoor temperature setpoints via /points endpoint silently fails — should use /zones/{zoneId} endpoint #222

Description

@tangyyylambert

The problem

When trying to change room temperature setpoints (parameters with sh-indoorSpHeat smart home category, e.g. parameter IDs 47751, 47752) via the integration, the API responds with "modified" but the value is never actually applied on the heat pump.

This affects NIBE SMO S40 (and likely other devices with zone-based climate systems).

Root cause

The integration uses the PATCH /v2/devices/{deviceId}/points endpoint for all writable parameters. However, for zone-based temperature setpoints, this endpoint silently accepts the request but does not apply the change.

The myUplink API has a dedicated endpoint for this: PATCH /v2/devices/{deviceId}/zones/{zoneId} which works correctly.

How I confirmed this

1. PATCH via /points — fails silently

curl -X PATCH "https://api.myuplink.com/v2/devices/{deviceId}/points" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"47752":"22"}'

Response: {"47752": "modified"} — but a subsequent GET still returns the old value, and the heat pump is unchanged.

2. PATCH via /zones/{zoneId} — works

curl -X PATCH "https://api.myuplink.com/v2/devices/{deviceId}/zones/3" \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"setpointHeat": 22.0}'

This actually changes the setpoint on the heat pump and is confirmed via GET on /smart-home-zones.

3. Zone data from the API

GET /v2/devices/{deviceId}/smart-home-zones returns:

[
    {
        "zoneId": "2",
        "name": "RDC",
        "mode": "heat",
        "temperature": 20.7,
        "setpointHeat": 20.0,
        "setpointRangeMin": 15,
        "setpointRangeMax": 25,
        "isCelsius": true
    },
    {
        "zoneId": "3",
        "name": "Etage",
        "mode": "heat",
        "temperature": 20.5,
        "setpointHeat": 20.0,
        "setpointRangeMin": 15,
        "setpointRangeMax": 25,
        "isCelsius": true
    }
]

The zone name matches the parameter category field (e.g. "RDC", "Etage").

What I noticed in the code

The integration already has:

  • A Zone class fully implemented in api.py
  • A get_zones() method that calls /smart-home-zones
  • A zones list on the Device class

However:

  • The call to get_zones() is commented out in Device.async_fetch_data():
    # self.zones = await self.system.api.get_zones(self.id)
  • There is no patch_zone() method — only patch_parameter() which uses /points

Proposed solution

1. Add a patch_zone method to the MyUplink class:

async def patch_zone(self, device_id, zone_id: str, setpoint_heat: float) -> bool:
    """Update the setpoint of a zone for a device."""
    _LOGGER.debug(
        "Patch zone %s for device %s with setpointHeat %s",
        zone_id, device_id, setpoint_heat,
    )
    async with self.lock, self.throttle:
        resp = await self.auth.request(
            "patch",
            f"devices/{device_id}/zones/{zone_id}",
            data=json.dumps({"setpointHeat": setpoint_heat}),
            headers={"Content-Type": "application/json"},
        )
    resp.raise_for_status()
    return resp.status == 200

2. Update Parameter.update_parameter() to route zone parameters:

async def update_parameter(self, value) -> None:
    """Patch parameter if writable."""
    if not self.is_writable:
        return
    if "sh-indoorSpHeat" in self.smart_home_categories and self.category:
        zones = await self.device.system.api.get_zones(self.device.id)
        for zone in zones:
            if zone.name == self.category:
                await self.device.system.api.patch_zone(
                    self.device.id, str(zone.id), float(value)
                )
                return
    await self.device.system.api.patch_parameter(
        self.device.id, str(self.id), str(value)
    )

The logic: when a parameter has the sh-indoorSpHeat smart home category, we look up the matching zone by name (which corresponds to the parameter's category field), and use the zones endpoint instead of the points endpoint.

Environment

  • Device: NIBE SMO S40
  • myUplink subscription: Premium (manage)
  • Integration version: 1.7.3
  • Home Assistant: 2025.x

Additional context

Other writable parameters (e.g. 27233 Quick water heating) work fine via /points. The issue is specifically with zone-based temperature setpoints that require the /zones/{zoneId} endpoint.

The Swagger documentation at https://api.myuplink.com/swagger/index.html confirms both endpoints exist:

  • PATCH /v2/devices/{deviceId}/points — for general parameter updates
  • PATCH /v2/devices/{deviceId}/zones/{zoneId} — for zone-specific updates

Thank you for this great integration! Happy to submit a PR if you'd prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions