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/examples/Tap_to_Measure/Tap_to_Measure.ino b/examples/Tap_to_Measure/Tap_to_Measure.ino index 5f4c099..4fbb75b 100644 --- a/examples/Tap_to_Measure/Tap_to_Measure.ino +++ b/examples/Tap_to_Measure/Tap_to_Measure.ino @@ -28,6 +28,8 @@ void setup() { // The loop function is called repeatedly until the Qbead is powered off. // It is used to read the IMU and update the current state of the game. void loop() { + bead.measureBattery(); + static long last_tap = 0; static uint32_t tap_color = white; diff --git a/src/Qbead.h b/src/Qbead.h index 77715cc..f0c9e6a 100644 --- a/src/Qbead.h +++ b/src/Qbead.h @@ -8,6 +8,8 @@ #include #include +#include + // default configs // TODO make them ifndef @@ -288,12 +290,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; + + XiaoBattery battery; + Adafruit_NeoPixel pixels; BLEService bleservice; @@ -301,6 +307,7 @@ class Qbead { BLECharacteristic blecharsph; BLECharacteristic blecharacc; BLECharacteristic blechartap; + BLECharacteristic blecharbatlvl; uint8_t connection_count = 0; const uint8_t nsections; @@ -309,6 +316,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 whentapped_buffer[3]; float x_whentapped, y_whentapped, z_whentapped; // set when wasTapped is called @@ -425,9 +433,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.notify(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..2961bbe --- /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 XiaoBattery { +public: + XiaoBattery(); + float GetBatteryVoltage(); + bool IsChargingBattery(); + float GetBatteryLevel(); +private: + // define min and max voltage for battery level determination + float maxVoltage = 4.11; + float minVoltage = 3.06; +}; + +XiaoBattery::XiaoBattery() { + 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 XiaoBattery::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 XiaoBattery::GetBatteryLevel() { + float batPercentage = (GetBatteryVoltage()-minVoltage)/(maxVoltage - minVoltage) * 100; + return batPercentage; +} + +bool XiaoBattery::IsChargingBattery() { return digitalRead(BAT_CHARGE_STATE) == LOW; } \ No newline at end of file