Skip to content

Feature Request: Expose per-battery data for dual-battery devices #523

@sbeitzel

Description

@sbeitzel

Feature Request: Expose per-battery data for dual-battery devices

Summary

Add a batteries property to RingDoorBell that returns structured per-battery information
for devices with one or more battery bays (e.g. Spotlight Cam Battery, Stickup Cam). The
existing battery_life property has a correctness bug for dual-battery devices that this
change would also fix.

Background / motivation

Several Ring devices (Spotlight Cam Battery, Stickup Cam, and others) have two battery bays
so that one battery can be swapped while the device stays powered. The Ring API already returns
rich per-battery data at two levels:

Top-level _attrs fields

"battery_life":   "12",
"battery_life_2": "100"

health sub-object (richer)

"batteries": [
  {
    "battery_number": 1,
    "battery_percentage": 12,
    "battery_voltage": 3562.0,
    "battery_percentage_category": "very_poor",
    "battery_voltage_category": "good"
  },
  {
    "battery_number": 2,
    "battery_percentage": 100,
    "battery_voltage": 4144.0,
    "battery_percentage_category": "very_good",
    "battery_voltage_category": "very_good"
  }
],
"active_battery": 1

Current battery_life bug

doorbot.py:158-173 sums both percentages and caps at 100:

value = 0
if bl1:
    value += int(bl1)
if bl2 := self._attrs.get("battery_life_2"):
    value += int(bl2)
return min(value, 100)

For battery_1 = 12 %, battery_2 = 100 %, this returns min(112, 100) = 100, hiding that the
active battery is nearly dead.

battery_life_2 is also not exposed as a public property at all, so library consumers have no
way to surface the second battery without reaching into private state.

Proposed change

1. Add a batteries property

Return the structured list from health.batteries. On single-battery devices this list has one
entry; on dual-battery devices it has two. This makes the API future-proof for any number of
battery bays.

@property
def batteries(self) -> list[dict] | None:
    """Return per-battery data from the health payload, or None if unavailable."""
    health = self._attrs.get("health", {})
    return health.get("batteries") or None

Suggested return shape (matches what the API already provides):

[
    {
        "battery_number": 1,
        "battery_percentage": 12,
        "battery_voltage": 3562.0,
        "battery_percentage_category": "very_poor",
        "battery_voltage_category": "good",
    },
    ...
]

Also consider exposing health.get("active_battery") as a companion property so consumers
can surface which bay is currently powering the device.

2. Fix battery_life for dual-battery devices

Change battery_life to return the active battery's percentage (i.e. _attrs["battery_life"]
directly) instead of the broken sum. The active battery is always reported as battery_life at
the top level, so the fix is simply:

@property
def battery_life(self) -> int | None:
    """Return battery life."""
    bl = self._attrs.get("battery_life")
    if bl is None and "battery_life_2" not in self._attrs:
        return None
    return int(bl) if bl is not None else None

This is a bug fix with a narrow breaking-change risk: integrations that relied on the
summed/capped value would see a lower (but correct) number for dual-battery devices.

Why not just add a battery_life_2 property?

A second flat property works for today's two-bay devices but doesn't generalise and still
requires consumers to implement their own "which batteries are present?" logic. The batteries
list from health is already structured, supports any count, and includes voltage and category
information that a flat property cannot carry. The list is the right public API.

Downstream benefit

Home Assistant's Ring integration currently exposes a single battery sensor. With a batteries
property (and a corrected battery_life) it can:

  • Show one sensor per battery bay, dynamically, with no magic numbers.
  • Expose the active_battery index as a device attribute.
  • Correctly warn users when the active battery is critically low even if the reserve is full.

Acceptance criteria

  • RingDoorBell.batteries returns a list[dict] (one entry per bay) when health.batteries
    is present, and None otherwise.
  • Each dict includes at minimum: battery_number, battery_percentage, battery_voltage.
  • battery_life returns the active battery's percentage (not the sum) for dual-battery devices.
  • Single-battery devices are unaffected: battery_life continues to work; batteries returns
    a one-element list.
  • New properties are covered by tests (mock payloads for single- and dual-battery devices).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions