aboutsummaryrefslogtreecommitdiff
path: root/src/js
diff options
context:
space:
mode:
authorJakob Stendahl <jakob.stendahl@outlook.com>2021-02-06 14:10:00 +0100
committerJakob Stendahl <jakob.stendahl@outlook.com>2021-02-06 14:10:00 +0100
commitb07c2d6792174c9132679671ea7dae77c87349d9 (patch)
tree06c534166d24f3c426a7dff9aa1cbce2e1cfd639 /src/js
parent34a24733ef7159105ab162f870b96e9649bc5c34 (diff)
downloadhoverbit-ble-b07c2d6792174c9132679671ea7dae77c87349d9.tar.gz
hoverbit-ble-b07c2d6792174c9132679671ea7dae77c87349d9.zip
Use parcel, add features
Diffstat (limited to 'src/js')
-rw-r--r--src/js/hoverControlModule.js75
-rw-r--r--src/js/main.js158
-rw-r--r--src/js/notification.js83
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]);
+}