Skip to content

Commit 7f870c6

Browse files
committed
treadmill
1 parent 45d938c commit 7f870c6

File tree

4 files changed

+480
-0
lines changed

4 files changed

+480
-0
lines changed

web/treadmill/treadmill.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
body {
2+
font-family: monospace;
3+
}

web/treadmill/treadmill.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
5+
<link rel="stylesheet" href="treadmill.css">
6+
<script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>
7+
<script type="module" src="../ui.js"></script>
8+
<script type="module" src="treadmill.js"></script>
9+
</style>
10+
</style>
11+
</head>
12+
<body>
13+
<script type="module">
14+
import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
15+
</script>
16+
17+
<bumble-controls id="bumble-controls"></bumble-controls><hr>
18+
<textarea id="log-output" style="width: 100%;" rows="10" disabled></textarea><hr>
19+
<scan-list id="scan-list"></scan-list>
20+
<connection-info id="connection-info" style="display:none"></connection-info>
21+
<security-request id="security-request" style="display:none"></security-request>
22+
<treadmill-values id="treadmill-values"></treadmill-values>
23+
</body>
24+
</html>

web/treadmill/treadmill.js

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import {LitElement, html, css} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
2+
import {setupSimpleApp} from '../bumble.js';
3+
4+
class ScanList extends LitElement {
5+
static properties = {
6+
listItems: {state: true},
7+
};
8+
9+
static styles = css`
10+
table, th, td {
11+
padding: 2px;
12+
white-space: pre;
13+
border: 1px solid black;
14+
border-collapse: collapse;
15+
}
16+
`;
17+
18+
constructor() {
19+
super();
20+
this.listItems = [];
21+
}
22+
23+
render() {
24+
if (this.listItems.length === 0) {
25+
return '';
26+
}
27+
return html`
28+
<table>
29+
<thead>
30+
<tr>
31+
<th>Address</th>
32+
<th>Address Type</th>
33+
<th>RSSI</th>
34+
<th>Data</th>
35+
<th>Connect</th>
36+
</tr>
37+
</thead>
38+
<tbody>
39+
${this.listItems.map(i => html`
40+
<tr>
41+
<td>${i['address']}</td>
42+
<td>${i['address_type']}</td>
43+
<td>${i['rssi']}</td>
44+
<td>${i['data']}</td>
45+
<td><button @click="${() => onConnectButton(i['address'])}">Connect</button></td>
46+
</tr>
47+
`)}
48+
</tbody>
49+
</table>
50+
`;
51+
}
52+
}
53+
customElements.define('scan-list', ScanList);
54+
55+
56+
class ConnectionInfo extends LitElement {
57+
static properties = {
58+
handle: {state: true},
59+
role_names: {state: true},
60+
self_address: {state: true},
61+
peer_address: {state: true},
62+
is_encrypted: {state: true},
63+
};
64+
65+
static styles = css`
66+
div {
67+
border: 1px solid black;
68+
border-collapse: collapse;
69+
}
70+
`;
71+
72+
constructor() {
73+
super();
74+
this.handle = 0;
75+
this.role = "UNKNOWN";
76+
this.self_address = "00:00:00:00:00:00"
77+
this.peer_address = "FF:FF:FF:FF:FF:FF"
78+
this.is_encrypted = "No"
79+
}
80+
81+
render() {
82+
return html`
83+
<div>
84+
<b>Connection Info</b><br \>
85+
Handle: ${this.handle}<br \>
86+
Role: ${this.role}<br \>
87+
Self Address: ${this.self_address}<br \>
88+
Peer Address: ${this.peer_address}<br \>
89+
Is Encrypted: ${this.is_encrypted}<br \>
90+
</div>
91+
`;
92+
}
93+
}
94+
customElements.define('connection-info', ConnectionInfo);
95+
96+
class TreadmillValues extends LitElement {
97+
static properties = {
98+
listValues: {state: Array},
99+
};
100+
101+
static styles = css`
102+
table {
103+
width: 100%;
104+
border: 1px solid black;
105+
border-collapse: collapse; /* Essential for clean table borders */
106+
margin-bottom: 20px;
107+
background-color: #ffffff;
108+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
109+
border-radius: 8px; /* Rounded corners for the entire table */
110+
overflow: hidden; /* Ensures rounded corners clip content */
111+
table-layout: fixed; /* Crucial for even spacing */
112+
}
113+
114+
th, td {
115+
border: 1px solid #ddd; /* Light border for cells */
116+
padding: 12px;
117+
text-align: left;
118+
}
119+
120+
th {
121+
background-color: #f8f8f8;
122+
font-weight: bold;
123+
color: #444;
124+
}
125+
126+
/* Zebra striping for table rows */
127+
tbody tr:nth-child(even) {
128+
background-color: #f9f9f9;
129+
}
130+
131+
tbody tr:hover {
132+
background-color: #f1f1f1;
133+
}
134+
`;
135+
136+
constructor() {
137+
super();
138+
this.listValues = [];
139+
}
140+
141+
addValue(value) {
142+
this.listValues = [value, ...this.listValues];
143+
}
144+
145+
render() {
146+
if (this.listValues.length === 0) {
147+
return '';
148+
}
149+
return html`
150+
<table>
151+
<thead>
152+
<tr>
153+
<th>Time</th>
154+
<th>Value</th>
155+
<th>Delta Time</th>
156+
<th>Delta Value</th>
157+
</tr>
158+
</thead>
159+
<tbody>
160+
${this.listValues.map((currentValue, index) => {
161+
let deltaTime = ''
162+
let deltaValue = ''
163+
if (index < (this.listValues.length - 1)) {
164+
const previousValue = this.listValues[index + 1];
165+
deltaTime = `${new Date(currentValue.time) - new Date(previousValue.time)} ms`
166+
deltaValue = Number(currentValue.value) - Number(previousValue.value)
167+
}
168+
169+
return html`
170+
<tr>
171+
<td>${currentValue.time}</td>
172+
<td>${currentValue.value}</td>
173+
<td>${deltaTime}</td>
174+
<td>${deltaValue}</td>
175+
</tr>
176+
`;
177+
})}
178+
</tbody>
179+
</table>
180+
`;
181+
}
182+
}
183+
customElements.define('treadmill-values', TreadmillValues);
184+
185+
class SecurityRequest extends LitElement {
186+
static properties = {
187+
handle: {state: true},
188+
role_names: {state: true},
189+
self_address: {state: true},
190+
peer_address: {state: true},
191+
is_encrypted: {state: true},
192+
};
193+
194+
static styles = css`
195+
div {
196+
border: 1px solid black;
197+
border-collapse: collapse;
198+
}
199+
`;
200+
201+
constructor() {
202+
super();
203+
this.handle = 0;
204+
this.role = "UNKNOWN";
205+
this.self_address = "00:00:00:00:00:00"
206+
this.peer_address = "FF:FF:FF:FF:FF:FF"
207+
this.is_encrypted = "No"
208+
}
209+
210+
render() {
211+
return html`
212+
<div>
213+
<b>Pair?</b><br \>
214+
<Button @click="${() => onPairButton(true)}">YES</Button>
215+
<Button @click="${() => onPairButton(false)}">NO</Button>
216+
</div>
217+
`;
218+
}
219+
}
220+
customElements.define('security-request', SecurityRequest);
221+
222+
const logOutput = document.querySelector('#log-output');
223+
function logToOutput(message) {
224+
console.log(message);
225+
logOutput.value += message + '\n';
226+
}
227+
228+
// Setup the UI
229+
const scanList = document.querySelector('#scan-list');
230+
const connectionInfo = document.querySelector('#connection-info');
231+
const bumbleControls = document.querySelector('#bumble-controls');
232+
const treadmillValues = document.querySelector('#treadmill-values');
233+
const securityRequest = document.querySelector('#security-request');
234+
235+
// Setup the app
236+
const app = await setupSimpleApp('treadmill.py', bumbleControls, logToOutput);
237+
app.on('scanning_updates', onScanningUpdates);
238+
app.on('hr_updates', onHrUpdates);
239+
app.on('connection_updates', onConnectionUpdates)
240+
app.on('on_security_request', onSecurityRequest)
241+
logToOutput('Click the Bluetooth button to start');
242+
243+
function onScanningUpdates(scanResults) {
244+
const items = scanResults.toJs({create_proxies : false}).map(entry => (
245+
{ address: entry.address, address_type: entry.address_type, rssi: entry.rssi, data: entry.data }
246+
));
247+
scanResults.destroy();
248+
scanList.listItems = items;
249+
}
250+
251+
function onHrUpdates(hrResults) {
252+
const items = hrResults.toJs({create_proxies : false})
253+
treadmillValues.addValue({value: items.get('value'), time: items.get('time')})
254+
hrResults.destroy();
255+
}
256+
257+
function onConnectButton(address) {
258+
app.do_connect(address)
259+
}
260+
261+
function onSecurityRequest() {
262+
securityRequest.style.display = 'block'
263+
}
264+
265+
function onPairButton(value) {
266+
app.do_security_request_response(value)
267+
securityRequest.style.display = 'none'
268+
}
269+
270+
function onConnectionUpdates(connection) {
271+
const items = connection.toJs({create_proxies : false})
272+
console.log(items)
273+
connection.destroy();
274+
scanList.style.display = 'none'
275+
connectionInfo.style.display = 'block'
276+
connectionInfo.handle = items.get('handle')
277+
connectionInfo.role = items.get('role')
278+
connectionInfo.self_address = items.get('self_address')
279+
connectionInfo.peer_address = items.get('peer_address')
280+
connectionInfo.is_encrypted = items.get('is_encrypted')
281+
}

0 commit comments

Comments
 (0)