All of lore.kernel.org
 help / color / mirror / Atom feed
* [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

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.