aboutsummaryrefslogtreecommitdiffstats
path: root/quantum/battery
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/battery')
-rw-r--r--quantum/battery/battery.c41
-rw-r--r--quantum/battery/battery.h46
-rw-r--r--quantum/battery/tests/battery_tests.cpp97
-rw-r--r--quantum/battery/tests/rules.mk7
-rw-r--r--quantum/battery/tests/testlist.mk2
5 files changed, 193 insertions, 0 deletions
diff --git a/quantum/battery/battery.c b/quantum/battery/battery.c
new file mode 100644
index 0000000000..faf3c5a214
--- /dev/null
+++ b/quantum/battery/battery.c
@@ -0,0 +1,41 @@
+// Copyright 2025 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "battery_driver.h"
+#include "battery.h"
+#include "timer.h"
+
+#ifndef BATTERY_SAMPLE_INTERVAL
+# define BATTERY_SAMPLE_INTERVAL 30000
+#endif
+
+static uint8_t last_bat_level = 100;
+
+void battery_init(void) {
+ battery_driver_init();
+
+ last_bat_level = battery_driver_sample_percent();
+}
+
+__attribute__((weak)) void battery_percent_changed_user(uint8_t level) {}
+__attribute__((weak)) void battery_percent_changed_kb(uint8_t level) {}
+
+static void handle_percent_changed(void) {
+ battery_percent_changed_user(last_bat_level);
+ battery_percent_changed_kb(last_bat_level);
+}
+
+void battery_task(void) {
+ static uint32_t bat_timer = 0;
+ if (timer_elapsed32(bat_timer) > BATTERY_SAMPLE_INTERVAL) {
+ last_bat_level = battery_driver_sample_percent();
+
+ handle_percent_changed();
+
+ bat_timer = timer_read32();
+ }
+}
+
+uint8_t battery_get_percent(void) {
+ return last_bat_level;
+}
diff --git a/quantum/battery/battery.h b/quantum/battery/battery.h
new file mode 100644
index 0000000000..0985723eaa
--- /dev/null
+++ b/quantum/battery/battery.h
@@ -0,0 +1,46 @@
+// Copyright 2025 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * \file
+ *
+ * \defgroup battery Battery API
+ *
+ * \brief API to query battery status.
+ * \{
+ */
+
+/**
+ * \brief Initialize the battery driver.
+ */
+void battery_init(void);
+
+/**
+ * \brief Perform housekeeping tasks.
+ */
+void battery_task(void);
+
+/**
+ * \brief Sample battery level.
+ *
+ * \return The battery percentage, in the range 0-100.
+ */
+uint8_t battery_get_percent(void);
+
+/**
+ * \brief user hook called when battery level changed.
+ *
+ */
+void battery_percent_changed_user(uint8_t level);
+
+/**
+ * \brief keyboard hook called when battery level changed.
+ *
+ */
+void battery_percent_changed_kb(uint8_t level);
+
+/** \} */
diff --git a/quantum/battery/tests/battery_tests.cpp b/quantum/battery/tests/battery_tests.cpp
new file mode 100644
index 0000000000..ee011be8a8
--- /dev/null
+++ b/quantum/battery/tests/battery_tests.cpp
@@ -0,0 +1,97 @@
+// Copyright 2025 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+using testing::_;
+
+class BatteryDriverMock {
+ public:
+ virtual ~BatteryDriverMock() {}
+
+ // mock methods
+ MOCK_METHOD0(battery_driver_init, void(void));
+ MOCK_METHOD0(battery_driver_sample_percent, uint8_t(void));
+ MOCK_METHOD1(battery_percent_changed_kb, void(uint8_t));
+};
+
+class BatteryTest : public ::testing::Test {
+ public:
+ BatteryTest() {
+ _batteryDriverMock.reset(new ::testing::NiceMock<BatteryDriverMock>());
+ }
+ virtual ~BatteryTest() {
+ _batteryDriverMock.reset();
+ }
+
+ static std::unique_ptr<BatteryDriverMock> _batteryDriverMock;
+};
+
+std::unique_ptr<BatteryDriverMock> BatteryTest::_batteryDriverMock;
+
+extern "C" {
+#include "quantum/battery/battery.h"
+#include "timer.h"
+
+void advance_time(uint32_t ms);
+
+void battery_driver_init(void) {
+ if (BatteryTest::_batteryDriverMock) {
+ BatteryTest::_batteryDriverMock->battery_driver_init();
+ }
+}
+
+uint8_t battery_driver_sample_percent(void) {
+ if (BatteryTest::_batteryDriverMock) {
+ return BatteryTest::_batteryDriverMock->battery_driver_sample_percent();
+ }
+ return 255;
+}
+
+void battery_percent_changed_kb(uint8_t level) {
+ if (BatteryTest::_batteryDriverMock) {
+ BatteryTest::_batteryDriverMock->battery_percent_changed_kb(level);
+ }
+}
+}
+
+TEST_F(BatteryTest, TestInit) {
+ // init driver and initial sample
+ EXPECT_CALL(*_batteryDriverMock, battery_driver_init()).Times(1);
+ EXPECT_CALL(*_batteryDriverMock, battery_driver_sample_percent()).Times(1);
+
+ battery_init();
+}
+
+TEST_F(BatteryTest, TestSampleCached) {
+ // sample before timeout
+ EXPECT_CALL(*_batteryDriverMock, battery_driver_sample_percent()).Times(0);
+
+ advance_time(1);
+ battery_task();
+}
+
+TEST_F(BatteryTest, TestSampleNotCached) {
+ // sample after timeout
+ EXPECT_CALL(*_batteryDriverMock, battery_driver_sample_percent()).Times(1);
+
+ advance_time(60000);
+ battery_task();
+}
+
+TEST_F(BatteryTest, TestGet) {
+ // sample does not directly sample
+ EXPECT_CALL(*_batteryDriverMock, battery_driver_sample_percent()).Times(0);
+
+ battery_get_percent();
+}
+
+TEST_F(BatteryTest, TestChanged) {
+ // callbacks on value changed
+ EXPECT_CALL(*_batteryDriverMock, battery_percent_changed_kb(_)).Times(1);
+
+ battery_task();
+ advance_time(60000);
+ battery_task();
+}
diff --git a/quantum/battery/tests/rules.mk b/quantum/battery/tests/rules.mk
new file mode 100644
index 0000000000..86980f1020
--- /dev/null
+++ b/quantum/battery/tests/rules.mk
@@ -0,0 +1,7 @@
+VPATH += $(DRIVER_PATH)/battery
+
+battery_SRC := \
+ $(PLATFORM_PATH)/timer.c \
+ $(PLATFORM_PATH)/$(PLATFORM_KEY)/timer.c \
+ $(QUANTUM_PATH)/battery/battery.c \
+ $(QUANTUM_PATH)/battery/tests/battery_tests.cpp \
diff --git a/quantum/battery/tests/testlist.mk b/quantum/battery/tests/testlist.mk
new file mode 100644
index 0000000000..e91da865a0
--- /dev/null
+++ b/quantum/battery/tests/testlist.mk
@@ -0,0 +1,2 @@
+TEST_LIST += \
+ battery \