diff options
Diffstat (limited to 'src/js')
-rw-r--r-- | src/js/hoverControlModule.js | 75 | ||||
-rw-r--r-- | src/js/main.js | 158 | ||||
-rw-r--r-- | src/js/notification.js | 83 |
3 files changed, 316 insertions, 0 deletions
diff --git a/src/js/hoverControlModule.js b/src/js/hoverControlModule.js new file mode 100644 index 0000000..44c06fb --- /dev/null +++ b/src/js/hoverControlModule.js @@ -0,0 +1,75 @@ +import { notif_alert, notif_warn, notif_info, notif_success } from './notification'; + +export default class hoverControlModule { + #throttle = 0; + #throttleAcc = 0; + #rudder = 0; + #rudderAcc = 0; + #arm = 0; + #armAcc = 0; + + constructor() {} + + acc(accString) { + accString.match(/[A-Z][-,0-9]+/g).forEach((item, i) => { + switch (item.substring(0, 1)) { + case "T": + this.#throttleAcc = parseInt(item.substring(1, item.length)); + break; + case "R": + this.#rudderAcc = parseInt(item.substring(1, item.length)); + break; + case "A": + this.#armAcc = parseInt(item.substring(1, item.length)) == 1; + if (this.#armAcc) { + document.body.classList.add("armed"); + } else { + document.body.classList.remove("armed"); + } + break; + case "S": + break; + default: + console.log(`Unkown acc: ${item}`); + } + }); + document.querySelector(".acc-string pre").innerHTML = `T: ${this.#throttleAcc}, R: ${this.#rudderAcc}`; + } + + reset() { + this.setArm(0); + this.setThrottle(0); + this.setRudder(0); + } + + setThrottle(throttle) { + if (!this.#armAcc) { return; } + if (throttle > 100) { throttle = 100; } + if (throttle < 0) { throttle = 0; } + this.#throttle = throttle; + } + getThrottle() { + return this.#throttle; + } + + setRudder(rudder) { + if (!this.#armAcc) { return; } + if (rudder > 90) { rudder = 90; } + if (rudder < -90) { rudder = -90; } + this.#rudder = rudder; + } + getRudder() { + return this.#rudder; + } + + setArm(arm) { + this.#arm = arm; + if (!this.#arm) { + this.#throttle = 0; + this.#rudder = 0; + } + } + getArm() { + return this.#arm; + } +} diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..1c26af9 --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,158 @@ +import nipplejs from 'nipplejs'; +import { requestMicrobit, getServices } from 'microbit-web-bluetooth'; +import hoverControlModule from './hoverControlModule'; +import { notif_alert, notif_warn, notif_info, notif_success } from './notification'; + +let sw = "service-worker.js"; +//if (navigator.serviceWorker) { +// navigator.serviceWorker.register(sw, {scope: '/hoverbit-ble/'}); +//} +navigator.serviceWorker.register( + sw, {scope: '/hoverbit-ble/'} +).then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + if (installingWorker == null) { return; } + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + notif_info("New content is available, relaunch the app to install it."); + } else { + notif_success("Content is cached for offline use."); + } + } + }; + }; +}).catch(error => { + notif_alert("Could not install service worker..."); + console.error("Error during service worker registration:", error); +}); + +document.getElementById("btn_ignore_landscape_warning").addEventListener("click", () => { + document.body.classList.add("ignore-landscape-warning"); +}); + +/* Define and initialize things */ +let hoverControl = new hoverControlModule(); +let bluetoothDevice; +let bluetoothDeviceServices; + +let joystickLeft = nipplejs.create({ + zone: document.querySelector(".joystick-left"), + size: 200, + position: {left: '50%', bottom: '50%'}, + mode: "static", + lockX: true +}); +let joystickRight = nipplejs.create({ + zone: document.querySelector(".joystick-right"), + size: 200, + position: {left: '50%', bottom: '50%'}, + mode: "static", + lockY: true +}); + +/* Setup event_listeners */ +joystickLeft.on("move", (evt, data) => { + let rudder = ((data.distance * 90) / 100); + if (data.angle.degree > 90) { rudder = rudder * -1; } + hoverControl.setRudder(rudder); +}); +joystickLeft.on("end", (evt, data) => { + hoverControl.setRudder(0); +}); + +joystickRight.on("move", (evt, data) => { + let throttle = data.distance; + if (data.angle.degree > 90) { throttle = 0; } + hoverControl.setThrottle(throttle); +}); +joystickRight.on("end", (evt, data) => { + hoverControl.setThrottle(0); +}); + +document.getElementById("btn_arm").addEventListener("click", () => { + hoverControl.setArm(true); +}); + +document.getElementById("btn_disarm").addEventListener("click", () => { + hoverControl.setArm(false); +}); + +document.querySelector("#btn_disconnect").addEventListener("click", () => { + hoverControl.reset(); + bluetoothDevice.gatt.disconnect(); +}); + +let intervalConnectionChecker = setInterval(() => { + if (bluetoothDevice !== undefined && bluetoothDevice) { + if (bluetoothDevice.gatt.connected) { + document.body.classList.add("connected"); + } else { + document.body.classList.remove("connected"); + document.body.classList.remove("armed"); + } + } else if (bluetoothDevice !== undefined) { + bluetoothDevice.gatt.reconnect(); + } +}, 500); + +let intervalSendCommands = setInterval(async() => { + if (bluetoothDevice !== undefined && bluetoothDevice) { + if (bluetoothDevice.gatt.connected && bluetoothDeviceServices.uartService) { + let command = + "T" + hoverControl.getThrottle().toString() + + "R" + hoverControl.getRudder().toString() + + "A" + (hoverControl.getArm() ? "1" : "0") + + "S0" + + ":"; + await bluetoothDeviceServices.uartService.sendText(command); + } + } +}, 70); + +function receiveText(event) { + /* Just make the ping symbol reappear. */ + var elm = document.querySelector(".ping i"); + var newone = elm.cloneNode(true); + elm.parentNode.replaceChild(newone, elm); + + /* Actually handle received text. */ + if ((event.detail).indexOf(":") != -1) { + let parts = (event.detail).split(":"); + + if (parts[0] == "B") { + document.querySelector(".battery-status").innerHTML = parts[1] + "mV"; + } else if (parts[0] == "ACC") { + hoverControl.acc(parts[1]); + } else { + console.log(parts); + } + } else { + notif_warn("Received weird data from MICRO:BIT..."); + console.log(`Received unknown: ${event.detail}`); + } +} + +document.getElementById("btn_connect").onclick = async () => { + if (bluetoothDevice !== undefined && bluetoothDevice.gatt.connected) { + bluetoothDevice.disconnect(); + } + + const device = await requestMicrobit(window.navigator.bluetooth); + bluetoothDevice = device; + + if (device) { + hoverControl.reset(); + const services = await getServices(device); + bluetoothDeviceServices = services; + + if (bluetoothDeviceServices.deviceInformationService) { + // logJson(await services.deviceInformationService.readDeviceInformation()); + } + + if (services.uartService) { + services.uartService.addEventListener("receiveText", receiveText); + } + } +} diff --git a/src/js/notification.js b/src/js/notification.js new file mode 100644 index 0000000..c2eb14c --- /dev/null +++ b/src/js/notification.js @@ -0,0 +1,83 @@ +let notif_queue = []; + +function notif(notif_c) { + let notification_area = document.querySelector(".statusline .notification-area"); + + if (notification_area.querySelector(".notification") === null) { + let notif_elem = document.createElement("div"); + notif_elem.className = "notification"; + notif_elem.appendChild(notif_c[0]); + notif_elem.appendChild(notif_c[1]); + + notification_area.appendChild(notif_elem); + + setTimeout(() => { + notification_area.removeChild(notif_elem); + if (notif_queue.length > 0) { + notif(notif_queue.pop()); + } + }, 5000); + } else { + notif_queue.push(notif_c); + } +} + +export function notif_alert(alert_str) { + let div = document.createElement("div"); + div.className = "notification-content"; + + let text = document.createElement("p"); + text.innerHTML = alert_str; + div.appendChild(text); + + let icon = document.createElement("i"); + icon.className = "alert fas fa-exclamation-triangle"; + div.appendChild(icon); + + notif([icon, div]); +} + +export function notif_warn(alert_str) { + let div = document.createElement("div"); + div.className = "notification-content"; + + let text = document.createElement("p"); + text.innerHTML = alert_str; + div.appendChild(text); + + let icon = document.createElement("i"); + icon.className = "warning fas fa-exclamation-triangle"; + div.appendChild(icon); + + notif([icon, div]); +} + +export function notif_info(info_str) { + let div = document.createElement("div"); + div.className = "notification-content"; + + let text = document.createElement("p"); + text.innerHTML = info_str; + div.appendChild(text); + + let icon = document.createElement("i"); + icon.className = "info fas fa-info-circle"; + div.appendChild(icon); + + notif([icon, div]); +} + +export function notif_success(success_str) { + let div = document.createElement("div"); + div.className = "notification-content"; + + let text = document.createElement("p"); + text.innerHTML = success_str; + div.appendChild(text); + + let icon = document.createElement("i"); + icon.className = "success fas fa-check-circle"; + div.appendChild(icon); + + notif([icon, div]); +} |