Skip to content

Commit d2cbb97

Browse files
Merge pull request #42 from HaudinFlorence/enable_the_use_of_an_already_connected_device_in_ipymovehub
Enable the use of an already connected device in ipymovehub
2 parents d857172 + 224680b commit d2cbb97

12 files changed

Lines changed: 224 additions & 81 deletions

File tree

bluetooth_manager/movehub.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,10 @@ async def __aexit__(self, exc_type, exc, tb):
146146

147147
class MoveHubLaneProxy(object):
148148

149-
def __init__(self, movehub, lane):
149+
def __init__(self, movehub, lane, identifier):
150150
self.movehub = movehub
151151
self.lane = lane
152+
self.identifier = identifier
152153

153154
async def get_distance_async(self):
154155
await self._poll()
@@ -411,6 +412,7 @@ class MoveHubWidget(DOMWidget):
411412
_view_module_version = Unicode(module_version).tag(sync=True)
412413
_device_info = Dict(DEFAULT_DEVICE_INFO, read_only=True).tag(sync=True)
413414
name = Unicode("device1").tag(sync=True)
415+
identifier = Unicode("").tag(sync=True)
414416
n_lanes =Int(3).tag(sync=True)
415417

416418
def __init__(self, *args, **kwargs):
@@ -419,7 +421,7 @@ def __init__(self, *args, **kwargs):
419421
self._run_lock = asyncio.Lock()
420422
self._lane_locks = [asyncio.Lock() for i in range(self.n_lanes)]
421423

422-
def run_async_program(self, program, lane=0, output=None):
424+
def run_async_program(self, program, lane=0, output=None, identifier=identifier):
423425

424426
if lane < 0 or lane >= self.n_lanes :
425427
raise RuntimeError(f"lane must be >=0 and < {self.n_lanes} but is {lane}")
@@ -428,11 +430,11 @@ def run_async_program(self, program, lane=0, output=None):
428430
output = Output()
429431
display(output)
430432
return asyncio.ensure_future(
431-
self._run_async_program(lane=lane, program=program, output=output)
433+
self._run_async_program(lane=lane, program=program, output=output, identifier=identifier)
432434
)
433435

434436

435-
def run_async_programs_concurrently(self, programs, output=None):
437+
def run_async_programs_concurrently(self, programs, output=None, identifier=None):
436438
if output is None:
437439
output = Output()
438440
display(output)
@@ -442,8 +444,10 @@ def run_async_programs_concurrently(self, programs, output=None):
442444

443445
futures = []
444446
for lane, program in enumerate(programs):
447+
if identifier is None:
448+
identifier = self.identifier
445449
f = asyncio.ensure_future(
446-
self._run_async_program(lane=lane, program=program, output=output)
450+
self._run_async_program(lane=lane, program=program, output=output, identifier=identifier)
447451
)
448452
futures.append(f)
449453
return futures
@@ -459,13 +463,20 @@ def run_program(self, program, output=None):
459463
lane_proxy = MoveHubLaneProxy(movehub=self, lane=0)
460464
program(lane_proxy, output)
461465

462-
def connect(self, output=None):
463-
async def main(lane, log):
466+
def connect(self, output=None, identifier=None):
467+
async def main(lane, log, identifier):
464468
pass
469+
if output is None:
470+
output = Output()
471+
display(output)
472+
473+
if identifier is None:
474+
identifier = self.identifier
465475

466-
self.run_async_program(main, output=output)
476+
self.run_async_program(main, output=output, identifier=identifier)
477+
467478

468-
async def _run_async_program(self, lane, program, output):
479+
async def _run_async_program(self, lane, program, output, identifier):
469480
def log(*args, **kwargs):
470481
old_stdout = sys.stdout
471482
sys.stdout = mystdout = StringIO()
@@ -480,10 +491,10 @@ def log(*args, **kwargs):
480491
self._log = log
481492

