* [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level
@ 2025-11-14 12:11 Roman Smirnov
2025-11-14 12:11 ` [PATCH BlueZ v7 2/2] unit: Add basic unit tests for battery charge handling Roman Smirnov
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Roman Smirnov @ 2025-11-14 12:11 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Roman Smirnov
The battery charge level may fluctuate due to uncalibrated
sensors. Commit smooths out such fluctuations.
The algorithm for determining uncalibrated sensors consists of
finding the number of changes in charge direction (i.e., "spikes").
If the number of spikes is zero, the device is charging or discharging.
If there is one spike, it may mean that the device has started charging
or has been disconnected from charging. If there are two or more spikes,
this is a clear indication of an uncalibrated sensor.
Check that the battery charge is fluctuating. If the battery charge
is fluctuating, use the average charge value, otherwise use the current
value.
Fixes: https://github.com/bluez/bluez/issues/1612
---
V6 -> V7: rename the bt_battery_filter() function to bt_battery_charge()
V4 -> V5: Initialize the next_charge variable to pass the tests.
V3 -> V4: Functionality has been moved to separate files, unit tests
have been added in the next patch, simple averaging of values is
used instead of exponential averaging, and last_charges now stores
values instead of pointers.
V2 -> V3: A queue is used instead of an array for the last charges,
a bug with the spikes variable increment has been fixed, and the
fluctuation check is called each time a new battery charge appears.
V1 -> V2: Smoothing is only applied to uncalibrated sensors, the
last 8 values are saved instead of 4, and the average value is used
for smoothing instead of the minimum value.
Makefile.am | 3 +-
src/battery.c | 10 ++++-
src/shared/battery.c | 96 ++++++++++++++++++++++++++++++++++++++++++++
src/shared/battery.h | 21 ++++++++++
4 files changed, 128 insertions(+), 2 deletions(-)
create mode 100644 src/shared/battery.c
create mode 100644 src/shared/battery.h
diff --git a/Makefile.am b/Makefile.am
index 94f625db4..4bf859685 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -244,7 +244,8 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
src/shared/ccp.h src/shared/ccp.c \
src/shared/lc3.h src/shared/tty.h \
src/shared/bap-defs.h \
- src/shared/asha.h src/shared/asha.c
+ src/shared/asha.h src/shared/asha.c \
+ src/shared/battery.h src/shared/battery.c
if READLINE
shared_sources += src/shared/shell.c src/shared/shell.h
diff --git a/src/battery.c b/src/battery.c
index 4c1ea79d9..fa30fde47 100644
--- a/src/battery.c
+++ b/src/battery.c
@@ -19,6 +19,7 @@
#include "gdbus/gdbus.h"
#include "bluetooth/bluetooth.h"
+#include "src/shared/battery.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
#include "battery.h"
@@ -39,6 +40,7 @@ struct btd_battery {
uint8_t percentage; /* valid between 0 to 100 inclusively */
char *source; /* Descriptive source of the battery info */
char *provider_path; /* The provider root path, if any */
+ struct bt_battery *filter;
};
struct btd_battery_provider_manager {
@@ -96,6 +98,7 @@ static struct btd_battery *battery_new(const char *path, const char *source,
battery->source = g_strdup(source);
if (provider_path)
battery->provider_path = g_strdup(provider_path);
+ battery->filter = bt_battery_new();
return battery;
}
@@ -108,6 +111,11 @@ static void battery_free(struct btd_battery *battery)
if (battery->source)
g_free(battery->source);
+ if (battery->filter) {
+ bt_battery_free(battery->filter);
+ free(battery->filter);
+ }
+
free(battery);
}
@@ -234,7 +242,7 @@ bool btd_battery_update(struct btd_battery *battery, uint8_t percentage)
if (battery->percentage == percentage)
return true;
- battery->percentage = percentage;
+ battery->percentage = bt_battery_charge(battery->filter, percentage);
g_dbus_emit_property_changed(btd_get_dbus_connection(), battery->path,
BATTERY_INTERFACE, "Percentage");
diff --git a/src/shared/battery.c b/src/shared/battery.c
new file mode 100644
index 000000000..33cec3a8c
--- /dev/null
+++ b/src/shared/battery.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Open Mobile Platform LLC <community@omp.ru>
+ *
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "src/shared/battery.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+
+struct bt_battery {
+ struct queue *last_charges; /* last charges received */
+ uint8_t avg_charge; /* average battery charge */
+ bool is_fluctuating; /* true, if the battery sensor fluctuates */
+};
+
+struct bt_battery *bt_battery_new(void)
+{
+ struct bt_battery *battery;
+
+ battery = new0(struct bt_battery, 1);
+ battery->last_charges = queue_new();
+ battery->avg_charge = 0;
+ battery->is_fluctuating = false;
+
+ return battery;
+}
+
+void bt_battery_free(struct bt_battery *battery)
+{
+ if (battery->last_charges)
+ queue_destroy(battery->last_charges, NULL);
+}
+
+static void bt_battery_check_fluctuations(struct bt_battery *battery)
+{
+ const struct queue_entry *entry;
+ uint8_t spikes = 0;
+ int8_t step;
+ int8_t direction = 0;
+ int8_t prev_direction;
+ uintptr_t prev_charge;
+ uintptr_t next_charge = 0;
+ uint16_t sum_charge = 0;
+
+ for (entry = queue_get_entries(battery->last_charges); entry->next;
+ entry = entry->next) {
+ prev_direction = direction;
+ prev_charge = PTR_TO_UINT(entry->data);
+ next_charge = PTR_TO_UINT(entry->next->data);
+ step = next_charge - prev_charge;
+ sum_charge += prev_charge;
+
+ /*
+ * The battery charge fluctuates too much,
+ * which may indicate a battery problem, so
+ * the actual value should be displayed.
+ */
+ if (abs(step) >= MAX_CHARGE_STEP) {
+ battery->is_fluctuating = false;
+ return;
+ }
+
+ if (step > 0)
+ direction = 1;
+ else if (step < 0)
+ direction = -1;
+
+ if (direction != prev_direction && prev_direction)
+ spikes++;
+ }
+
+ sum_charge += next_charge;
+ battery->avg_charge = sum_charge / LAST_CHARGES_SIZE;
+
+ battery->is_fluctuating = (spikes > 1) ? true : false;
+}
+
+uint8_t bt_battery_charge(struct bt_battery *battery, uint8_t percentage)
+{
+ queue_push_tail(battery->last_charges, UINT_TO_PTR(percentage));
+
+ if (queue_length(battery->last_charges) == LAST_CHARGES_SIZE) {
+ bt_battery_check_fluctuations(battery);
+ queue_pop_head(battery->last_charges);
+ }
+
+ return (battery->is_fluctuating) ? battery->avg_charge : percentage;
+}
diff --git a/src/shared/battery.h b/src/shared/battery.h
new file mode 100644
index 000000000..457acbc63
--- /dev/null
+++ b/src/shared/battery.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Open Mobile Platform LLC <community@omp.ru>
+ *
+ *
+ */
+
+#include <stdint.h>
+
+#define LAST_CHARGES_SIZE 8
+#define MAX_CHARGE_STEP 5
+
+struct bt_battery;
+
+struct bt_battery *bt_battery_new(void);
+void bt_battery_free(struct bt_battery *battery);
+
+uint8_t bt_battery_charge(struct bt_battery *battery, uint8_t percentage);
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH BlueZ v7 2/2] unit: Add basic unit tests for battery charge handling
2025-11-14 12:11 [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level Roman Smirnov
@ 2025-11-14 12:11 ` Roman Smirnov
2025-11-14 13:45 ` [BlueZ,v7,1/2] shared/battery: improve the display of the charge level bluez.test.bot
2025-11-17 16:30 ` [PATCH BlueZ v7 1/2] " patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: Roman Smirnov @ 2025-11-14 12:11 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Roman Smirnov
---
V5 -> V6: Add stdlib.h header file.
.gitignore | 1 +
Makefile.am | 6 ++
doc/test-coverage.txt | 3 +-
unit/test-battery.c | 224 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 233 insertions(+), 1 deletion(-)
create mode 100644 unit/test-battery.c
diff --git a/.gitignore b/.gitignore
index d23a06af4..784fc77db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -117,6 +117,7 @@ unit/test-ecc
unit/test-hog
unit/test-bap
unit/test-bass
+unit/test-battery
tools/mgmt-tester
tools/smp-tester
tools/gap-tester
diff --git a/Makefile.am b/Makefile.am
index 4bf859685..deb59fad3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -701,6 +701,12 @@ unit_test_vcp_SOURCES = unit/test-vcp.c $(btio_sources)
unit_test_vcp_LDADD = src/libshared-glib.la \
lib/libbluetooth-internal.la $(GLIB_LIBS)
+unit_tests += unit/test-battery
+
+unit_test_battery_SOURCES = unit/test-battery.c
+unit_test_battery_LDADD = src/libshared-glib.la \
+ lib/libbluetooth-internal.la $(GLIB_LIBS)
+
if MIDI
unit_tests += unit/test-midi
unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST
diff --git a/doc/test-coverage.txt b/doc/test-coverage.txt
index 741492a3e..b92a2ae59 100644
--- a/doc/test-coverage.txt
+++ b/doc/test-coverage.txt
@@ -30,8 +30,9 @@ test-gobex-transfer 36 OBEX transfer handling
test-gdbus-client 13 D-Bus client handling
test-gatt 180 GATT qualification test cases
test-hog 6 HID Over GATT qualification test cases
+test-battery 10 Battery charge test cases
-----
- 761
+ 771
Automated end-to-end testing
diff --git a/unit/test-battery.c b/unit/test-battery.c
new file mode 100644
index 000000000..eab1ebdd7
--- /dev/null
+++ b/unit/test-battery.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2025 Open Mobile Platform LLC <community@omp.ru>
+ *
+ *
+ */
+
+#include <glib.h>
+#include <stdlib.h>
+
+#include "src/shared/battery.h"
+#include "src/shared/tester.h"
+
+#define DATA_SIZE 10
+
+static uint8_t calculate_average(const uint8_t *charges)
+{
+ uint16_t average = 0;
+
+ for (int i = DATA_SIZE - LAST_CHARGES_SIZE; i < DATA_SIZE; i++)
+ average += charges[i];
+
+ return average / LAST_CHARGES_SIZE;
+}
+
+static uint8_t process_data(struct bt_battery *battery, uint8_t *charges)
+{
+ uint8_t battery_avg;
+
+ for (int i = 0; i < DATA_SIZE; i++)
+ battery_avg = bt_battery_charge(battery, charges[i]);
+
+ return battery_avg;
+}
+
+static void test_discharging(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 84, 83, 83, 81, 80, 80, 80, 79, 79, 78 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_charging(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 48, 48, 48, 49, 49, 50, 51, 51, 51, 53 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_discharge_started(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 48, 48, 49, 50, 51, 51, 49, 48, 47, 45 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_charge_started(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 57, 57, 56, 56, 55, 54, 55, 57, 57, 58 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_fluctuations(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 74, 73, 75, 72, 74, 72, 73, 71, 75, 73 };
+ uint8_t processed_charge, average;
+
+ average = calculate_average(charges);
+ processed_charge = process_data(battery, charges);
+
+ g_assert(processed_charge == average);
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_fluctuations_with_anomaly(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 33, 33, 34, 32, 94, 33, 31, 33, 34, 32 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_fluctuations_with_old_anomaly(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 94, 22, 22, 21, 21, 20, 21, 20, 21, 20 };
+ uint8_t processed_charge, average;
+
+ average = calculate_average(charges);
+ processed_charge = process_data(battery, charges);
+
+ g_assert(processed_charge == average);
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_bad_battery(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 28, 38, 92, 34, 85, 34, 45, 41, 29, 40 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_device_report_5_percent(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 55, 55, 50, 50, 50, 55, 55, 55, 60, 60 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+static void test_device_report_10_percent(const void *data)
+{
+ struct bt_battery *battery = bt_battery_new();
+ uint8_t charges[DATA_SIZE] = { 30, 30, 30, 40, 40, 50, 50, 50, 50, 60 };
+ uint8_t processed_charge;
+
+ for (int i = 0; i < DATA_SIZE; i++) {
+ processed_charge = bt_battery_charge(battery, charges[i]);
+ g_assert(processed_charge == charges[i]);
+ }
+
+ bt_battery_free(battery);
+ free(battery);
+ tester_test_passed();
+}
+
+int main(int argc, char *argv[])
+{
+ tester_init(&argc, &argv);
+
+ tester_add("/battery/test_discharging", NULL, NULL,
+ test_discharging, NULL);
+ tester_add("/battery/test_charging", NULL, NULL,
+ test_charging, NULL);
+ tester_add("/battery/test_discharge_started", NULL, NULL,
+ test_discharge_started, NULL);
+ tester_add("/battery/test_charge_started", NULL, NULL,
+ test_charge_started, NULL);
+ tester_add("/battery/test_fluctuations", NULL, NULL,
+ test_fluctuations, NULL);
+ tester_add("/battery/test_fluctuations_with_anomaly", NULL, NULL,
+ test_fluctuations_with_anomaly, NULL);
+ tester_add("/battery/test_fluctuations_with_old_anomaly", NULL, NULL,
+ test_fluctuations_with_old_anomaly, NULL);
+ tester_add("/battery/test_bad_battery", NULL, NULL, test_bad_battery, NULL);
+ tester_add("/battery/test_device_report_5_percent", NULL, NULL,
+ test_device_report_5_percent, NULL);
+ tester_add("/battery/test_device_report_10_percent", NULL, NULL,
+ test_device_report_10_percent, NULL);
+
+ return tester_run();
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* RE: [BlueZ,v7,1/2] shared/battery: improve the display of the charge level
2025-11-14 12:11 [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level Roman Smirnov
2025-11-14 12:11 ` [PATCH BlueZ v7 2/2] unit: Add basic unit tests for battery charge handling Roman Smirnov
@ 2025-11-14 13:45 ` bluez.test.bot
2025-11-17 16:30 ` [PATCH BlueZ v7 1/2] " patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: bluez.test.bot @ 2025-11-14 13:45 UTC (permalink / raw)
To: linux-bluetooth, r.smirnov
[-- Attachment #1: Type: text/plain, Size: 1262 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1023520
---Test result---
Test Summary:
CheckPatch PENDING 0.34 seconds
GitLint PENDING 0.28 seconds
BuildEll PASS 20.13 seconds
BluezMake PASS 2633.84 seconds
MakeCheck PASS 20.23 seconds
MakeDistcheck PASS 185.97 seconds
CheckValgrind PASS 238.31 seconds
CheckSmatch PASS 311.05 seconds
bluezmakeextell PASS 128.73 seconds
IncrementalBuild PENDING 0.27 seconds
ScanBuild PASS 912.27 seconds
Details
##############################
Test: CheckPatch - PENDING
Desc: Run checkpatch.pl script
Output:
##############################
Test: GitLint - PENDING
Desc: Run gitlint
Output:
##############################
Test: IncrementalBuild - PENDING
Desc: Incremental build with the patches in the series
Output:
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level
2025-11-14 12:11 [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level Roman Smirnov
2025-11-14 12:11 ` [PATCH BlueZ v7 2/2] unit: Add basic unit tests for battery charge handling Roman Smirnov
2025-11-14 13:45 ` [BlueZ,v7,1/2] shared/battery: improve the display of the charge level bluez.test.bot
@ 2025-11-17 16:30 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2025-11-17 16:30 UTC (permalink / raw)
To: Roman Smirnov; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Fri, 14 Nov 2025 15:11:36 +0300 you wrote:
> The battery charge level may fluctuate due to uncalibrated
> sensors. Commit smooths out such fluctuations.
>
> The algorithm for determining uncalibrated sensors consists of
> finding the number of changes in charge direction (i.e., "spikes").
> If the number of spikes is zero, the device is charging or discharging.
> If there is one spike, it may mean that the device has started charging
> or has been disconnected from charging. If there are two or more spikes,
> this is a clear indication of an uncalibrated sensor.
>
> [...]
Here is the summary with links:
- [BlueZ,v7,1/2] shared/battery: improve the display of the charge level
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=c5d4a041483c
- [BlueZ,v7,2/2] unit: Add basic unit tests for battery charge handling
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-11-17 16:30 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-14 12:11 [PATCH BlueZ v7 1/2] shared/battery: improve the display of the charge level Roman Smirnov
2025-11-14 12:11 ` [PATCH BlueZ v7 2/2] unit: Add basic unit tests for battery charge handling Roman Smirnov
2025-11-14 13:45 ` [BlueZ,v7,1/2] shared/battery: improve the display of the charge level bluez.test.bot
2025-11-17 16:30 ` [PATCH BlueZ v7 1/2] " patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).