Skip to content

Commit e54eecd

Browse files
authored
Merge pull request #415 from mitmedialab/doodlebot-revised-connection-flow
Doodlebot revised connection flow
2 parents 0ff6477 + 20d85c5 commit e54eecd

File tree

12 files changed

+758
-1101
lines changed

12 files changed

+758
-1101
lines changed

.devcontainer/devcontainer.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"postStartCommand": "pnpm config set store-dir /home/vscode/.local/share/pnpm/store && pnpm install",
3+
"customizations": {
4+
"vscode": {
5+
"extensions": [
6+
"ms-vscode.vscode-typescript-next",
7+
"esbenp.prettier-vscode",
8+
"svelte.svelte-vscode"
9+
],
10+
"settings": {
11+
"[svelte]": {
12+
"editor.defaultFormatter": "svelte.svelte-vscode"
13+
},
14+
"[typescript]": {
15+
"editor.defaultFormatter": "esbenp.prettier-vscode"
16+
},
17+
"editor.formatOnSave": true,
18+
"editor.tabSize": 2,
19+
"svelte.enable-ts-plugin": true
20+
}
21+
}
22+
},
23+
"features": {
24+
"ghcr.io/devcontainers/features/node:latest": {
25+
"version": "20"
26+
},
27+
"ghcr.io/pmalacho-mit/devcontainer-features/git-subrepo:latest": {}
28+
},
29+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04"
30+
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
**/.pnpm-store
12
.DS_Store
23
./**/.DS_Store
34

