diff options
author | jakob.stendahl <jakob.stendahl@infomedia.dk> | 2023-01-16 09:43:05 +0100 |
---|---|---|
committer | Jakob Stendahl <jakob.stendahl@outlook.com> | 2023-01-16 09:44:19 +0100 |
commit | 1a2aa931a57f586c17cde95748d195f565619910 (patch) | |
tree | 1b7cc05647f57220d981227c0008f10e48cf7624 | |
parent | b7b331dd28ff0fb9763c20c8cc65c0c69cf460c8 (diff) | |
download | microbit-gamepad-1a2aa931a57f586c17cde95748d195f565619910.tar.gz microbit-gamepad-1a2aa931a57f586c17cde95748d195f565619910.zip |
Add some error handling, add Uart service, add event queue for gatt events
-rw-r--r-- | package-lock.json | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/index.html | 2 | ||||
-rw-r--r-- | src/js/main.js | 17 | ||||
-rw-r--r-- | src/js/uBit.js | 161 |
5 files changed, 163 insertions, 23 deletions
diff --git a/package-lock.json b/package-lock.json index eb14828..191c6d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "microbit-gamepad", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "microbit-gamepad", - "version": "1.0.2", + "version": "1.0.3", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-free": "^5.15.2" diff --git a/package.json b/package.json index e195b30..e51e650 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "microbit-gamepad", - "version": "1.0.2", + "version": "1.0.3", "description": "", "license": "MIT", "scripts": { diff --git a/src/index.html b/src/index.html index 525c5de..9272a78 100644 --- a/src/index.html +++ b/src/index.html @@ -40,7 +40,7 @@ <select name="layout" id="layout"> <option value="1">DPad + Buttons</option> <option value="2">DPad + DPad</option> - <option value="9">Joystick + Joystick (not micro:bit compatible)</option> + <option value="9">Joystick + Joystick (Needs more advanced micro:bit code)</option> </select> </label> <label class="form-control form-cb" for="show-gamepad-alt-text"> diff --git a/src/js/main.js b/src/js/main.js index 3a6f9e9..0b80e3a 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -115,11 +115,15 @@ document.querySelector("#btn_hide_settings").addEventListener("click", () => { document.querySelector("#btn_disconnect").addEventListener("click", () => { ubit.disconnect(); }); -document.getElementById("btn_connect").addEventListener("click", () => { +document.getElementById("btn_connect").addEventListener("click", async () => { if (!navigator.bluetooth) { notif_alert("You need a bluetooth enabled browser for this app to work, try chrome."); } - ubit.searchDevice(); + try { + await ubit.searchDevice(); + } catch (e) { + notif_alert(`Could not connect to device: ${e}.`); + } }); /* Handle gamepad events */ @@ -184,7 +188,14 @@ gamepad.onTouchEvent(e => { } } if ((ubit.isConnected()) && (event_value != null)) { - ubit.eventService.sendEvent(event_type, event_value); + ubit.sendEvent(event_type, event_value); + } + + if ((e.id == "right") && e.hasOwnProperty("x")) { + ubit.sendUart(`x:${e.x}\n`); + } + if ((e.id == "left") && e.hasOwnProperty("y")) { + ubit.sendUart(`y:${e.y}\n`); } }); diff --git a/src/js/uBit.js b/src/js/uBit.js index 0275ff9..9ed8342 100644 --- a/src/js/uBit.js +++ b/src/js/uBit.js @@ -1,3 +1,4 @@ +import { notif_alert, notif_warn, notif_info, notif_success } from './notification'; /* * This code is written with a lot of help from these resources: * https://github.com/antefact/microBit.js/blob/master/src/microBit.js @@ -10,7 +11,7 @@ const UART_TX_CHARACTERISTIC_UUID = "6e400002-b5a3-f393-e0a9-e50e24dc /* Used for writing UART data to micro bit */ const UART_RX_CHARACTERISTIC_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; /* The event service characteristic (which extends the uBit message bus over bluetooth) */ -const EVENT_SERVICE_CHARACTERISTIC_UUID = "e95d93af-251d-470a-a062-fa1922dfa9a8"; +const EVENT_SERVICE_UUID = "e95d93af-251d-470a-a062-fa1922dfa9a8"; /* This should be read once connected, as the ubit will advertise which events it wants to subscribe to */ const UBIT_REQUIREMENT_CHARACTERISTIC_UUID = "e95db84c-251d-470a-a062-fa1922dfa9a8"; /* The characteristic where we should write the events we wish to be informed of from the microbit */ @@ -43,50 +44,134 @@ export const MESEvents = { } class BluetoothService { + static gattEventQueue = []; SERVICE_UUID = null; + + static doGattEvent() { + if (BluetoothService.gattEventQueue <= 0) { return; } + BluetoothService.gattEventQueue.pop()(); + } } class EventService extends BluetoothService { - static SERVICE_UUID = EVENT_SERVICE_CHARACTERISTIC_UUID; + /* Implements methods for interacting with microbit EventService */ + static SERVICE_UUID = EVENT_SERVICE_UUID; service; constructor(service, ubitEvent) { super(); this.service = service; this.ubitEvent = ubitEvent; - console.log("EventService initialized."); + console.debug("EventService initialized."); } sendEvent(event_type, event_value) { - this.ubitEvent.writeValue( - new Uint16Array([event_type, event_value]) - ); + BluetoothService.gattEventQueue.push(() => { + this.ubitEvent.writeValue( + new Uint16Array([event_type, event_value]) + ); + }); } static async getService(gattServer) { console.debug("Getting EventService"); let service = await gattServer.getPrimaryService(EventService.SERVICE_UUID); - console.debug("Getting UBitevent characteristic"); + console.debug("Getting ClientEvent characteristic"); let ubitEventCharacteristic = await service.getCharacteristic(CLIENTEVENT_CHARACTERISTIC_UUID); return new EventService(service, ubitEventCharacteristic); } } +class UartService extends BluetoothService { + /* Implements methods for interacting with microbit UartService */ + static SERVICE_UUID = UART_SERVICE_UUID; + handlers = []; + + constructor(service, uartTx, uartRx) { + super(); + this.service = service; + this.uartTx = uartTx; + this.uartRx = uartRx; + console.debug("UartService initialized."); + } + + async sendUart(str, isVolatile=true) { + let encoder = new TextEncoder(); + try { + await this.uartRx.writeValue( + encoder.encode(str) + ) + } catch (e) { + if (!isVolatile) { + console.error(e); + } + } + } + + #onUartTx(e) { + for (let i = 0; i < this.handlers.length; i++) { + this.handlers[i](); + } + } + + onUartTx(callback) { + this.handlers.push(callback); + } + + static async getService(gattServer) { + console.debug("Getting UartService"); + let service = await gattServer.getPrimaryService(UartService.SERVICE_UUID); + + console.debug("Getting Uart characteristics"); + + let uartTxCharacteristic = await service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID); + await uartTxCharacteristic.startNotifications(); + await uartTxCharacteristic.addEventListener("characteristicvaluechanged", (e) => { + this.#onUartTx(e); + }); + + let uartRxCharacteristic = await service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID); + + return new UartService(service, uartTxCharacteristic, uartRxCharacteristic); + } + + +} + export class uBitBLE { eventService; + eventServiceAvailable = false; + uartService; + uartServiceAvailable = false; + uartTxHandlers = []; device; constructor() { - this.onConnectCallback = function() {}; - this.onDisconnectCallback = function() {}; + this.onConnectCallback = []; + this.onDisconnectCallback = []; + this.pushInterval = setInterval(BluetoothService.doGattEvent, 40); + } + + #onDisconnect(e) { + console.debug("Device disconnected", e); + for (let i = 0; i < this.onDisconnectCallback.length; i++) { + this.onDisconnectCallback[i](); + } + } + + #onConnect() { + console.debug("Device connected"); + for (let i = 0; i < this.onConnectCallback.length; i++) { + this.onConnectCallback[i](); + } } onConnect(callbackFunction) { - this.onConnectCallback = callbackFunction; + this.onConnectCallback.push(callbackFunction); } onDisconnect(callbackFunction) { - this.onDisconnectCallback = callbackFunction; + this.onDisconnectCallback.push(callbackFunction); } isConnected() { @@ -106,19 +191,63 @@ export class uBitBLE { async searchDevice() { this.device = await navigator.bluetooth.requestDevice({ filters: [{namePrefix: "BBC micro:bit"}], - optionalServices: [EVENT_SERVICE_CHARACTERISTIC_UUID] + optionalServices: [EVENT_SERVICE_UUID, UART_SERVICE_UUID] }); - this.device.addEventListener('gattserverdisconnected', this.onDisconnectCallback); + this.device.addEventListener('gattserverdisconnected', (e) => this.#onDisconnect(e)); console.log("Connected to new device", this.device.name, this.device.id); console.debug("Connection to GATT server..."); const server = await this.device.gatt.connect() - this.onConnectCallback(); + this.#onConnect(); + console.debug("Getting services..."); - const eventService = await EventService.getService(server); - this.eventService = eventService; + try { + const eventService = await EventService.getService(server); + this.eventService = eventService; + this.eventServiceAvailable = true; + } catch (e) { + this.eventServiceAvailable = false; + console.debug("Could not get EventService"); + notif_warn("Connected device's firmware does not support bluetooth EventService, gamepad will not work."); + } + + try { + const uartService = await UartService.getService(server); + this.uartService = uartService; + for (let i = 0; i < this.uartTxHandlers.length; i++) { + this.uartService.onUartTx(this.uartTxHandlers[i]); + } + this.uartServiceAvailable = true; + } catch (e) { + this.uartServiceAvailable = false; + console.debug("Could not get UartService", e) + notif_info("Connected device's firmware does not support bluetooth UartService, joysticks won't work."); + } + } + + sendEvent(event_type, event_value) { + if (this.isConnected() && this.eventServiceAvailable) { + this.eventService.sendEvent(event_type, event_value); + } else { + console.debug(`Could not send event {${event_type}, ${event_value}}, because: ${this.isConnected() ? "Device does not have EventService characteristic" : "No device connected"}.`); + } + } + + sendUart(str) { + if (this.isConnected() && this.uartServiceAvailable) { + this.uartService.sendUart(str); + } else { + console.debug(`Could not send uart data, because: ${this.isConnected() ? "Device does not have UartService characteristic" : "No device connected"}.`); + } + } + + onUartTx(callback) { + this.uartTxHandlers.push(callback); + if (this.uartServiceAvailable) { + this.uartService.onUartTx(callback); + } } } |