Skip to content

Commit

Permalink
battery: Interpolate battery level in percent
Browse files Browse the repository at this point in the history
Estimate remaining battery based on measured battery voltage. Use linear
interpolation to achieve a smooth line instead of 10 percent jumps.

Fixes #2744
  • Loading branch information
MattHag committed Jan 1, 2025
1 parent 5d5a5ad commit ec0cc6a
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 55 deletions.
63 changes: 38 additions & 25 deletions lib/logitech_receiver/hidpp20.py
Original file line number Diff line number Diff line change
Expand Up @@ -1503,25 +1503,6 @@ def feature_request(device, feature, function=0x00, *params, no_reply=False):
return device.request((feature_index << 8) + (function & 0xFF), *params, no_reply=no_reply)


# voltage to remaining charge from Logitech
battery_voltage_remaining = (
(4186, 100),
(4067, 90),
(3989, 80),
(3922, 70),
(3859, 60),
(3811, 50),
(3778, 40),
(3751, 30),
(3717, 20),
(3671, 10),
(3646, 5),
(3579, 2),
(3500, 0),
(-1000, 0),
)


class Hidpp20:
def get_firmware(self, device) -> tuple[common.FirmwareInfo] | None:
"""Reads a device's firmware info.
Expand Down Expand Up @@ -1962,9 +1943,41 @@ def decipher_adc_measurement(report) -> tuple[SupportedFeature, Battery]:


def estimate_battery_level_percentage(value_millivolt: int) -> int | None:
charge_level = None
for level in battery_voltage_remaining:
if level[0] < value_millivolt:
charge_level = level[1]
break
return charge_level
"""Estimate battery level percentage based on battery voltage.
Uses linear approximation to estimate the battery level in percent.
Parameters
----------
value_millivolt
Measured battery voltage in millivolt.
"""
battery_voltage_to_percentage = [
(4186, 100),
(4067, 90),
(3989, 80),
(3922, 70),
(3859, 60),
(3811, 50),
(3778, 40),
(3751, 30),
(3717, 20),
(3671, 10),
(3646, 5),
(3579, 2),
(3500, 0),
]

if value_millivolt >= battery_voltage_to_percentage[0][0]:
return battery_voltage_to_percentage[0][1]
if value_millivolt <= battery_voltage_to_percentage[-1][0]:
return battery_voltage_to_percentage[-1][1]

for i in range(len(battery_voltage_to_percentage) - 1):
v_high, p_high = battery_voltage_to_percentage[i]
v_low, p_low = battery_voltage_to_percentage[i + 1]
if v_low <= value_millivolt <= v_high:
# Linear interpolation
percent = p_low + (p_high - p_low) * (value_millivolt - v_low) / (v_high - v_low)
return round(percent)
return 0
51 changes: 21 additions & 30 deletions tests/logitech_receiver/test_hidpp20_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_get_battery_voltage():
feature, battery = _hidpp20.get_battery_voltage(device)

assert feature == SupportedFeature.BATTERY_VOLTAGE
assert battery.level == 90
assert battery.level == 92
assert common.BatteryStatus.RECHARGING in battery.status
assert battery.voltage == 0x1000

Expand All @@ -130,7 +130,7 @@ def test_get_adc_measurement():
feature, battery = _hidpp20.get_adc_measurement(device)

assert feature == SupportedFeature.ADC_MEASUREMENT
assert battery.level == 90
assert battery.level == 92
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000

Expand Down Expand Up @@ -389,7 +389,7 @@ def test_decipher_battery_voltage():
feature, battery = hidpp20.decipher_battery_voltage(report)

assert feature == SupportedFeature.BATTERY_VOLTAGE
assert battery.level == 90
assert battery.level == 92
assert common.BatteryStatus.RECHARGING in battery.status
assert battery.voltage == 0x1000

Expand All @@ -410,7 +410,7 @@ def test_decipher_adc_measurement():
feature, battery = hidpp20.decipher_adc_measurement(report)

assert feature == SupportedFeature.ADC_MEASUREMENT
assert battery.level == 90
assert battery.level == 92
assert battery.status == common.BatteryStatus.RECHARGING
assert battery.voltage == 0x1000

Expand Down Expand Up @@ -454,36 +454,27 @@ def test_led_zone_locations(code, expected_name):
@pytest.mark.parametrize(
"millivolt, expected_percentage",
[
(-1234, None),
(-1234, 0),
(500, 0),
(2000, 0),
(3500, 0),
(3501, 0),
(3519, 0),
(3520, 0),
(3579, 0),
(3646, 2),
(3580, 2),
(3671, 5),
(3672, 10),
(3717, 10),
(3718, 20),
(3751, 20),
(3752, 30),
(3778, 30),
(3779, 40),
(3811, 40),
(3812, 50),
(3859, 50),
(3860, 60),
(3922, 60),
(3923, 70),
(3989, 70),
(3990, 80),
(4067, 80),
(4068, 90),
(4186, 90),
(4187, 100),
(3520, 1),
(3559, 1),
(3579, 2),
(3646, 5),
(3671, 10),
(3717, 20),
(3751, 30),
(3778, 40),
(3811, 50),
(3859, 60),
(3922, 70),
(3989, 80),
(4067, 90),
(4180, 99),
(4181, 100),
(4186, 100),
(4500, 100),
],
)
Expand Down

0 comments on commit ec0cc6a

Please sign in to comment.