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