@robotical/sensors-dashboard
Version:
A dashboard tool for depicting Marty data by Robotical
196 lines (195 loc) • 8.68 kB
JavaScript
"use strict";
require("core-js/modules/esnext.iterator.filter.js");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isMicroBitWebBluetoothSupported = exports.isMicroBitDevice = exports.default = void 0;
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.array-buffer.slice.js");
require("core-js/modules/es.typed-array.uint8-array.js");
require("core-js/modules/es.typed-array.fill.js");
require("core-js/modules/es.typed-array.set.js");
require("core-js/modules/es.typed-array.sort.js");
require("core-js/modules/es.typed-array.to-locale-string.js");
require("core-js/modules/esnext.iterator.constructor.js");
require("core-js/modules/esnext.iterator.for-each.js");
require("core-js/modules/es.symbol.description.js");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
const MICROBIT_SERVICE = 0xf005;
const MICROBIT_SENSOR_CHARACTERISTIC = "5261da01-fa7e-42ab-850b-7c80220097cc";
const SENSOR_DATA_TIMEOUT_MS = 4500;
const getWebBluetooth = () => navigator.bluetooth;
const signed16 = (high, low) => {
let value = low | high << 8;
if (value >= 1 << 15) {
value -= 1 << 16;
}
return value;
};
const copySensors = sensors => _objectSpread(_objectSpread({}, sensors), {}, {
touchPins: [...sensors.touchPins]
});
const isMicroBitWebBluetoothSupported = () => {
var _getWebBluetooth;
return typeof navigator !== "undefined" && typeof ((_getWebBluetooth = getWebBluetooth()) === null || _getWebBluetooth === void 0 ? void 0 : _getWebBluetooth.requestDevice) === "function";
};
exports.isMicroBitWebBluetoothSupported = isMicroBitWebBluetoothSupported;
const isMicroBitDevice = device => Boolean(device && typeof device === "object" && device.isMicroBitWebBluetooth);
exports.isMicroBitDevice = isMicroBitDevice;
class MicroBitWebBluetooth {
constructor() {
_defineProperty(this, "id", "");
_defineProperty(this, "name", "micro:bit");
_defineProperty(this, "deviceType", "micro:bit");
_defineProperty(this, "isMicroBitWebBluetooth", true);
_defineProperty(this, "sensors", MicroBitWebBluetooth.defaultSensors());
_defineProperty(this, "device", null);
_defineProperty(this, "server", null);
_defineProperty(this, "sensorCharacteristic", null);
_defineProperty(this, "sensorListeners", new Set());
_defineProperty(this, "disconnectListeners", new Set());
_defineProperty(this, "receiveTimeoutId", null);
_defineProperty(this, "didEmitDisconnect", false);
_defineProperty(this, "handleCharacteristicValueChanged", event => {
var _event$target;
this.handleSensorData((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.value);
});
_defineProperty(this, "handleGattServerDisconnected", () => {
this.clearReceiveTimeout();
this.releaseConnection();
this.emitDisconnect();
});
}
static defaultSensors() {
return {
tiltX: 0,
tiltY: 0,
buttonA: 0,
buttonB: 0,
touchPins: [0, 0, 0],
gestureState: 0
};
}
async connect() {
const bluetooth = getWebBluetooth();
if (!bluetooth) {
throw new Error("Web Bluetooth is not available in this browser.");
}
const device = await bluetooth.requestDevice({
filters: [{
services: [MICROBIT_SERVICE]
}]
});
if (!device.gatt) {
throw new Error("The selected micro:bit does not expose a Bluetooth connection.");
}
this.device = device;
this.id = device.id || "microbit-".concat(Date.now());
this.name = device.name || "micro:bit";
this.didEmitDisconnect = false;
device.addEventListener("gattserverdisconnected", this.handleGattServerDisconnected);
try {
this.server = await device.gatt.connect();
const service = await this.server.getPrimaryService(MICROBIT_SERVICE);
this.sensorCharacteristic = await service.getCharacteristic(MICROBIT_SENSOR_CHARACTERISTIC);
this.sensorCharacteristic.addEventListener("characteristicvaluechanged", this.handleCharacteristicValueChanged);
await this.sensorCharacteristic.startNotifications();
try {
this.handleSensorData(await this.sensorCharacteristic.readValue());
} catch (_unused) {
// Some firmware begins notifying before direct reads are available.
}
this.resetReceiveTimeout();
return this;
} catch (error) {
this.releaseConnection();
throw error;
}
}
disconnect() {
this.clearReceiveTimeout();
this.releaseConnection();
this.emitDisconnect();
}
isConnected() {
var _this$server;
return Boolean((_this$server = this.server) === null || _this$server === void 0 ? void 0 : _this$server.connected);
}
getFriendlyName() {
return this.name || "micro:bit";
}
addSensorListener(callback) {
this.sensorListeners.add(callback);
return () => {
this.sensorListeners.delete(callback);
};
}
addDisconnectListener(callback) {
this.disconnectListeners.add(callback);
return () => {
this.disconnectListeners.delete(callback);
};
}
handleSensorData(value) {
if (!value || value.byteLength < 10) {
return;
}
const data = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
const previous = copySensors(this.sensors);
this.sensors = {
tiltX: signed16(data[0], data[1]),
tiltY: signed16(data[2], data[3]),
buttonA: data[4],
buttonB: data[5],
touchPins: [data[6], data[7], data[8]],
gestureState: data[9]
};
this.sensorListeners.forEach(callback => callback(this.sensors, previous));
this.resetReceiveTimeout();
}
releaseConnection() {
var _this$device, _this$server2;
if (this.sensorCharacteristic) {
this.sensorCharacteristic.removeEventListener("characteristicvaluechanged", this.handleCharacteristicValueChanged);
try {
var _this$sensorCharacter, _this$sensorCharacter2;
(_this$sensorCharacter = (_this$sensorCharacter2 = this.sensorCharacteristic).stopNotifications) === null || _this$sensorCharacter === void 0 ? void 0 : _this$sensorCharacter.call(_this$sensorCharacter2).catch(() => undefined);
} catch (_unused2) {
// Ignore cleanup errors from a device that is already disconnected.
}
}
(_this$device = this.device) === null || _this$device === void 0 ? void 0 : _this$device.removeEventListener("gattserverdisconnected", this.handleGattServerDisconnected);
if ((_this$server2 = this.server) !== null && _this$server2 !== void 0 && _this$server2.connected) {
this.server.disconnect();
}
this.server = null;
this.sensorCharacteristic = null;
this.device = null;
}
resetReceiveTimeout() {
this.clearReceiveTimeout();
this.receiveTimeoutId = window.setTimeout(() => this.disconnect(), SENSOR_DATA_TIMEOUT_MS);
}
clearReceiveTimeout() {
if (this.receiveTimeoutId !== null) {
window.clearTimeout(this.receiveTimeoutId);
this.receiveTimeoutId = null;
}
}
emitDisconnect() {
if (this.didEmitDisconnect) {
return;
}
this.didEmitDisconnect = true;
const listeners = Array.from(this.disconnectListeners);
listeners.forEach(callback => callback(this));
this.sensorListeners.clear();
this.disconnectListeners.clear();
}
}
exports.default = MicroBitWebBluetooth;