Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions bluetooth_manager/movehub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -411,6 +412,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):
Expand All @@ -419,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}")
Expand All @@ -428,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)
Expand All @@ -442,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
Expand All @@ -459,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()
Expand All @@ -480,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()
Expand Down
79 changes: 45 additions & 34 deletions examples/introduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 1,
"id": "762d552a-dafd-4b4b-9b0c-8b6612d2415b",
"metadata": {
"scrolled": true
Expand Down Expand Up @@ -53,42 +53,16 @@
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 2,
"id": "aae633ad-bbb2-45c2-a1ff-5fab18146172",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# number of concurrent \"lanes\"\n",
"n_lanes = 4 \n",
"movehub = MoveHubWidget(n_lanes=n_lanes)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "9b5f6086-3605-47ee-a170-57d3b97600e2",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "98eb5a27381841b7abdf27f7da8522bf",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d6eec676075b4a9c9c3fa5797a533771",
"model_id": "b4963924a83143199f0756442e81826b",
"version_major": 2,
"version_minor": 0
},
Expand All @@ -101,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)"
]
},
{
Expand Down Expand Up @@ -247,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",
Expand All @@ -261,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",
Expand Down Expand Up @@ -397,7 +408,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.1"
"version": "3.13.2"
}
},
"nbformat": 4,
Expand Down
6 changes: 3 additions & 3 deletions src/bluetooth-extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const BluetoothSidebarPlugin: JupyterFrontEndPlugin<void> = {
}).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!');
Expand Down Expand Up @@ -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);
});
}
Expand Down
15 changes: 10 additions & 5 deletions src/bluetooth/BluetoothManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -19,7 +20,7 @@ export class BluetoothManager implements IBluetoothManager {
>(this);
this._registry = new BluetoothManager.DeviceRegistry();
this._deviceList = [];
this.identifierRegistry = [];
this._identifierRegistry = [];
}

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

get identifierRegistry(): Array<string> {
return this._identifierRegistry;
}

async connectDevice(
registryItem: IDeviceRegistryItem
): Promise<BluetoothManager.Device | undefined> {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -126,7 +131,6 @@ export class BluetoothManager implements IBluetoothManager {
else {
return;
}

}

private _deviceList: Array<BluetoothManager.Device>;
Expand All @@ -136,7 +140,7 @@ export class BluetoothManager implements IBluetoothManager {
BluetoothManager.DeviceRegistry
>;
private _registry: BluetoothManager.DeviceRegistry;
public identifierRegistry: Array<string>;
private _identifierRegistry: Array<string>;
}

export namespace BluetoothManager {
Expand Down Expand Up @@ -277,10 +281,11 @@ export interface IBluetoothManager {
>;
get deviceList(): Array<BluetoothManager.Device>;
get registry(): BluetoothManager.DeviceRegistry;
get identifierRegistry(): Array<string>;
}

export interface IDeviceRegistryItem {
identifier: string;
deviceType: string;
factory: (
native: BluetoothDevice
) => Promise<BluetoothManager.Device | undefined>;
Expand Down
31 changes: 31 additions & 0 deletions src/movehub-extension/components/CopyToClipboard.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div style={{display:"flex", alignItems:"center", justifyContent:"center", width: "250px", gap:"10px", margin: "4px 0"}}>
<div>ID: </div>
<input
className='input-movehub-id'
type="text"
value={textToCopy}
placeholder={textToCopy}
/>
<button className='copy-button' onClick={handleCopyClick} title={"Copy ID"}>
<img src={copySVGUrl} alt={"Button with copy icon"} />
</button>
</div>
);
}
8 changes: 8 additions & 0 deletions src/movehub-extension/components/DeviceInfoTableComplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) {
<tr className="custom-table-tr">
<th className="custom-table-th"></th>
<th className="custom-table-th">Status</th>
<th className="custom-table-th">MAC address</th>
<th className="custom-table-th">Identifier</th>
<th className="custom-table-th">Led color</th>
<th className="custom-table-th">Battery</th>
Expand All @@ -51,6 +52,13 @@ export function DeviceInfoTableComplete({ moveHub }: { moveHub: MoveHub }) {
<ColoredCircleWithText color={'red'} text={'disconnected'} />
)}
</td>
<td className="custom-table-td">
{deviceState.connected ? (
<div>{deviceState.primaryMACAddress}</div>
) : (
<div></div>
)}
</td>
<td className="custom-table-td">
{deviceState.connected ? (
<div>{deviceState.identifier}</div>
Expand Down
19 changes: 19 additions & 0 deletions src/movehub-extension/components/MoveHubList.tsx
Original file line number Diff line number Diff line change
@@ -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) =>
<>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: "10px" }}>
<div>Move Hub n°{index + 1}</div> <CopyToClipboard textToCopy={item.native.id} />
</div>
</>
);
return (
<ul>{listItems}</ul>
)
}
Loading