From f88b00c2c34a1802d6471db42e3722e29e38f20d Mon Sep 17 00:00:00 2001 From: Florence Haudin Date: Wed, 5 Mar 2025 12:41:50 +0100 Subject: [PATCH 1/3] Enable the use of an already connected Move Hub in ipymovehub. --- bluetooth_manager/movehub.py | 1 + examples/introduction.ipynb | 18 ++++++++----- src/movehub-extension/widget.tsx | 46 ++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/bluetooth_manager/movehub.py b/bluetooth_manager/movehub.py index c8c35b8..40d2e11 100644 --- a/bluetooth_manager/movehub.py +++ b/bluetooth_manager/movehub.py @@ -411,6 +411,7 @@ class MoveHubWidget(DOMWidget): _view_module_version = Unicode(module_version).tag(sync=True) _device_info = Dict(DEFAULT_DEVICE_INFO, read_only=True).tag(sync=True) name = Unicode("device1").tag(sync=True) + identifier = Unicode("").tag(sync=True) n_lanes =Int(3).tag(sync=True) def __init__(self, *args, **kwargs): diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 91b74b0..4b65ea7 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 42, "id": "762d552a-dafd-4b4b-9b0c-8b6612d2415b", "metadata": { "scrolled": true @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 46, "id": "aae633ad-bbb2-45c2-a1ff-5fab18146172", "metadata": { "scrolled": true @@ -61,20 +61,24 @@ "outputs": [], "source": [ "# number of concurrent \"lanes\"\n", + "# Check the list of connected MoveHub in the Bluetooh Device section\n", + "# If you want to use one of them in the widget, use its identifier \n", + "# (in brackets)\n", "n_lanes = 4 \n", - "movehub = MoveHubWidget(n_lanes=n_lanes)" + "identifier =\"V5AFFoX5OfQmQss/ap6fyw==\"\n", + "movehub = MoveHubWidget(n_lanes=n_lanes, identifier=identifier)" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 48, "id": "9b5f6086-3605-47ee-a170-57d3b97600e2", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "98eb5a27381841b7abdf27f7da8522bf", + "model_id": "1c183206b68b4c5d993053ebbe9b86ae", "version_major": 2, "version_minor": 0 }, @@ -88,12 +92,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d6eec676075b4a9c9c3fa5797a533771", + "model_id": "e2aa45575c9f44ceaa5bd88efdb12a5e", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "MoveHubWidget(n_lanes=4)" + "MoveHubWidget(identifier='V5AFFoX5OfQmQss/ap6fyw==', n_lanes=4)" ] }, "metadata": {}, diff --git a/src/movehub-extension/widget.tsx b/src/movehub-extension/widget.tsx index d077fe5..0a8fd50 100644 --- a/src/movehub-extension/widget.tsx +++ b/src/movehub-extension/widget.tsx @@ -42,12 +42,6 @@ import { DeviceInfoTableComplete } from './components/DeviceInfoTableComplete'; // movehub instance, we would need to re-connect any time // we restart the kernel. -/*interface deviceCache { - [key: string]: any; -}*/ - -//const device_cache: deviceCache = {}; - export class MoveHubModel extends DOMWidgetModel { defaults() { return { @@ -60,6 +54,7 @@ export class MoveHubModel extends DOMWidgetModel { _view_module_version: MoveHubModel.view_module_version, _device_info: {}, name: 'device1', + identifier: '', n_lanes: 3 }; } @@ -96,12 +91,35 @@ export class MoveHubModel extends DOMWidgetModel { console.log(`initialize with n_lanes=${n_lanes}`, this); const name: string = this.get('name'); console.log(`initialize with name=${name}`); - /*if (!(name in device_cache)) { - device_cache[name] = await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); - }*/ - //this.movehub = device_cache[name]; - this.movehub = - await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); + const identifier: string = this.get('identifier'); + console.log(`initialize with identifier=${identifier}`); + /* Check if there is already a paired device */ + if (MoveHubModel.bluetoothManager.deviceList.length > 0) { + MoveHubModel.bluetoothManager.deviceList.forEach(async device => { + if (device instanceof (MoveHub)) { + if (identifier !== '') { /* An identifier is provided by the user in the notebook*/ + if (identifier === device.deviceInfo.identifier) { /*the given identifier fits the one of a connected MoveHub*/ + this.movehub = device + console.warn(`The identifier ${identifier} is known, use the corresponding Move Hub`) + return; + } + else + console.warn('The identifier is unknown, create a new MoveHub') + this.movehub = + await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); + } + else + console.warn('The identifier is not provided, create a new Move Hub'); + this.movehub = + await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); + } + }) + } + else { /* The list is empty, creates a new instance of MoveHub*/ + this.movehub = + await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); + console.warn('There is no device connected, create a new Move Hub'); + } this.on('msg:custom', async (command: any, buffers: any) => { const lane = command['lane']; this.lanes[lane] = this.lanes[lane].then(async () => { @@ -245,8 +263,8 @@ export class MoveHubView extends DOMWidgetView { async render() { this.el.classList.add('jupyter-react-widget'); this.root = ReactDOMClient.createRoot(this.el); - const model = this.model as MoveHubModel; - const movehub = model.movehub; + const model = this.model as MoveHubModel + const movehub = model.movehub this.root.render(); } From db1f95614c5bab71e98f02fd370b6b31f94eb511 Mon Sep 17 00:00:00 2001 From: Florence Haudin Date: Fri, 28 Mar 2025 10:24:10 +0100 Subject: [PATCH 2/3] Replace back call by apply. --- src/movehub-extension/widget.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/movehub-extension/widget.tsx b/src/movehub-extension/widget.tsx index 0a8fd50..574d35e 100644 --- a/src/movehub-extension/widget.tsx +++ b/src/movehub-extension/widget.tsx @@ -153,34 +153,34 @@ export class MoveHubModel extends DOMWidgetModel { this.poll(); break; case 'led': - this.movehub.led.call(this.movehub, args); + this.movehub.led.apply(this.movehub, args); break; case 'ledAsync': - await this.movehub.ledAsync.call(this.movehub, args); + await this.movehub.ledAsync.apply(this.movehub, args); break; case 'motorTime': - this.movehub.motorTime.call(this.movehub, args); + this.movehub.motorTime.apply(this.movehub, args); break; case 'motorTimeMulti': - this.movehub.motorTimeMulti.call(this.movehub, args); + this.movehub.motorTimeMulti.apply(this.movehub, args); break; case 'motorTimeAsync': - await this.movehub.motorTimeAsync.call(this.movehub, args); + await this.movehub.motorTimeAsync.apply(this.movehub, args); break; case 'motorTimeMultiAsync': - await this.movehub.motorTimeMultiAsync.call(this.movehub, args); + await this.movehub.motorTimeMultiAsync.apply(this.movehub, args); break; case 'motorAngle': - this.movehub.motorAngle.call(this.movehub, args); + this.movehub.motorAngle.apply(this.movehub, args); break; case 'motorAngleMulti': - this.movehub.motorAngleMulti.call(this.movehub, args); + this.movehub.motorAngleMulti.apply(this.movehub, args); break; case 'motorAngleAsync': - await this.movehub.motorAngleAsync.call(this.movehub, args); + await this.movehub.motorAngleAsync.apply(this.movehub, args); break; case 'motorAngleMultiAsync': - await this.movehub.motorAngleMultiAsync.call(this.movehub, args); + await this.movehub.motorAngleMultiAsync.apply(this.movehub, args); break; default: console.error(`unknown command "${cmd}"`); From 224680ba31798876a20d1eba067c5bd5d6547a95 Mon Sep 17 00:00:00 2001 From: Florence Haudin Date: Mon, 31 Mar 2025 22:46:59 +0200 Subject: [PATCH 3/3] Update the widget to display different views depending on the presence of already connected devices or not. Add an identifier attribute to the connect method in movehub.py. --- bluetooth_manager/movehub.py | 32 ++++--- examples/introduction.ipynb | 85 ++++++++++--------- src/bluetooth-extension/index.ts | 6 +- src/bluetooth/BluetoothManager.ts | 15 ++-- .../components/CopyToClipboard.tsx | 31 +++++++ .../components/DeviceInfoTableComplete.tsx | 8 ++ .../components/MoveHubList.tsx | 19 +++++ .../components/MoveHubView.tsx | 29 +++++++ src/movehub-extension/index.ts | 2 +- src/movehub-extension/widget.tsx | 60 ++++++------- style/base.css | 18 ++++ style/copy.svg | 3 + 12 files changed, 214 insertions(+), 94 deletions(-) create mode 100644 src/movehub-extension/components/CopyToClipboard.tsx create mode 100644 src/movehub-extension/components/MoveHubList.tsx create mode 100644 src/movehub-extension/components/MoveHubView.tsx create mode 100644 style/copy.svg diff --git a/bluetooth_manager/movehub.py b/bluetooth_manager/movehub.py index 40d2e11..ce74699 100644 --- a/bluetooth_manager/movehub.py +++ b/bluetooth_manager/movehub.py @@ -146,9 +146,10 @@ async def __aexit__(self, exc_type, exc, tb): class MoveHubLaneProxy(object): - def __init__(self, movehub, lane): + def __init__(self, movehub, lane, identifier): self.movehub = movehub self.lane = lane + self.identifier = identifier async def get_distance_async(self): await self._poll() @@ -420,7 +421,7 @@ def __init__(self, *args, **kwargs): self._run_lock = asyncio.Lock() self._lane_locks = [asyncio.Lock() for i in range(self.n_lanes)] - def run_async_program(self, program, lane=0, output=None): + def run_async_program(self, program, lane=0, output=None, identifier=identifier): if lane < 0 or lane >= self.n_lanes : raise RuntimeError(f"lane must be >=0 and < {self.n_lanes} but is {lane}") @@ -429,11 +430,11 @@ def run_async_program(self, program, lane=0, output=None): output = Output() display(output) return asyncio.ensure_future( - self._run_async_program(lane=lane, program=program, output=output) + self._run_async_program(lane=lane, program=program, output=output, identifier=identifier) ) - def run_async_programs_concurrently(self, programs, output=None): + def run_async_programs_concurrently(self, programs, output=None, identifier=None): if output is None: output = Output() display(output) @@ -443,8 +444,10 @@ def run_async_programs_concurrently(self, programs, output=None): futures = [] for lane, program in enumerate(programs): + if identifier is None: + identifier = self.identifier f = asyncio.ensure_future( - self._run_async_program(lane=lane, program=program, output=output) + self._run_async_program(lane=lane, program=program, output=output, identifier=identifier) ) futures.append(f) return futures @@ -460,13 +463,20 @@ def run_program(self, program, output=None): lane_proxy = MoveHubLaneProxy(movehub=self, lane=0) program(lane_proxy, output) - def connect(self, output=None): - async def main(lane, log): + def connect(self, output=None, identifier=None): + async def main(lane, log, identifier): pass + if output is None: + output = Output() + display(output) + + if identifier is None: + identifier = self.identifier - self.run_async_program(main, output=output) + self.run_async_program(main, output=output, identifier=identifier) + - async def _run_async_program(self, lane, program, output): + async def _run_async_program(self, lane, program, output, identifier): def log(*args, **kwargs): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() @@ -481,10 +491,10 @@ def log(*args, **kwargs): self._log = log async with self._lane_locks[lane]: - lane_proxy = MoveHubLaneProxy(movehub=self, lane=lane) + lane_proxy = MoveHubLaneProxy(movehub=self, lane=lane, identifier=identifier) try: await lane_proxy._connect() - await program(lane_proxy, log) + await program(lane_proxy, log, identifier) except Exception as ex: err_str = "".join( traceback.TracebackException.from_exception(ex).format() diff --git a/examples/introduction.ipynb b/examples/introduction.ipynb index 4b65ea7..f0dace3 100644 --- a/examples/introduction.ipynb +++ b/examples/introduction.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 1, "id": "762d552a-dafd-4b4b-9b0c-8b6612d2415b", "metadata": { "scrolled": true @@ -53,51 +53,21 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 2, "id": "aae633ad-bbb2-45c2-a1ff-5fab18146172", "metadata": { "scrolled": true }, - "outputs": [], - "source": [ - "# number of concurrent \"lanes\"\n", - "# Check the list of connected MoveHub in the Bluetooh Device section\n", - "# If you want to use one of them in the widget, use its identifier \n", - "# (in brackets)\n", - "n_lanes = 4 \n", - "identifier =\"V5AFFoX5OfQmQss/ap6fyw==\"\n", - "movehub = MoveHubWidget(n_lanes=n_lanes, identifier=identifier)" - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "9b5f6086-3605-47ee-a170-57d3b97600e2", - "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "1c183206b68b4c5d993053ebbe9b86ae", + "model_id": "b4963924a83143199f0756442e81826b", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e2aa45575c9f44ceaa5bd88efdb12a5e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "MoveHubWidget(identifier='V5AFFoX5OfQmQss/ap6fyw==', n_lanes=4)" + "MoveHubWidget(n_lanes=4)" ] }, "metadata": {}, @@ -105,8 +75,45 @@ } ], "source": [ - "movehub.connect()\n", - "display(movehub) " + "# number of concurrent \"lanes\"\n", + "n_lanes = 4 \n", + "widget = MoveHubWidget(n_lanes=n_lanes)\n", + "output = Output()\n", + "display(widget)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b5f6086-3605-47ee-a170-57d3b97600e2", + "metadata": {}, + "outputs": [], + "source": [ + "# Case 1 : Execute this cell if there is no MoveHub connected\n", + "widget.connect(output=output)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "514c7c39-8b62-4caa-a506-4abb79891592", + "metadata": {}, + "outputs": [], + "source": [ + "# Case 2 : Execute this cell if there is/are one/some MoveHub(s) connected\n", + "# Choose one of them and paste its identifier\n", + "widget.identifier = \"dcRIlxx9l8bQ3T6UVofW8Q==\"\n", + "widget.connect(output=output, identifier=widget.identifier)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2b06dac-4056-435f-800a-60134c48c27e", + "metadata": {}, + "outputs": [], + "source": [ + "display(widget)" ] }, { @@ -251,7 +258,7 @@ "box = ipywidgets.HBox([button_disco, button_motors_ab, button_motor_c])\n", "output = Output()\n", "\n", - "async def disco(lane, log):\n", + "async def disco(lane, log):main\n", " button_disco.disabled = True\n", " for i in range(10):\n", " await lane.set_led_async(LedColor.pink)\n", @@ -265,7 +272,7 @@ " await lane.motor_time_multi_async(seconds=2, power_a=-10, power_b=10)\n", " await lane.motor_time_multi_async(seconds=2, power_a=10, power_b=-10)\n", " await lane.motor_time_multi_async(seconds=2, power_a=-10, power_b=-10)\n", - " button_motors_ab.disabled = False\n", + " button_motors_ab.disabled =main False\n", "\n", "async def motor_c(lane, log):\n", " button_motor_c.disabled = True\n", @@ -401,7 +408,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.1" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/src/bluetooth-extension/index.ts b/src/bluetooth-extension/index.ts index 019a62a..38306d2 100644 --- a/src/bluetooth-extension/index.ts +++ b/src/bluetooth-extension/index.ts @@ -110,7 +110,7 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin = { }).then(async result => { if (result.button.accept) { bluetoothManager.registry.itemsList.forEach(async item => { - if (item.identifier === result.value) { + if (item.deviceType === result.value) { await bluetoothManager.connectDevice(item); } else { console.warn('There is no corresponding item in the registry!'); @@ -172,8 +172,8 @@ export class DropDownRegistry this.registry = registry; registry.itemsList.forEach(item => { const option = document.createElement('option'); - option.value = item.identifier; - option.text = item.identifier; + option.value = item.deviceType; + option.text = item.deviceType; this._selectList.appendChild(option); }); } diff --git a/src/bluetooth/BluetoothManager.ts b/src/bluetooth/BluetoothManager.ts index 0b286d4..f4bb789 100644 --- a/src/bluetooth/BluetoothManager.ts +++ b/src/bluetooth/BluetoothManager.ts @@ -5,6 +5,7 @@ import { buildCompleteIdentifier } from '../bluetooth-extension'; import { IDisposable } from '@lumino/disposable'; import { Dialog, showDialog } from '@jupyterlab/apputils'; + /** * A class used to update the list of connected device and the related signals used to rerender the connected devices section. */ @@ -19,7 +20,7 @@ export class BluetoothManager implements IBluetoothManager { >(this); this._registry = new BluetoothManager.DeviceRegistry(); this._deviceList = []; - this.identifierRegistry = []; + this._identifierRegistry = []; } get deviceList(): Array { @@ -30,6 +31,10 @@ export class BluetoothManager implements IBluetoothManager { return this._registry; } + get identifierRegistry(): Array { + return this._identifierRegistry; + } + async connectDevice( registryItem: IDeviceRegistryItem ): Promise { @@ -88,7 +93,7 @@ export class BluetoothManager implements IBluetoothManager { this._registry.add(registryItem); this.registeredByAPlugin.emit(this._registry); console.warn( - `New item from category ${registryItem.identifier} is added to the registry.` + `New item from category ${registryItem.deviceType} is added to the registry.` ); return this._registry; } @@ -126,7 +131,6 @@ export class BluetoothManager implements IBluetoothManager { else { return; } - } private _deviceList: Array; @@ -136,7 +140,7 @@ export class BluetoothManager implements IBluetoothManager { BluetoothManager.DeviceRegistry >; private _registry: BluetoothManager.DeviceRegistry; - public identifierRegistry: Array; + private _identifierRegistry: Array; } export namespace BluetoothManager { @@ -277,10 +281,11 @@ export interface IBluetoothManager { >; get deviceList(): Array; get registry(): BluetoothManager.DeviceRegistry; + get identifierRegistry(): Array; } export interface IDeviceRegistryItem { - identifier: string; + deviceType: string; factory: ( native: BluetoothDevice ) => Promise; diff --git a/src/movehub-extension/components/CopyToClipboard.tsx b/src/movehub-extension/components/CopyToClipboard.tsx new file mode 100644 index 0000000..d45f7ba --- /dev/null +++ b/src/movehub-extension/components/CopyToClipboard.tsx @@ -0,0 +1,31 @@ +import { useState } from 'react'; +import copySVG from '../../../style/copy.svg'; +const copySVGUrl = `data:image/svg+xml;base64,${btoa(copySVG)}`; + +interface ICopyToClipboardProps { + textToCopy: string +} +export default function CopyToClipboard({ textToCopy }: ICopyToClipboardProps) { + + const handleCopyClick = async () => { + try { + await navigator.clipboard.writeText(textToCopy); + } catch (err) { + console.error('Failed to copy text: ', err); + } + }; + return ( +
+
ID:
+ + +
+ ); +} diff --git a/src/movehub-extension/components/DeviceInfoTableComplete.tsx b/src/movehub-extension/components/DeviceInfoTableComplete.tsx index 876f210..0c74665 100644 --- a/src/movehub-extension/components/DeviceInfoTableComplete.tsx +++ b/src/movehub-extension/components/DeviceInfoTableComplete.tsx @@ -34,6 +34,7 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) { Status + MAC address Identifier Led color Battery @@ -51,6 +52,13 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) { )} + + {deviceState.connected ? ( +
{deviceState.primaryMACAddress}
+ ) : ( +
+ )} + {deviceState.connected ? (
{deviceState.identifier}
diff --git a/src/movehub-extension/components/MoveHubList.tsx b/src/movehub-extension/components/MoveHubList.tsx new file mode 100644 index 0000000..1a69a7e --- /dev/null +++ b/src/movehub-extension/components/MoveHubList.tsx @@ -0,0 +1,19 @@ +import { BluetoothManager, IBluetoothManager } from "../../bluetooth/BluetoothManager"; +import CopyToClipboard from "./CopyToClipboard"; + +interface IDeviceListProps { + bluetoothManager: IBluetoothManager +} + +export function MoveHubList({ bluetoothManager }: IDeviceListProps) { + const listItems = bluetoothManager.deviceList.map((item: BluetoothManager.Device, index) => + <> +
+
Move Hub n°{index + 1}
+
+ + ); + return ( +
    {listItems}
+ ) +} \ No newline at end of file diff --git a/src/movehub-extension/components/MoveHubView.tsx b/src/movehub-extension/components/MoveHubView.tsx new file mode 100644 index 0000000..75757fa --- /dev/null +++ b/src/movehub-extension/components/MoveHubView.tsx @@ -0,0 +1,29 @@ +import { IBluetoothManager } from "../../bluetooth/BluetoothManager" +import { MoveHubList } from "./MoveHubList" +import { DeviceInfoTableComplete } from "./DeviceInfoTableComplete" +import { MoveHub } from "../moveHub" + +interface IMoveHubViewProps { + bluetoothManager: IBluetoothManager + areMoveHubsAlreadyConnected: boolean + moveHub: MoveHub | undefined; +} + +export default function MoveHubViewComponent({ areMoveHubsAlreadyConnected, bluetoothManager, moveHub }: IMoveHubViewProps) { + if (areMoveHubsAlreadyConnected === true) { + return ( + <> + {!moveHub ? <>
Warning: there are already one or more Lego Move Hubs connected. If you want to use one of them, you can copy paste its ID from the clipboard:
+ : } + + ) + } + + else { + return ( + <> + {!moveHub ?
There is no device connected yet. Instantiate a new instance of Move Hub class.
: } + + ) + } +} \ No newline at end of file diff --git a/src/movehub-extension/index.ts b/src/movehub-extension/index.ts index 5eecae4..c5951fb 100644 --- a/src/movehub-extension/index.ts +++ b/src/movehub-extension/index.ts @@ -25,7 +25,7 @@ export const addLEGOMoveHubControlPanel = export const moveHubServiceUUID = '00001623-1212-efde-1623-785feabcd123'; export const moveHubCharacteristicUUID = '00001624-1212-efde-1623-785feabcd123'; export const movehubRegistryItem: IDeviceRegistryItem = { - identifier: 'LEGO® Move Hub', + deviceType: 'LEGO® Move Hub', options: { acceptAllDevices: false, filters: [{ services: [moveHubServiceUUID] }], diff --git a/src/movehub-extension/widget.tsx b/src/movehub-extension/widget.tsx index 574d35e..61b3461 100644 --- a/src/movehub-extension/widget.tsx +++ b/src/movehub-extension/widget.tsx @@ -35,7 +35,8 @@ import * as ReactDOMClient from 'react-dom/client'; // @ts-expect-error To be fixed import { Root } from 'react-dom/client'; //import { DeviceInfoTableComplete } from './components/DeviceInfoTableComplete'; -import { DeviceInfoTableComplete } from './components/DeviceInfoTableComplete'; +import MoveHubViewComponent from './components/MoveHubView'; + // we use globals for the movehub device since connecting to // them takes a long time. If the model would hold the @@ -92,34 +93,9 @@ export class MoveHubModel extends DOMWidgetModel { const name: string = this.get('name'); console.log(`initialize with name=${name}`); const identifier: string = this.get('identifier'); - console.log(`initialize with identifier=${identifier}`); - /* Check if there is already a paired device */ - if (MoveHubModel.bluetoothManager.deviceList.length > 0) { - MoveHubModel.bluetoothManager.deviceList.forEach(async device => { - if (device instanceof (MoveHub)) { - if (identifier !== '') { /* An identifier is provided by the user in the notebook*/ - if (identifier === device.deviceInfo.identifier) { /*the given identifier fits the one of a connected MoveHub*/ - this.movehub = device - console.warn(`The identifier ${identifier} is known, use the corresponding Move Hub`) - return; - } - else - console.warn('The identifier is unknown, create a new MoveHub') - this.movehub = - await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); - } - else - console.warn('The identifier is not provided, create a new Move Hub'); - this.movehub = - await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); - } - }) - } - else { /* The list is empty, creates a new instance of MoveHub*/ - this.movehub = - await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); - console.warn('There is no device connected, create a new Move Hub'); - } + console.log(`initialize with name=${identifier}`); + + this.on('msg:custom', async (command: any, buffers: any) => { const lane = command['lane']; this.lanes[lane] = this.lanes[lane].then(async () => { @@ -194,10 +170,21 @@ export class MoveHubModel extends DOMWidgetModel { async connect() { const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); - if (!this.movehub.deviceInfo.connected) { - console.log('not connected yet'); + const identifier = this.get('identifier'); + + /*if (!this.movehub.deviceInfo.connected) { + console.log('not connected yet');*/ + if (identifier === '') { this.movehub = await MoveHubModel.bluetoothManager.connectDevice(movehubRegistryItem); + } + else { + const selectedDevice = MoveHubModel.bluetoothManager.deviceList.find(device => device.native.id === identifier); + if (selectedDevice && selectedDevice instanceof MoveHub) { + this.movehub = selectedDevice; + console.log('MoveHub is:', this.movehub); + //} + } for (let i = 0; i < 30; i++) { await sleep(100); if ( @@ -263,11 +250,14 @@ export class MoveHubView extends DOMWidgetView { async render() { this.el.classList.add('jupyter-react-widget'); this.root = ReactDOMClient.createRoot(this.el); - const model = this.model as MoveHubModel - const movehub = model.movehub - this.root.render(); - } + const model = this.model as MoveHubModel; + const moveHub = model.movehub; + const areDevicesConnected = (MoveHubModel.bluetoothManager.deviceList.length !== 0); + this.root.render( + ( + )) + } remove() { this.root?.unmount(); } diff --git a/style/base.css b/style/base.css index 7fa2222..ab87ab8 100644 --- a/style/base.css +++ b/style/base.css @@ -321,3 +321,21 @@ align-items: center; justify-content: center; } + +.input-movehub-id { + width: 200px; + height: 24px; +} + +.copy-button { + display: flex; + align-items: center; + justify-content: center; + border: solid 0; + height: 30px; + width: 30px; +} + +ul { + padding-left: 0; +} \ No newline at end of file diff --git a/style/copy.svg b/style/copy.svg new file mode 100644 index 0000000..fe5fb5a --- /dev/null +++ b/style/copy.svg @@ -0,0 +1,3 @@ + + +