From 47809d3cafb26abf2fa9c8d05bc33852f21e0311 Mon Sep 17 00:00:00 2001 From: hbvanommen Date: Sat, 10 Jan 2026 18:57:59 +0100 Subject: [PATCH 1/3] Battery level readout --- examples/Tap_to_Measure/Tap_to_Measure.ino | 6 +-- src/Qbead.h | 33 +++++++++++++++- src/QbeadBattery.h | 45 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/QbeadBattery.h diff --git a/examples/Tap_to_Measure/Tap_to_Measure.ino b/examples/Tap_to_Measure/Tap_to_Measure.ino index a2a52aa..1202a4d 100644 --- a/examples/Tap_to_Measure/Tap_to_Measure.ino +++ b/examples/Tap_to_Measure/Tap_to_Measure.ino @@ -38,9 +38,9 @@ void loop() { // Show the result. bead.show(); - - // ### Check for taps - if (bead.tapped) { + bead.measureBattery(); + if (bead.wasTapped()){ + total_taps++; bead.clear(); BlochVector acc_vector(bead.x, bead.y, bead.z); float probability = pow(innerProductAbs(current_state, acc_vector),2); diff --git a/src/Qbead.h b/src/Qbead.h index f79318d..2cda037 100644 --- a/src/Qbead.h +++ b/src/Qbead.h @@ -8,6 +8,8 @@ #include #include +#include + // default configs // TODO make them ifndef @@ -281,12 +283,16 @@ class Qbead { blecharcol(QB_UUID_COL_CHAR), blecharsph(QB_UUID_SPH_CHAR), blecharacc(QB_UUID_ACC_CHAR), - blechartap(QB_UUID_TAP_CHAR) + blechartap(QB_UUID_TAP_CHAR), + blecharbatlvl(UUID16_CHR_BATTERY_LEVEL) {} static Qbead *singletoninstance; // we need a global singleton static instance because bluefruit callbacks do not support context variables -- thankfully this is fine because there is indeed only one Qbead in existence at any time LSM6DS3 imu; + + Xiao battery; + Adafruit_NeoPixel pixels; BLEService bleservice; @@ -294,6 +300,7 @@ class Qbead { BLECharacteristic blecharsph; BLECharacteristic blecharacc; BLECharacteristic blechartap; + BLECharacteristic blecharbatlvl; uint8_t connection_count = 0; const uint8_t nsections; @@ -302,6 +309,7 @@ class Qbead { const uint8_t phi_quant; const uint8_t ix, iy, iz; const bool sx, sy, sz; + uint8_t bat_buff[1]; float rbuffer[3]; float x, y, z, rx, ry, rz; // filtered and raw acc, in units of g float t_acc, p_acc; // theta and phi according to gravity @@ -383,9 +391,32 @@ class Qbead { blechartap.setFixedLen(3*sizeof(float)); blechartap.begin(); blechartap.write(zerobuffer20, 3*sizeof(float)); + // BLE Characteristic battery level + blecharbatlvl.setProperties(CHR_PROPS_READ | CHR_PROPS_NOTIFY); + blecharbatlvl.setPermission(SECMODE_OPEN, SECMODE_OPEN); + blecharbatlvl.setUserDescriptor("battery level"); + blecharbatlvl.setFixedLen(1*sizeof(uint8_t)); + blecharbatlvl.begin(); + blecharbatlvl.write(zerobuffer20, sizeof(uint8_t)); startBLEadv(); } + void measureBattery(){ + // if you measure the voltage twice in a row, the second time, the voltage will be 0.2V lower. + float percentage = battery.GetBatteryLevel(); + + float voltage = battery.GetBatteryVoltage(); + bool charging = battery.IsChargingBattery(); + bat_buff[0] = uint8_t(percentage); + Serial.println("battery voltage"); + Serial.print(voltage); + Serial.print(", "); + Serial.print(charging); + Serial.print(", "); + Serial.println(percentage); + blecharbatlvl.write(bat_buff, sizeof(uint8_t)); + } + void clear() { pixels.clear(); } diff --git a/src/QbeadBattery.h b/src/QbeadBattery.h new file mode 100644 index 0000000..0789ee2 --- /dev/null +++ b/src/QbeadBattery.h @@ -0,0 +1,45 @@ +#include +#include + +#define BAT_HIGH_CHARGE 22 // HIGH for 50mA, LOW for 100mA +#define BAT_CHARGE_STATE 23 // LOW for charging, HIGH not charging + +class Xiao { +public: + Xiao(); + float GetBatteryVoltage(); + bool IsChargingBattery(); + float GetBatteryLevel(); +private: + // define min and max voltage for battery level determination + float maxVoltage = 4.11; + float minVoltage = 3.06; +}; + +Xiao::Xiao() { + pinMode(VBAT_ENABLE, OUTPUT); + pinMode(BAT_CHARGE_STATE, INPUT); + + digitalWrite(BAT_HIGH_CHARGE, LOW); // charge with 100ma if LOW, else 50mA +} + +#define VBAT_MV_PER_LBS (0.003395996F) + +float Xiao::GetBatteryVoltage() { + digitalWrite(VBAT_ENABLE, LOW); + // might want to wait very briefly for the voltage to settle + uint32_t adcCount = analogRead(PIN_VBAT); + float adcVoltage = adcCount * VBAT_MV_PER_LBS; + float vBat = adcVoltage * (1510.0 / 510.0); + + digitalWrite(VBAT_ENABLE, HIGH); + + return vBat; +} + +float Xiao::GetBatteryLevel() { + float batPercentage = (GetBatteryVoltage()-minVoltage)/(maxVoltage - minVoltage) * 100; + return batPercentage; +} + +bool Xiao::IsChargingBattery() { return digitalRead(BAT_CHARGE_STATE) == LOW; } \ No newline at end of file From 3c040046b93525be11d4b687788d7be90d337d89 Mon Sep 17 00:00:00 2001 From: hbvanommen Date: Sat, 10 Jan 2026 19:36:59 +0100 Subject: [PATCH 2/3] Cleaning up --- examples/Tap_to_Measure/Tap_to_Measure.ino | 4 ++-- src/Qbead.h | 2 +- src/QbeadBattery.h | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/Tap_to_Measure/Tap_to_Measure.ino b/examples/Tap_to_Measure/Tap_to_Measure.ino index 1202a4d..3883a4f 100644 --- a/examples/Tap_to_Measure/Tap_to_Measure.ino +++ b/examples/Tap_to_Measure/Tap_to_Measure.ino @@ -38,9 +38,9 @@ void loop() { // Show the result. bead.show(); + // for testing purposes just readout the battery level every loop now bead.measureBattery(); - if (bead.wasTapped()){ - total_taps++; + if (bead.tapped){ bead.clear(); BlochVector acc_vector(bead.x, bead.y, bead.z); float probability = pow(innerProductAbs(current_state, acc_vector),2); diff --git a/src/Qbead.h b/src/Qbead.h index 2cda037..3cac759 100644 --- a/src/Qbead.h +++ b/src/Qbead.h @@ -291,7 +291,7 @@ class Qbead { LSM6DS3 imu; - Xiao battery; + XiaoBattery battery; Adafruit_NeoPixel pixels; diff --git a/src/QbeadBattery.h b/src/QbeadBattery.h index 0789ee2..2961bbe 100644 --- a/src/QbeadBattery.h +++ b/src/QbeadBattery.h @@ -4,9 +4,9 @@ #define BAT_HIGH_CHARGE 22 // HIGH for 50mA, LOW for 100mA #define BAT_CHARGE_STATE 23 // LOW for charging, HIGH not charging -class Xiao { +class XiaoBattery { public: - Xiao(); + XiaoBattery(); float GetBatteryVoltage(); bool IsChargingBattery(); float GetBatteryLevel(); @@ -16,7 +16,7 @@ class Xiao { float minVoltage = 3.06; }; -Xiao::Xiao() { +XiaoBattery::XiaoBattery() { pinMode(VBAT_ENABLE, OUTPUT); pinMode(BAT_CHARGE_STATE, INPUT); @@ -25,7 +25,7 @@ Xiao::Xiao() { #define VBAT_MV_PER_LBS (0.003395996F) -float Xiao::GetBatteryVoltage() { +float XiaoBattery::GetBatteryVoltage() { digitalWrite(VBAT_ENABLE, LOW); // might want to wait very briefly for the voltage to settle uint32_t adcCount = analogRead(PIN_VBAT); @@ -37,9 +37,9 @@ float Xiao::GetBatteryVoltage() { return vBat; } -float Xiao::GetBatteryLevel() { +float XiaoBattery::GetBatteryLevel() { float batPercentage = (GetBatteryVoltage()-minVoltage)/(maxVoltage - minVoltage) * 100; return batPercentage; } -bool Xiao::IsChargingBattery() { return digitalRead(BAT_CHARGE_STATE) == LOW; } \ No newline at end of file +bool XiaoBattery::IsChargingBattery() { return digitalRead(BAT_CHARGE_STATE) == LOW; } \ No newline at end of file From 30e0b45f24eb2fd36859e5dd5fbbae4d8ef50091 Mon Sep 17 00:00:00 2001 From: hbvanommen Date: Wed, 14 Jan 2026 11:03:02 +0100 Subject: [PATCH 3/3] Prototype for reporting battery level in html page --- QBeadVisualisation.html | 29 ++++++++++++++++++++++++++++- src/Qbead.h | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/QBeadVisualisation.html b/QBeadVisualisation.html index 62486b1..d3f74a8 100644 --- a/QBeadVisualisation.html +++ b/QBeadVisualisation.html @@ -27,6 +27,7 @@

Live Output

+

@@ -313,6 +314,7 @@

Live Output

var accCharacteristic; var sphCharacteristic; var colCharacteristic; + var batteryLevelCharacteristic; function onStartButtonClick() { let serviceUuid = "e30c1fc6-359c-12be-2544-63d6aa088d45"; @@ -320,7 +322,8 @@

Live Output

let accUuid = "e30c1fc9-359c-12be-2544-63d6aa088d45"; let sphUuid = "e30c1fc8-359c-12be-2544-63d6aa088d45"; let colUuid = "e30c1fc7-359c-12be-2544-63d6aa088d45"; - + let batUuid = "00002a19-0000-1000-8000-00805f9b34fb"; + log('Requesting Bluetooth Device...'); navigator.bluetooth.requestDevice( { @@ -338,6 +341,13 @@

Live Output

.then(service => { log('Getting Characteristics...'); service.getCharacteristic(colUuid).then(ch => {colCharacteristic=ch;}); + service.getCharacteristic(batUuid).then(ch => { + batteryLevelCharacteristic=ch; + return batteryLevelCharacteristic.startNotifications().then(_ => { + batteryLevelCharacteristic.addEventListener('characteristicvaluechanged', + handleBatteryNotifications); + }) + }); return service.getCharacteristic(accUuid); }) .then(characteristic => { @@ -365,6 +375,17 @@

Live Output

log('Argh! ' + error); }); } + if (batteryLevelCharacteristic) { + batteryLevelCharacteristic.stopNotifications() + .then(_ => { + log('> Notifications stopped'); + batteryLevelCharacteristic.removeEventListener('characteristicvaluechanged', + handleBatteryNotifications); + }) + .catch(error => { + log('Argh! ' + error); + }); + } } function onPhiRangeChange() { @@ -408,6 +429,12 @@

Live Output

window.z = z; log(`> ${x.toFixed(3)} ${y.toFixed(3)} ${z.toFixed(3)} ${t.toFixed(0)} ${p.toFixed(0)} `); } + + function handleBatteryNotifications(event) { + let batteryPercentage = event.target.value.getInt8(0); + document.getElementById("batteryLevel").textContent = `Battery Level: ${batteryPercentage}%`; + // log(`> ${batteryPercentage} `); + } diff --git a/src/Qbead.h b/src/Qbead.h index 3cac759..9dd809d 100644 --- a/src/Qbead.h +++ b/src/Qbead.h @@ -414,7 +414,7 @@ class Qbead { Serial.print(charging); Serial.print(", "); Serial.println(percentage); - blecharbatlvl.write(bat_buff, sizeof(uint8_t)); + blecharbatlvl.notify(bat_buff, sizeof(uint8_t)); } void clear() {