+
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;