diff --git a/index.html b/index.html index 22b292e..5121bcb 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,10 @@
- + + + +
Mobile Layout
@@ -48,15 +51,15 @@
-
Override axes
with WASD
+
Override axes
with WASD
-
+
-
Help/Info!
+
Help/Info!
-
+
diff --git a/index.js b/index.js index 587b8a1..1d20d0c 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) + let bleAgent = createBleAgent(); let keyboardAgent = createKeyboardAgent(); let axisAgent = createMobileAxisAgent(); @@ -9,6 +11,9 @@ let buttonCallback = null let mobileElements = document.getElementsByClassName("mobile-only"); let desktopElements = document.getElementsByClassName("desktop-only"); + +let helpRow = document.getElementsByClassName("help-row"); + let infoElement = document.getElementById("info-container"); let hackSpacerElement = document.getElementById("hack-spacer"); @@ -20,7 +25,6 @@ let lastKeyPressed = 1; // --------------------------- state management ------------------------------------ // if (localStorage.getItem(toggleMobile.id) == null) { - let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) if (isMobile) { localStorage.setItem(toggleMobile.id, 'true'); } else { @@ -29,6 +33,8 @@ if (localStorage.getItem(toggleMobile.id) == null) { updateMobileSlider(toggleMobile, false); } + if(isMobile) for (let element of helpRow) element.style.display = "none"; + document.addEventListener('DOMContentLoaded', function () { updateMobileSlider(toggleMobile, toggleState=false); updateSlider(toggleKeyboardWASD, toggleState=false); @@ -106,7 +112,7 @@ function renderLoop() { //bytes 0: packet version //bytes 1-4: axes //bytes 5-6: button states - //bytes 7-19: pressed keyboard keys + //bytes 7-17: pressed keyboard keys let rawPacket = new Uint8Array(1 + 4 + 2 + 11) rawPacket[0] = 0x01; //packet version @@ -225,42 +231,35 @@ function renderLoop() { // -------------------------------------------- bluetooth --------------------------------------- // function createBleAgent() { - let parent = document.getElementById('ButtonBLE') + let buttonBLE = document.getElementById('ble-button') + let statusBLE = document.getElementById('ble-status') + let batteryDisplay = document.getElementById('battery-level') const SERVICE_UUID_PESTOBLE = '27df26c5-83f4-4964-bae0-d7b7cb0a1f54'; const CHARACTERISTIC_UUID_GAMEPAD = '452af57e-ad27-422c-88ae-76805ea641a9'; + const CHARACTERISTIC_UUID_TELEMETRY = '266d9d74-3e10-4fcd-88d2-cb63b5324d0c'; - parent.onclick = changeBleState; - parent.ontouchend = changeBleState; - - function displayBleStatus(status) { - parent.innerHTML = status; - switch (status) { - case 'Connecting': - parent.style.backgroundColor = 'grey'; - break; - case 'Connected': - parent.style.backgroundColor = '#4dae50'; - break; - case 'Disconnecting': - parent.style.backgroundColor = 'grey'; - break; - case 'Not Connected': - parent.style.backgroundColor = 'grey'; - break; - default: - parent.style.backgroundColor = '#eb5b5b'; - } + if (isMobile){ + buttonBLE.ontouchend = updateBLE; + } else { + buttonBLE.onclick = updateBLE; + } + + function displayBleStatus(status, color) { + statusBLE.innerHTML = status; + console.log(status) + statusBLE.style.backgroundColor = color; } - let device; + let device = null; let server; let service; let characteristic_gamepad; - let bleUpdateInProgress = false; + let characteristic_battery; let isConnectedBLE = false; + let bleUpdateInProgress = false; - async function changeBleState() { + async function updateBLE() { if (bleUpdateInProgress) return bleUpdateInProgress = true; if (!isConnectedBLE) connectBLE(); @@ -269,46 +268,89 @@ function createBleAgent() { } async function connectBLE() { - displayBleStatus('Connecting'); try { - device = await navigator.bluetooth.requestDevice({ filters: [{ services: [SERVICE_UUID_PESTOBLE] }] }); + if (device == null){ + displayBleStatus('Connecting', 'black'); + device = await navigator.bluetooth.requestDevice({ filters: [{ services: [SERVICE_UUID_PESTOBLE] }] }); + } else { + displayBleStatus('Attempting Reconnect...', 'black'); + } + server = await device.gatt.connect(); service = await server.getPrimaryService(SERVICE_UUID_PESTOBLE); + characteristic_gamepad = await service.getCharacteristic(CHARACTERISTIC_UUID_GAMEPAD); + try{ + characteristic_battery = await service.getCharacteristic(CHARACTERISTIC_UUID_TELEMETRY); + await characteristic_battery.startNotifications() + await characteristic_battery.addEventListener('characteristicvaluechanged', handleBatteryCharacteristic); + }catch{ + console.log("Pestolink version on robot is real old :(") + } + await device.addEventListener('gattserverdisconnected', robotDisconnect); - displayBleStatus('Connected'); isConnectedBLE = true; + buttonBLE.innerHTML = '❌'; + displayBleStatus('Connected', '#4dae50'); //green } catch (error) { - displayBleStatus("Error"); - console.error('Error:', error); + if (error.name === 'NotFoundError') { + displayBleStatus('No Device Selected', '#eb5b5b'); + } else if (error.name === 'SecurityError') { + displayBleStatus('Security error', '#eb5b5b'); + } else { + console.log( error); + displayBleStatus('Connection failed', '#eb5b5b'); + connectBLE(); + } + } + } + + function handleBatteryCharacteristic(event){ + batteryWatchdogReset(); + let value = event.target.value.getUint8(0); + let voltage = (value/255.0) * 12 + + if(voltage >= 7.6) { + batteryDisplay.style.textShadow = "0 0 2px green, 0 0 2px green, 0 0 2px green, 0 0 2px green"; + } else if (voltage >= 7) { + batteryDisplay.style.textShadow = "0 0 2px green, 0 0 2px yellow, 0 0 2px yellow, 0 0 2px yellow"; + } else { + batteryDisplay.style.textShadow = "0 0 2px red, 0 0 2px red, 0 0 2px red, 0 0 2px red"; } + + batteryDisplay.innerHTML = "🔋︎ " + voltage.toFixed(1) + "V"; } async function disconnectBLE() { displayBleStatus('Disconnecting'); try { + batteryWatchdogStop(); + await device.removeEventListener('gattserverdisconnected', robotDisconnect); await device.gatt.disconnect(); - displayBleStatus('Not Connected'); + displayBleStatus('Not Connected', 'grey'); isConnectedBLE = false; + buttonBLE.innerHTML = '🔗'; + } catch (error) { - displayBleStatus("Error"); + displayBleStatus("Error", '#eb5b5b'); console.error('Error:', error); } } function robotDisconnect(event) { - displayBleStatus('Not Connected'); + batteryWatchdogStop(); + displayBleStatus('Not Connected', 'grey'); isConnectedBLE = false; + connectBLE(); } async function sendPacketBLE(byteArray) { if (!isConnectedBLE) return; - if (bleUpdateInProgress) return; try { await characteristic_gamepad.writeValueWithoutResponse(new Uint8Array(byteArray)); @@ -317,11 +359,30 @@ function createBleAgent() { } } + // Function to create and manage the watchdog timer + let timer; + const timeout = 1000; // 400ms + // Function to start or reset the watchdog timer + function batteryWatchdogReset() { + displayBleStatus('Connected', '#4dae50'); //green + if (timer) {clearTimeout(timer);} + timer = setTimeout(() => { + displayBleStatus('timeout?', 'black'); + }, timeout); + } + // Function to stop the watchdog timer + function batteryWatchdogStop() { + batteryWatchdogReset() + if (timer) {clearTimeout(timer);timer = null;} + } + return { attemptSend: sendPacketBLE }; } + + // -------------------------------------------- mobile --------------------------------------- // function createMobileAxisAgent() { diff --git a/styles.css b/styles.css index 627d663..c522f9c 100644 --- a/styles.css +++ b/styles.css @@ -113,6 +113,7 @@ body { border-radius: 50%; -webkit-user-select: none; user-select: none; + -webkit-user-select: none; cursor: pointer; height: 13vw; width: 13vw; @@ -122,21 +123,52 @@ body { width: 20vw; height: 30vw; display: grid; - grid-template-rows: 1fr 3fr; - grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr 2fr; + grid-template-columns: 1fr 1fr; grid-gap: 1vw; padding: 1vw; } -#ButtonBLE { +#ble-button { background-color: rgb(189, 188, 188); border-radius: 1vw; - font-size: 2vw; + font-size: 4vw; grid-row: 1; grid-column: 1; cursor: pointer; + border: none; + color: #e6e5e5; + text-align: center; + transition: background-color 0.3s, transform 0.1s, box-shadow 0.3s; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} +#ble-button:hover { + background-color: rgb(121, 121, 121); +} +#ble-button:active { + background-color: rgb(159, 159, 159); + transform: translateY(2px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +#battery-level { + background-color: grey; + color: white; + border-radius: 1vw; + font-size: 2vw; + grid-row: 1; + grid-column: 2; } +#ble-status { + background-color: black; + font-size: 2vw; + grid-row: 2; + grid-column: 1; + grid-column: span 2; +} + + #mobile-button button { font-size: 5vw; background-color: grey; @@ -196,6 +228,7 @@ body { padding: 0.5vw; grid-column-gap: 0.5vw; grid-template-columns: 2fr 1fr; + grid-column: span 2; background-color: grey; border-radius: 1vw; justify-content: center;