Skip to content

Commit bb2cb02

Browse files
committed
nicla-system: Add battery monitor web app.
1 parent 8540a9c commit bb2cb02

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
const connectButton = document.getElementById('connect');
2+
const batteryLevelElement = document.getElementById('battery-level');
3+
const batteryLabel = document.getElementById('battery-label');
4+
5+
const serviceUuid = '19b10000-0000-537e-4f6c-d104768a1214';
6+
let pollIntervalID;
7+
let peripheralDevice;
8+
9+
let data = {
10+
"batteryPercentage": {
11+
"name": "Battery Percentage",
12+
"value": 0,
13+
"unit": "%",
14+
"characteristic": null,
15+
"characteristicUUID": "19b10000-1001-537e-4f6c-d104768a1214",
16+
"extractData": function(dataView) {
17+
return dataView.getInt8(0);
18+
}
19+
},
20+
"batteryVoltage": {
21+
"name": "Battery Voltage",
22+
"value": 0,
23+
"unit": "V",
24+
"characteristic": null,
25+
"characteristicUUID": "19b10000-1002-537e-4f6c-d104768a1214",
26+
"extractData": function(dataView) {
27+
return dataView.getFloat32(0, true);
28+
}
29+
},
30+
"batteryChargeLevel": {
31+
"name": "Battery Charge Level",
32+
"value": 0,
33+
"unit": "",
34+
"characteristic": null,
35+
"characteristicUUID": "19b10000-1003-537e-4f6c-d104768a1214",
36+
"extractData": function(dataView) {
37+
return dataView.getInt8(0);
38+
},
39+
"getColor": function(value) {
40+
// Red to green range with 5 steps and white for the unknown state
41+
const colors = ["#ffffff", "#ff2d2d", "#fc9228", "#ffea00", "#adfd5c", "#00c600"];
42+
return colors[value];
43+
}
44+
}
45+
};
46+
47+
function onDisconnected(event) {
48+
let device = event.target;
49+
connectButton.disabled = false;
50+
connectButton.style.opacity = 1;
51+
if(pollIntervalID) clearInterval(pollIntervalID);
52+
console.log(`Device ${device.name} is disconnected.`);
53+
54+
// Reset the battery level display
55+
batteryLevelElement.style.width = "0px";
56+
batteryLabel.textContent = "";
57+
}
58+
59+
async function connectToPeripheralDevice(usePolling = false, pollInterval = 5000){
60+
if (peripheralDevice && peripheralDevice.gatt.connected) {
61+
console.log("Already connected");
62+
return;
63+
}
64+
65+
peripheralDevice = await navigator.bluetooth.requestDevice({
66+
filters: [{ services: [serviceUuid] }]
67+
});
68+
peripheralDevice.addEventListener('gattserverdisconnected', onDisconnected);
69+
70+
const server = await peripheralDevice.gatt.connect();
71+
console.log("Connected to: " + peripheralDevice.name);
72+
const service = await server.getPrimaryService(serviceUuid);
73+
74+
await Promise.all(
75+
Object.keys(data).map(async (key) => {
76+
let item = data[key];
77+
const characteristic = await service.getCharacteristic(item.characteristicUUID);
78+
item.characteristic = characteristic;
79+
80+
if (!usePolling) {
81+
characteristic.addEventListener('characteristicvaluechanged', handleCharacteristicChange);
82+
characteristic.readValue(); // Perform an initial read
83+
await characteristic.startNotifications();
84+
}
85+
})
86+
);
87+
88+
if (usePolling) {
89+
pollIntervalID = setInterval(readCharacteristicsData, pollInterval);
90+
await readCharacteristicsData();
91+
}
92+
}
93+
94+
connectButton.addEventListener('click', async () => {
95+
try {
96+
await connectToPeripheralDevice(true);
97+
connectButton.disabled = true;
98+
connectButton.style.opacity = 0.5;
99+
} catch (error) {
100+
if(error.message === "User cancelled the requestDevice() chooser."){
101+
return;
102+
}
103+
104+
console.error('Error:', error);
105+
connectButton.style.backgroundColor = "red";
106+
}
107+
});
108+
109+
function displayBatteryLevel() {
110+
const batteryPercentage = data.batteryPercentage.value;
111+
const batteryVoltage = data.batteryVoltage.value;
112+
const regulatedVoltage = (batteryVoltage / batteryPercentage * 100).toFixed(2);
113+
114+
// Map the range from 0-5 to 0-100
115+
const batteryPercentageMapped = data.batteryChargeLevel.value * 20;
116+
batteryLevelElement.style.width = `${batteryPercentageMapped * 0.56}px`; // Scale the battery level to the width of the battery div
117+
batteryLevelElement.style.backgroundColor = data.batteryChargeLevel.getColor(data.batteryChargeLevel.value);
118+
batteryLabel.textContent = `${batteryVoltage.toFixed(2)}V (${batteryPercentage}% of ${regulatedVoltage}V)`;
119+
}
120+
121+
async function readCharacteristicsData() {
122+
await Promise.all(
123+
Object.keys(data).map(async (key) => {
124+
let item = data[key];
125+
console.log("Requesting " + item.name + "...");
126+
item.value = item.extractData(await item.characteristic.readValue());
127+
console.log(item.name + ": " + item.value + item.unit);
128+
})
129+
);
130+
displayBatteryLevel();
131+
}
132+
133+
function handleCharacteristicChange(event) {
134+
// Find the characteristic that changed in the data object by matching the UUID
135+
let dataItem = Object.values(data).find(item => item.characteristicUUID === event.target.uuid);
136+
let dataView = event.target.value;
137+
dataItem.value = dataItem.extractData(dataView);
138+
139+
console.log(dataItem.name + " changed: " + dataItem.value + dataItem.unit);
140+
displayBatteryLevel();
141+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>WebBLE Battery Monitor</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link
10+
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&family=Roboto+Mono:wght@400;700&display=swap"
11+
rel="stylesheet">
12+
<link rel="stylesheet" href="style.css">
13+
<script defer src="app.js"></script>
14+
</head>
15+
<body>
16+
<h1>WebBLE Battery Monitor 🔋</h1>
17+
<div class="container">
18+
<div class="battery">
19+
<div class="battery-level" id="battery-level"></div>
20+
</div>
21+
<p id="battery-label"></p>
22+
<button id="connect">Connect</button>
23+
</div>
24+
</body>
25+
</html>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
:root {
2+
--main-control-color: #008184;
3+
--main-control-color-hover: #005c5f;
4+
--main-flexbox-gap: 16px;
5+
--secondary-text-color: #87898b;
6+
}
7+
8+
body {
9+
font-family: 'Open Sans', sans-serif;
10+
text-align: center;
11+
}
12+
13+
.container {
14+
display: inline-block;
15+
margin-top: 20px;
16+
}
17+
18+
button {
19+
font-family: 'Open Sans', sans-serif;
20+
font-weight: 700;
21+
font-size: 1rem;
22+
justify-content: center;
23+
background-color: var(--main-control-color);
24+
color: #fff;
25+
cursor: pointer;
26+
letter-spacing: 1.28px;
27+
line-height: normal;
28+
outline: none;
29+
padding: 8px 18px;
30+
text-align: center;
31+
text-decoration: none;
32+
border: 2px solid transparent;
33+
border-radius: 32px;
34+
text-transform: uppercase;
35+
box-sizing: border-box;
36+
}
37+
38+
button:hover {
39+
background-color: var(--main-control-color-hover);
40+
}
41+
42+
.battery {
43+
width: 60px;
44+
height: 30px;
45+
border: 2px solid #999;
46+
border-radius: 5px;
47+
position: relative;
48+
margin: 20px auto;
49+
}
50+
51+
.battery-level {
52+
position: absolute;
53+
bottom: 2px;
54+
left: 2px;
55+
background-color: green;
56+
border-radius: 2px;
57+
width: 0;
58+
height: 26px;
59+
}

0 commit comments

Comments
 (0)