Skip to content

Commit

Permalink
adding in support for external BLE
Browse files Browse the repository at this point in the history
  • Loading branch information
pmalacho-mit committed Dec 9, 2024
1 parent 4ab248d commit c57ba37
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 57 deletions.
97 changes: 45 additions & 52 deletions extensions/src/doodlebot/Doodlebot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ const msg = (content: string, type: "success" | "warning" | "error") => {
}
}

type BLECommunication = {
onDisconnect: (...callbacks: (() => void)[]) => void,
onReceive: (callback: (text: CustomEvent<string>) => void) => void,
send: (text: string) => Promise<void>,
}

export default class Doodlebot {
/**
*
Expand Down Expand Up @@ -136,7 +142,11 @@ export default class Doodlebot {
{ requestBluetooth, credentials, saveIP }: CreatePayload,
...filters: BluetoothLEScanFilter[]) {
const { robot, services } = await Doodlebot.getBLE(ble, ...filters);
return new Doodlebot(robot, services, requestBluetooth, credentials, saveIP);
return new Doodlebot({
onReceive: (callback) => services.uartService.addEventListener("receiveText", callback),
onDisconnect: (callback) => ble.addEventListener("gattserverdisconnected", callback),
send: (text) => services.uartService.sendText(text),
}, requestBluetooth, credentials, saveIP);
}

private pending: Pending = { motor: undefined, wifi: undefined, websocket: undefined, ip: undefined };
Expand Down Expand Up @@ -183,28 +193,16 @@ export default class Doodlebot {
private audioCallbacks = new Set<(chunk: Float32Array) => void>();

constructor(
private device: BluetoothDevice,
private services: Services,
private ble: BLECommunication,
private requestBluetooth: RequestBluetooth,
private credentials: NetworkCredentials,
private saveIP: SaveIP
) {
this.attachToBLE(device, services);
this.ble.onReceive(this.receiveTextBLE.bind(this));
this.ble.onDisconnect(this.handleBleDisconnect.bind(this));
this.connectionWorkflow(credentials);
}

private attachToBLE(device: BluetoothDevice, services: Services) {
this.device = device;
this.services = services;
this.subscribe(services.uartService, "receiveText", this.receiveTextBLE.bind(this));
this.subscribe(device, "gattserverdisconnected", this.handleBleDisconnect.bind(this));
}

private subscribe<T extends SubscriptionTarget>(target: T, event: Subscription<T>["event"], listener: Subscription<T>["listener"]) {
target.addEventListener(event, listener);
this.subscriptions.push({ target, event, listener });
}

private formCommand(...args: (string | number)[]) {
return `(${args.join(",")})`;
}
Expand Down Expand Up @@ -497,38 +495,34 @@ export default class Doodlebot {
msg("Could not retrieve IP address from doodlebot", "error")
}

return new Promise<string>(async (resolve) => {
const self = this;
const { device } = this;

//let interval: NodeJS.Timeout;

const reconnectToBluetooth = async () => {
this.requestBluetooth(async (ble) => {
msg("Reconnected to doodlebot", "success");
//clearInterval(interval);
const { robot, services } = await Doodlebot.getBLE(ble);
self.attachToBLE(robot, services);
device.removeEventListener("gattserverdisconnected", reconnectToBluetooth);
msg("Waiting to issue connect command", "warning");
await new Promise((resolve) => setTimeout(resolve, 5000));
msg("Testing doodlebot's IP after reconnect", "warning");
const ip = await self.getIPAddress();
msg(
ip === localIp ? "Doodlebot's IP is local, not valid" : "Doodlebot's IP is valid",
ip === localIp ? "warning" : "success"
)
resolve(this.setIP(ip));
});
}
// return new Promise<string>(async (resolve) => {
// const self = this;
// const { device } = this;

device.addEventListener("gattserverdisconnected", reconnectToBluetooth);
// const reconnectToBluetooth = async () => {
// this.requestBluetooth(async (ble) => {
// msg("Reconnected to doodlebot", "success");
// const { robot, services } = await Doodlebot.getBLE(ble);
// self.attachToBLE(robot, services);
// device.removeEventListener("gattserverdisconnected", reconnectToBluetooth);
// msg("Waiting to issue connect command", "warning");
// await new Promise((resolve) => setTimeout(resolve, 5000));
// msg("Testing doodlebot's IP after reconnect", "warning");
// const ip = await self.getIPAddress();
// msg(
// ip === localIp ? "Doodlebot's IP is local, not valid" : "Doodlebot's IP is valid",
// ip === localIp ? "warning" : "success"
// )
// resolve(this.setIP(ip));
// });
// }

msg("Attempting to connect to wifi", "warning");
// device.addEventListener("gattserverdisconnected", reconnectToBluetooth);

await this.sendBLECommand(command.wifi, credentials.ssid, credentials.password);
//interval = setInterval(() => this.sendBLECommand(command.network), 5000);
});
// msg("Attempting to connect to wifi", "warning");

// await this.sendBLECommand(command.wifi, credentials.ssid, credentials.password);
// });
}

/**
Expand Down Expand Up @@ -635,7 +629,7 @@ export default class Doodlebot {
line.unshift(...newSegment);
return line;
}

printLine() {
console.log(this.wholeString);
}
Expand All @@ -650,7 +644,7 @@ export default class Doodlebot {
line;
lineCounter = 0;
detector;

async followLine() {
let first = true;
const delay = 0.5;
Expand All @@ -660,7 +654,7 @@ export default class Doodlebot {
let prevAngle;
let lineData
await this.detector.initialize(this);

while (true) {
console.log("NEXT");
try {
Expand Down Expand Up @@ -712,11 +706,11 @@ export default class Doodlebot {
const { radius, angle } = command;
console.log(command);
if (command.distance > 0) {
this.sendWebsocketCommand("m", Math.round(12335.6*command.distance), Math.round(12335.6*command.distance), 500, 500);
this.sendWebsocketCommand("m", Math.round(12335.6 * command.distance), Math.round(12335.6 * command.distance), 500, 500);
} else {
this.sendBLECommand("t", radius, angle);
}

}
console.log("after 2");
console.log(this.cumulativeLine);
Expand Down Expand Up @@ -828,8 +822,7 @@ export default class Doodlebot {
* @returns
*/
sendBLECommand(command: Command, ...args: (string | number)[]) {
const { uartService } = this.services;
return uartService.sendText(this.formCommand(command, ...args));
return this.ble.send(this.formCommand(command, ...args));
}

/**
Expand Down
60 changes: 55 additions & 5 deletions extensions/src/doodlebot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,61 @@ export default class DoodlebotBlocks extends extension(details, "ui", "indicator
DIMENSIONS = [480, 360];

init(env: Environment) {
this.openUI("Connect");
this.setIndicator("disconnected");
if (window.isSecureContext) this.openUI("Connect")
else this.connectToDoodlebotWithExternalBLE();
this._loop();
}

private async connectToDoodlebotWithExternalBLE() {
const disconnectMessage = "disconnected";
const urlParams = new URLSearchParams(window.location.search); // Hack for now
let source: MessageEventSource;
let targetOrigin: string;

await new Promise<void>((resolve) => {
const onInitialMessage = (event: MessageEvent) => {
source = event.source;
targetOrigin = event.origin;
window.removeEventListener("message", onInitialMessage);
source.postMessage("ready", { targetOrigin })
resolve();
}
window.addEventListener("message", onInitialMessage);
});

const doodlebot = new Doodlebot(
{
onDisconnect: () => {
window.addEventListener("message", (event) => {
if (event.data !== disconnectMessage) return;
this.setIndicator("disconnected");
alert("Disconnected from robot"); // Decide how to handle (maybe direct user to close window and go back to https)
});
},
onReceive: (callback) => {
window.addEventListener('message', (event) => {
if (event.data === disconnectMessage) return;
callback(event.data);
});
},
send: (text) => new Promise<void>(resolve => {
const onMessageReturn = ({ data }: MessageEvent<string>) => {
if (data !== text) return;
window.removeEventListener("message", onMessageReturn);
resolve();
}
window.addEventListener("message", onMessageReturn);
source.postMessage(text, { targetOrigin });
})
},
() => alert("requestBluetooth called"), // placeholder
{ ssid: urlParams.get("ssid"), password: urlParams.get("password"), ipOverride: urlParams.get("ip") },
() => alert("save IP called"), // placeholder
)
this.setDoodlebot(doodlebot);
}

setDoodlebot(doodlebot: Doodlebot) {
this.doodlebot = doodlebot;
this.setIndicator("connected");
Expand Down Expand Up @@ -569,7 +619,7 @@ export default class DoodlebotBlocks extends extension(details, "ui", "indicator
}

@block({
type: "command",
type: "command",
text: (url) => `import model ${url}`,
arg: {
type: "string",
Expand All @@ -580,13 +630,13 @@ export default class DoodlebotBlocks extends extension(details, "ui", "indicator
await this.useModel(url);
}


@block({
type: "hat",
text: (className) => `when model detects ${className}`,
arg: {
type: "string",
options: function() {
options: function () {
if (!this) {
throw new Error('Context is undefined');
}
Expand All @@ -607,7 +657,7 @@ export default class DoodlebotBlocks extends extension(details, "ui", "indicator
return this.getModelPrediction();
}


async useModel(url: string) {
try {
const modelUrl = this.modelArgumentToURL(url);
Expand Down

0 comments on commit c57ba37

Please sign in to comment.