482493
async with self._lane_locks[lane]:
483-
lane_proxy = MoveHubLaneProxy(movehub=self, lane=lane)
494+
lane_proxy = MoveHubLaneProxy(movehub=self, lane=lane, identifier=identifier)
484495
try:
485496
await lane_proxy._connect()
486-
await program(lane_proxy, log)
497+
await program(lane_proxy, log, identifier)
487498
except Exception as ex:
488499
err_str = "".join(
489500
traceback.TracebackException.from_exception(ex).format()

examples/introduction.ipynb

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
},
1616
{
1717
"cell_type": "code",
18-
"execution_count": 14,
18+
"execution_count": 1,
1919
"id": "762d552a-dafd-4b4b-9b0c-8b6612d2415b",
2020
"metadata": {
2121
"scrolled": true
@@ -53,42 +53,16 @@
5353
},
5454
{
5555
"cell_type": "code",
56-
"execution_count": 15,
56+
"execution_count": 2,
5757
"id": "aae633ad-bbb2-45c2-a1ff-5fab18146172",
5858
"metadata": {
5959
"scrolled": true
6060
},
61-
"outputs": [],
62-
"source": [
63-
"# number of concurrent \"lanes\"\n",
64-
"n_lanes = 4 \n",
65-
"movehub = MoveHubWidget(n_lanes=n_lanes)"
66-
]
67-
},
68-
{
69-
"cell_type": "code",
70-
"execution_count": 16,
71-
"id": "9b5f6086-3605-47ee-a170-57d3b97600e2",
72-
"metadata": {},
7361
"outputs": [
7462
{
7563
"data": {
7664
"application/vnd.jupyter.widget-view+json": {
77-
"model_id": "98eb5a27381841b7abdf27f7da8522bf",
78-
"version_major": 2,
79-
"version_minor": 0
80-
},
81-
"text/plain": [
82-
"Output()"
83-
]
84-
},
85-
"metadata": {},
86-
"output_type": "display_data"
87-
},
88-
{
89-
"data": {
90-
"application/vnd.jupyter.widget-view+json": {
91-
"model_id": "d6eec676075b4a9c9c3fa5797a533771",
65+
"model_id": "b4963924a83143199f0756442e81826b",
9266
"version_major": 2,
9367
"version_minor": 0
9468
},
@@ -101,8 +75,45 @@
10175
}
10276
],
10377
"source": [
104-
"movehub.connect()\n",
105-
"display(movehub) "
78+
"# number of concurrent \"lanes\"\n",
79+
"n_lanes = 4 \n",
80+
"widget = MoveHubWidget(n_lanes=n_lanes)\n",
81+
"output = Output()\n",
82+
"display(widget)"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": null,
88+
"id": "9b5f6086-3605-47ee-a170-57d3b97600e2",
89+
"metadata": {},
90+
"outputs": [],
91+
"source": [
92+
"# Case 1 : Execute this cell if there is no MoveHub connected\n",
93+
"widget.connect(output=output)"
94+
]
95+
},
96+
{
97+
"cell_type": "code",
98+
"execution_count": null,
99+
"id": "514c7c39-8b62-4caa-a506-4abb79891592",
100+
"metadata": {},
101+
"outputs": [],
102+
"source": [
103+
"# Case 2 : Execute this cell if there is/are one/some MoveHub(s) connected\n",
104+
"# Choose one of them and paste its identifier\n",
105+
"widget.identifier = \"dcRIlxx9l8bQ3T6UVofW8Q==\"\n",
106+
"widget.connect(output=output, identifier=widget.identifier)"
107+
]
108+
},
109+
{
110+
"cell_type": "code",
111+
"execution_count": null,
112+
"id": "e2b06dac-4056-435f-800a-60134c48c27e",
113+
"metadata": {},
114+
"outputs": [],
115+
"source": [
116+
"display(widget)"
106117
]
107118
},
108119
{
@@ -247,7 +258,7 @@
247258
"box = ipywidgets.HBox([button_disco, button_motors_ab, button_motor_c])\n",
248259
"output = Output()\n",
249260
"\n",
250-
"async def disco(lane, log):\n",
261+
"async def disco(lane, log):main\n",
251262
" button_disco.disabled = True\n",
252263
" for i in range(10):\n",
253264
" await lane.set_led_async(LedColor.pink)\n",
@@ -261,7 +272,7 @@
261272
" await lane.motor_time_multi_async(seconds=2, power_a=-10, power_b=10)\n",
262273
" await lane.motor_time_multi_async(seconds=2, power_a=10, power_b=-10)\n",
263274
" await lane.motor_time_multi_async(seconds=2, power_a=-10, power_b=-10)\n",
264-
" button_motors_ab.disabled = False\n",
275+
" button_motors_ab.disabled =main False\n",
265276
"\n",
266277
"async def motor_c(lane, log):\n",
267278
" button_motor_c.disabled = True\n",

src/bluetooth-extension/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin<void> = {
9696
}).then(async result => {
9797
if (result.button.accept) {
9898
bluetoothManager.registry.itemsList.forEach(async item => {
99-
if (item.identifier === result.value) {
99+
if (item.deviceType === result.value) {
100100
await bluetoothManager.connectDevice(item);
101101
} else {
102102
console.warn('There is no corresponding item in the registry!');
@@ -160,8 +160,8 @@ export class DropDownRegistry
160160
this.registry = registry;
161161
registry.itemsList.forEach(item => {
162162
const option = document.createElement('option');
163-
option.value = item.identifier;
164-
option.text = item.identifier;
163+
option.value = item.deviceType;
164+
option.text = item.deviceType;
165165
this._selectList.appendChild(option);
166166
});
167167
}

src/bluetooth/BluetoothManager.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { buildCompleteIdentifier } from '../bluetooth-extension';
55
import { IDisposable } from '@lumino/disposable';
66
import { Dialog, showDialog } from '@jupyterlab/apputils';
77

8+
89
/**
910
* A class used to update the list of connected device and the related signals used to rerender the connected devices section.
1011
*/
@@ -19,7 +20,7 @@ export class BluetoothManager implements IBluetoothManager {
1920
>(this);
2021
this._registry = new BluetoothManager.DeviceRegistry();
2122
this._deviceList = [];
22-
this.identifierRegistry = [];
23+
this._identifierRegistry = [];
2324
}
2425

2526
get deviceList(): Array<BluetoothManager.Device> {
@@ -30,6 +31,10 @@ export class BluetoothManager implements IBluetoothManager {
3031
return this._registry;
3132
}
3233

34+
get identifierRegistry(): Array<string> {
35+
return this._identifierRegistry;
36+
}
37+
3338
async connectDevice(
3439
registryItem: IDeviceRegistryItem
3540
): Promise<BluetoothManager.Device | undefined> {
@@ -88,7 +93,7 @@ export class BluetoothManager implements IBluetoothManager {
8893
this._registry.add(registryItem);
8994
this.registeredByAPlugin.emit(this._registry);
9095
console.warn(
91-
`New item from category ${registryItem.identifier} is added to the registry.`
96+
`New item from category ${registryItem.deviceType} is added to the registry.`
9297
);
9398
return this._registry;
9499
}
@@ -126,7 +131,6 @@ export class BluetoothManager implements IBluetoothManager {
126131
else {
127132
return;
128133
}
129-
130134
}
131135

132136
private _deviceList: Array<BluetoothManager.Device>;
@@ -136,7 +140,7 @@ export class BluetoothManager implements IBluetoothManager {
136140
BluetoothManager.DeviceRegistry
137141
>;
138142
private _registry: BluetoothManager.DeviceRegistry;
139-
public identifierRegistry: Array<string>;
143+
private _identifierRegistry: Array<string>;
140144
}
141145

142146
export namespace BluetoothManager {
@@ -279,10 +283,11 @@ export interface IBluetoothManager {
279283
>;
280284
get deviceList(): Array<BluetoothManager.Device>;
281285
get registry(): BluetoothManager.DeviceRegistry;
286+
get identifierRegistry(): Array<string>;
282287
}
283288

284289
export interface IDeviceRegistryItem {
285-
identifier: string;
290+
deviceType: string;
286291
factory: (
287292
native: BluetoothDevice
288293
) => Promise<BluetoothManager.Device | undefined>;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useState } from 'react';
2+
import copySVG from '../../../style/copy.svg';
3+
const copySVGUrl = `data:image/svg+xml;base64,${btoa(copySVG)}`;
4+
5+
interface ICopyToClipboardProps {
6+
textToCopy: string
7+
}
8+
export default function CopyToClipboard({ textToCopy }: ICopyToClipboardProps) {
9+
10+
const handleCopyClick = async () => {
11+
try {
12+
await navigator.clipboard.writeText(textToCopy);
13+
} catch (err) {
14+
console.error('Failed to copy text: ', err);
15+
}
16+
};
17+
return (
18+
<div style={{display:"flex", alignItems:"center", justifyContent:"center", width: "250px", gap:"10px", margin: "4px 0"}}>
19+
<div>ID: </div>
20+
<input
21+
className='input-movehub-id'
22+
type="text"
23+
value={textToCopy}
24+
placeholder={textToCopy}
25+
/>
26+
<button className='copy-button' onClick={handleCopyClick} title={"Copy ID"}>
27+
<img src={copySVGUrl} alt={"Button with copy icon"} />
28+
</button>
29+
</div>
30+
);
31+
}

src/movehub-extension/components/DeviceInfoTableComplete.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) {
3434
<tr className="custom-table-tr">
3535
<th className="custom-table-th"></th>
3636
<th className="custom-table-th">Status</th>
37+
<th className="custom-table-th">MAC address</th>
3738
<th className="custom-table-th">Identifier</th>
3839
<th className="custom-table-th">Led color</th>
3940
<th className="custom-table-th">Battery</th>
@@ -51,6 +52,13 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) {
5152
<ColoredCircleWithText color={'red'} text={'disconnected'} />
5253
)}
5354
</td>
55+
<td className="custom-table-td">
56+
{deviceState.connected ? (
57+
<div>{deviceState.primaryMACAddress}</div>
58+
) : (
59+
<div></div>
60+
)}
61+
</td>
5462
<td className="custom-table-td">
5563
{deviceState.connected ? (
5664
<div>{deviceState.identifier}</div>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { BluetoothManager, IBluetoothManager } from "../../bluetooth/BluetoothManager";
2+
import CopyToClipboard from "./CopyToClipboard";
3+
4+
interface IDeviceListProps {
5+
bluetoothManager: IBluetoothManager
6+
}
7+
8+
export function MoveHubList({ bluetoothManager }: IDeviceListProps) {
9+
const listItems = bluetoothManager.deviceList.map((item: BluetoothManager.Device, index) =>
10+
<>
11+
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "10px" }}>
12+
<div>Move Hub n°{index + 1}</div> <CopyToClipboard textToCopy={item.native.id} />
13+
</div>
14+
</>
15+
);
16+
return (
17+
<ul>{listItems}</ul>
18+
)
19+
}

0 commit comments

Comments
 (0)