Lines changed: 151 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<script lang="ts">
22
import type Extension from ".";
33
import { ReactiveInvoke, reactiveInvoke, color } from "$common";
4-
import Doodlebot from "./Doodlebot";
4+
import {
5+
type BLEDeviceWithUartService,
6+
getBLEDeviceWithUartService,
7+
} from "./ble";
8+
import { onDestroy } from "svelte";
59
610
export let extension: Extension;
711
@@ -12,62 +16,63 @@
1216
const invoke: ReactiveInvoke<Extension> = (functionName, ...args) =>
1317
reactiveInvoke((extension = extension), functionName, args);
1418
15-
const storageKeys = {
16-
ssid: "doodlebot-ssid",
17-
password: "doodlebot-password",
18-
ip: "doodlebot-ip",
19-
};
20-
21-
const ipPrefix = "192.168.0.";
19+
let connected = extension.connected;
2220
23-
let error: string;
24-
let ssid = localStorage.getItem(storageKeys.ssid) ?? "";
25-
let password = localStorage.getItem(storageKeys.password) ?? "";
21+
let error: string | null = null;
2622
27-
const savedIP = localStorage.getItem(storageKeys.ip);
28-
const ipParts = [
29-
savedIP?.split(".")[0] ?? "192",
30-
savedIP?.split(".")[1] ?? "168",
31-
savedIP?.split(".")[2] ?? "0",
32-
savedIP?.split(".")[3] ?? "0",
33-
];
23+
let bleDevice: BLEDeviceWithUartService | null = null;
24+
let topLevelDomain: string | null = null;
25+
let topLevelDomainOverride: string =
26+
new URLSearchParams(window.location.search).get("tld") ?? "";
3427
35-
const inputs = {
36-
ssid: null as HTMLInputElement,
37-
password: null as HTMLInputElement,
28+
const errorOut = (message: string, err?: Error) => {
29+
invoke("setIndicator", "disconnected");
30+
if (err) console.error(err);
31+
error = message;
3832
};
3933
40-
const createConnection = async () => {
41-
const ipOverride =
42-
ipParts.filter(Boolean).length === 4 ? ipParts.join(".") : undefined;
43-
34+
const retrieveDevice = async () => {
4435
try {
45-
const doodlebot = await Doodlebot.tryCreate(bluetooth, {
46-
credentials: { ssid, password, ipOverride },
47-
requestBluetooth: extension.requestBluetooth.bind(extension),
48-
saveIP: (ip) => localStorage.setItem(storageKeys.ip, ip),
49-
});
50-
51-
invoke("setDoodlebot", doodlebot);
52-
localStorage.setItem(storageKeys.ssid, ssid);
53-
localStorage.setItem(storageKeys.password, password);
54-
if (ipOverride) localStorage.setItem(storageKeys.ip, ipOverride);
55-
close();
36+
const result = await getBLEDeviceWithUartService(bluetooth);
37+
if ("error" in result) {
38+
invoke("setIndicator", "disconnected");
39+
error = result.error;
40+
} else {
41+
bleDevice = result;
42+
topLevelDomain = result.device.name;
43+
if (!topLevelDomain.endsWith(".direct.mitlivinglab.org"))
44+
topLevelDomain += ".direct.mitlivinglab.org";
45+
}
5646
} catch (err) {
57-
invoke("setIndicator", "disconnected");
58-
console.error(err);
59-
error =
47+
errorOut(
6048
err.message === "Bluetooth adapter not available."
6149
? "Your device does not support BLE connections."
6250
: err.message == "User cancelled the requestDevice() chooser."
6351
? "You must select a device to connect to. Please try again."
6452
: err.message !== "User cancelled the requestDevice() chooser."
6553
? "There was a problem connecting your device, please try again or request assistance."
66-
: err.message;
54+
: err.message,
55+
err
56+
);
6757
}
6858
};
6959
70-
let showAdvanced = true;
60+
const setConnection = () => {
61+
if (!bleDevice || !topLevelDomain)
62+
return errorOut("You must select a device to connect to.");
63+
extension.setDoodlebot(topLevelDomainOverride || topLevelDomain, bleDevice);
64+
extension.connected = true;
65+
close();
66+
};
67+
68+
let showAdvanced = false;
69+
70+
onDestroy(() => {
71+
if (!connected)
72+
try {
73+
extension.setIndicator("disconnected");
74+
} catch (e) {}
75+
});
7176
</script>
7277

7378
<div
@@ -76,33 +81,39 @@
7681
style:background-color={color.ui.white}
7782
style:color={color.text.primary}
7883
>
79-
{#if error}
80-
<div class="error">
81-
{error}
84+
{#if connected}
85+
<h1>You're connected to doodlebot!</h1>
86+
<div>
87+
If you'd like to reconnect, or connect to a different device, you must
88+
reload this page.
8289
</div>
83-
{/if}
84-
{#if bluetooth}
85-
<h1>How to connect to doodlebot</h1>
8690
<div>
87-
<h3>1. Set network credentials:</h3>
88-
<p>
89-
SSID (Network Name):
90-
<input
91-
bind:this={inputs.ssid}
92-
bind:value={ssid}
93-
type="text"
94-
placeholder="e.g. my_wifi"
95-
/>
96-
</p>
97-
<p>
98-
Password:
99-
<input
100-
bind:this={inputs.password}
101-
bind:value={password}
102-
type="password"
103-
placeholder="e.g. 12345"
104-
/>
105-
</p>
91+
<button on:click={() => window.location.reload()}> Reload </button>
92+
</div>
93+
{:else}
94+
{#if error}
95+
<div class="error">
96+
{error}
97+
</div>
98+
{/if}
99+
{#if bluetooth}
100+
<h1>Please connect to a doodlebot...</h1>
101+
<div>
102+
<h3>...by selecting a bluetooth device</h3>
103+
<button class="open" on:click={retrieveDevice}>
104+
Open Bluetooth Menu
105+
</button>
106+
{#if bleDevice}
107+
You've selected 🤖 <strong>{bleDevice.device.name}</strong>.
108+
{/if}
109+
</div>
110+
<div style:margin-top="20px">
111+
<button
112+
class="connect"
113+
disabled={!bleDevice || !topLevelDomain}
114+
on:click={setConnection}>Connect</button
115+
>
116+
</div>
106117
<div>
107118
<button
108119
class="collapser"
@@ -115,31 +126,18 @@
115126
style:max-height={showAdvanced ? "fit-content" : "0"}
116127
>
117128
<p>
118-
IP:
119-
{#each ipParts as part, i}
120-
<input
121-
class="ip"
122-
bind:this={inputs.password}
123-
bind:value={ipParts[i]}
124-
type="text"
125-
placeholder="e.g. 192"
126-
/>
127-
{i < ipParts.length - 1 ? "." : ""}
128-
{/each}
129+
Use top level domain:
130+
<input type="text" bind:value={topLevelDomainOverride} />
131+
{#if topLevelDomain}
132+
(instead of '{topLevelDomain}')
133+
{/if}
129134
</p>
130135
</div>
131136
</div>
132-
</div>
133-
<div>
134-
<h3>2. Select bluetooth device</h3>
135-
136-
<button disabled={!password || !ssid} on:click={createConnection}>
137-
Open Bluetooth Menu
138-
</button>
139-
</div>
140-
{:else}
141-
Uh oh! Your browser does not support bluetooth. Here's how to fix that...
142-
TBD
137+
{:else}
138+
Uh oh! Your browser does not support bluetooth. Please contact an
139+
instructor.
140+
{/if}
143141
{/if}
144142
</div>
145143

@@ -165,7 +163,70 @@
165163
outline: none;
166164
}
167165
168-
.ip {
169-
width: 3rem;
166+
.open {
167+
background-color: dodgerblue;
168+
border: 1px solid dodgerblue;
169+
border-radius: 4px;
170+
box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px 0;
171+
box-sizing: border-box;
172+
color: #fff;
173+
cursor: pointer;
174+
font-family:
175+
"Akzidenz Grotesk BQ Medium",
176+
-apple-system,
177+
BlinkMacSystemFont,
178+
sans-serif;
179+
font-size: 16px;
180+
font-weight: 600;
181+
outline: none;
182+
outline: 0;
183+
padding: 5px 15px;
184+
text-align: center;
185+
transform: translateY(0);
186+
transition:
187+
transform 150ms,
188+
box-shadow 150ms;
189+
user-select: none;
190+
-webkit-user-select: none;
191+
touch-action: manipulation;
192+
}
193+
194+
/* CSS */
195+
.connect {
196+
background-color: #13aa52;
197+
border: 1px solid #13aa52;
198+
border-radius: 4px;
199+
box-shadow: rgba(0, 0, 0, 0.1) 0 2px 4px 0;
200+
box-sizing: border-box;
201+
color: #fff;
202+
cursor: pointer;
203+
font-family:
204+
"Akzidenz Grotesk BQ Medium",
205+
-apple-system,
206+
BlinkMacSystemFont,
207+
sans-serif;
208+
font-size: 16px;
209+
font-weight: 600;
210+
outline: none;
211+
outline: 0;
212+
padding: 10px 25px;
213+
text-align: center;
214+
transform: translateY(0);
215+
transition:
216+
transform 150ms,
217+
box-shadow 150ms;
218+
user-select: none;
219+
-webkit-user-select: none;
220+
touch-action: manipulation;
221+
}
222+
223+
.connect:disabled {
224+
opacity: 0.5;
225+
cursor: not-allowed;
226+
}
227+
228+
.connect:not(:disabled):hover {
229+
box-shadow: rgba(0, 0, 0, 0.15) 0 3px 9px 0;
230+
transform: translateY(-2px);
170231
}
171232
</style>

0 commit comments

Comments
 (0)