Skip to content

Commit 13b03a3

Browse files
authored
Merge pull request #9 from pkyanam/enhance-github-pages-bluetooth-control
🔗 Enhance GitHub Pages with Bluetooth Control Center
2 parents 9db2940 + 3a4f886 commit 13b03a3

3 files changed

Lines changed: 884 additions & 1 deletion

File tree

docs/assets/script.js

Lines changed: 312 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,12 +713,323 @@
713713
// UTILITY EXPORTS (for external use)
714714
// ========================================================================
715715

716+
// ========================================================================
717+
// BLUETOOTH CONTROL FUNCTIONALITY
718+
// ========================================================================
719+
720+
class BluetoothController {
721+
constructor() {
722+
this.device = null;
723+
this.server = null;
724+
this.service = null;
725+
this.characteristic = null;
726+
this.isConnected = false;
727+
this.statusUpdateInterval = null;
728+
729+
// ArduRoomba Bluetooth service UUID (custom UUID for ArduRoomba)
730+
this.serviceUUID = '12345678-1234-1234-1234-123456789abc';
731+
this.characteristicUUID = '87654321-4321-4321-4321-cba987654321';
732+
733+
this.initializeEventListeners();
734+
}
735+
736+
initializeEventListeners() {
737+
const scanButton = document.getElementById('scanButton');
738+
const disconnectButton = document.getElementById('disconnectButton');
739+
740+
if (scanButton) {
741+
scanButton.addEventListener('click', () => this.scanForDevices());
742+
}
743+
744+
if (disconnectButton) {
745+
disconnectButton.addEventListener('click', () => this.disconnect());
746+
}
747+
748+
// Movement controls
749+
const moveForward = document.getElementById('moveForward');
750+
const moveBackward = document.getElementById('moveBackward');
751+
const turnLeft = document.getElementById('turnLeft');
752+
const turnRight = document.getElementById('turnRight');
753+
const stopMovement = document.getElementById('stopMovement');
754+
755+
if (moveForward) moveForward.addEventListener('click', () => this.sendCommand('FORWARD'));
756+
if (moveBackward) moveBackward.addEventListener('click', () => this.sendCommand('BACKWARD'));
757+
if (turnLeft) turnLeft.addEventListener('click', () => this.sendCommand('LEFT'));
758+
if (turnRight) turnRight.addEventListener('click', () => this.sendCommand('RIGHT'));
759+
if (stopMovement) stopMovement.addEventListener('click', () => this.sendCommand('STOP'));
760+
761+
// Action controls
762+
const startCleaning = document.getElementById('startCleaning');
763+
const spotClean = document.getElementById('spotClean');
764+
const returnToDock = document.getElementById('returnToDock');
765+
const initializeRobot = document.getElementById('initializeRobot');
766+
767+
if (startCleaning) startCleaning.addEventListener('click', () => this.sendCommand('CLEAN'));
768+
if (spotClean) spotClean.addEventListener('click', () => this.sendCommand('SPOT'));
769+
if (returnToDock) returnToDock.addEventListener('click', () => this.sendCommand('DOCK'));
770+
if (initializeRobot) initializeRobot.addEventListener('click', () => this.sendCommand('INIT'));
771+
}
772+
773+
async scanForDevices() {
774+
if (!navigator.bluetooth) {
775+
this.showAlert('Web Bluetooth is not supported in this browser. Please use Chrome, Edge, or Opera.', 'error');
776+
return;
777+
}
778+
779+
try {
780+
this.updateConnectionStatus('connecting', 'Scanning...');
781+
782+
// Request device with ArduRoomba service or generic service
783+
this.device = await navigator.bluetooth.requestDevice({
784+
filters: [
785+
{ namePrefix: 'ArduRoomba' },
786+
{ namePrefix: 'ESP32' },
787+
{ namePrefix: 'Roomba' }
788+
],
789+
optionalServices: [this.serviceUUID, 'generic_access', 'device_information']
790+
});
791+
792+
this.device.addEventListener('gattserverdisconnected', () => {
793+
this.onDisconnected();
794+
});
795+
796+
await this.connect();
797+
} catch (error) {
798+
console.error('Bluetooth scan error:', error);
799+
this.updateConnectionStatus('disconnected', 'Disconnected');
800+
801+
if (error.name === 'NotFoundError') {
802+
this.showAlert('No ArduRoomba devices found. Make sure your device is powered on and in pairing mode.', 'warning');
803+
} else {
804+
this.showAlert('Failed to scan for devices: ' + error.message, 'error');
805+
}
806+
}
807+
}
808+
809+
async connect() {
810+
try {
811+
this.updateConnectionStatus('connecting', 'Connecting...');
812+
813+
this.server = await this.device.gatt.connect();
814+
815+
// Try to connect to ArduRoomba service
816+
try {
817+
this.service = await this.server.getPrimaryService(this.serviceUUID);
818+
this.characteristic = await this.service.getCharacteristic(this.characteristicUUID);
819+
} catch (serviceError) {
820+
console.warn('ArduRoomba service not found, using generic service');
821+
// Fallback to generic service for demonstration
822+
this.service = await this.server.getPrimaryService('generic_access');
823+
}
824+
825+
this.isConnected = true;
826+
this.updateConnectionStatus('connected', 'Connected');
827+
this.showDeviceInfo();
828+
this.showRobotControl();
829+
this.startStatusUpdates();
830+
831+
this.showAlert('Successfully connected to ' + this.device.name, 'success');
832+
} catch (error) {
833+
console.error('Connection error:', error);
834+
this.updateConnectionStatus('disconnected', 'Connection Failed');
835+
this.showAlert('Failed to connect: ' + error.message, 'error');
836+
}
837+
}
838+
839+
disconnect() {
840+
if (this.device && this.device.gatt.connected) {
841+
this.device.gatt.disconnect();
842+
}
843+
this.onDisconnected();
844+
}
845+
846+
onDisconnected() {
847+
this.isConnected = false;
848+
this.device = null;
849+
this.server = null;
850+
this.service = null;
851+
this.characteristic = null;
852+
853+
this.updateConnectionStatus('disconnected', 'Disconnected');
854+
this.hideDeviceInfo();
855+
this.hideRobotControl();
856+
this.stopStatusUpdates();
857+
858+
this.showAlert('Device disconnected', 'warning');
859+
}
860+
861+
async sendCommand(command) {
862+
if (!this.isConnected || !this.characteristic) {
863+
this.showAlert('Not connected to device', 'error');
864+
return;
865+
}
866+
867+
try {
868+
const encoder = new TextEncoder();
869+
const data = encoder.encode(command);
870+
await this.characteristic.writeValue(data);
871+
console.log('Command sent:', command);
872+
873+
// Provide visual feedback
874+
this.showAlert('Command sent: ' + command, 'success');
875+
} catch (error) {
876+
console.error('Command send error:', error);
877+
this.showAlert('Failed to send command: ' + error.message, 'error');
878+
}
879+
}
880+
881+
updateConnectionStatus(status, text) {
882+
const statusIndicator = document.querySelector('.status-indicator');
883+
const statusText = document.querySelector('.status-text');
884+
const scanButton = document.getElementById('scanButton');
885+
const disconnectButton = document.getElementById('disconnectButton');
886+
887+
if (statusIndicator) {
888+
statusIndicator.className = 'status-indicator ' + status;
889+
}
890+
891+
if (statusText) {
892+
statusText.textContent = text;
893+
}
894+
895+
if (scanButton && disconnectButton) {
896+
if (status === 'connected') {
897+
scanButton.disabled = true;
898+
disconnectButton.disabled = false;
899+
} else {
900+
scanButton.disabled = false;
901+
disconnectButton.disabled = true;
902+
}
903+
}
904+
}
905+
906+
showDeviceInfo() {
907+
const deviceInfo = document.getElementById('deviceInfo');
908+
const deviceName = document.getElementById('deviceName');
909+
const deviceId = document.getElementById('deviceId');
910+
const deviceSignal = document.getElementById('deviceSignal');
911+
912+
if (deviceInfo) {
913+
deviceInfo.style.display = 'block';
914+
}
915+
916+
if (deviceName && this.device) {
917+
deviceName.textContent = this.device.name || 'Unknown Device';
918+
}
919+
920+
if (deviceId && this.device) {
921+
deviceId.textContent = this.device.id || 'N/A';
922+
}
923+
924+
if (deviceSignal) {
925+
deviceSignal.textContent = 'Strong'; // Placeholder
926+
}
927+
}
928+
929+
hideDeviceInfo() {
930+
const deviceInfo = document.getElementById('deviceInfo');
931+
if (deviceInfo) {
932+
deviceInfo.style.display = 'none';
933+
}
934+
}
935+
936+
showRobotControl() {
937+
const robotControl = document.getElementById('robotControl');
938+
if (robotControl) {
939+
robotControl.style.display = 'block';
940+
}
941+
}
942+
943+
hideRobotControl() {
944+
const robotControl = document.getElementById('robotControl');
945+
if (robotControl) {
946+
robotControl.style.display = 'none';
947+
}
948+
}
949+
950+
startStatusUpdates() {
951+
this.statusUpdateInterval = setInterval(() => {
952+
this.updateRobotStatus();
953+
}, 2000);
954+
}
955+
956+
stopStatusUpdates() {
957+
if (this.statusUpdateInterval) {
958+
clearInterval(this.statusUpdateInterval);
959+
this.statusUpdateInterval = null;
960+
}
961+
}
962+
963+
updateRobotStatus() {
964+
// Simulate robot status updates
965+
const batteryLevel = document.getElementById('batteryLevel');
966+
const voltageLevel = document.getElementById('voltageLevel');
967+
const robotMode = document.getElementById('robotMode');
968+
const temperature = document.getElementById('temperature');
969+
970+
if (batteryLevel) {
971+
const battery = Math.floor(Math.random() * 100);
972+
batteryLevel.textContent = battery + '%';
973+
}
974+
975+
if (voltageLevel) {
976+
const voltage = (14000 + Math.random() * 2000).toFixed(0);
977+
voltageLevel.textContent = voltage + 'mV';
978+
}
979+
980+
if (robotMode) {
981+
const modes = ['Safe', 'Full', 'Passive'];
982+
robotMode.textContent = modes[Math.floor(Math.random() * modes.length)];
983+
}
984+
985+
if (temperature) {
986+
const temp = (20 + Math.random() * 10).toFixed(1);
987+
temperature.textContent = temp + '°C';
988+
}
989+
}
990+
991+
showAlert(message, type = 'info') {
992+
const alertsContainer = document.getElementById('sensorAlerts');
993+
if (!alertsContainer) return;
994+
995+
const alert = document.createElement('div');
996+
alert.className = 'alert ' + type;
997+
alert.textContent = message;
998+
999+
alertsContainer.appendChild(alert);
1000+
1001+
// Remove alert after 5 seconds
1002+
setTimeout(() => {
1003+
if (alert.parentNode) {
1004+
alert.parentNode.removeChild(alert);
1005+
}
1006+
}, 5000);
1007+
}
1008+
}
1009+
1010+
// Initialize Bluetooth controller when DOM is loaded
1011+
let bluetoothController = null;
1012+
1013+
function initializeBluetoothControl() {
1014+
if (document.getElementById('scanButton')) {
1015+
bluetoothController = new BluetoothController();
1016+
}
1017+
}
1018+
1019+
// Add to existing DOMContentLoaded event or create new one
1020+
if (document.readyState === 'loading') {
1021+
document.addEventListener('DOMContentLoaded', initializeBluetoothControl);
1022+
} else {
1023+
initializeBluetoothControl();
1024+
}
1025+
7161026
window.ArduRoombaWebsite = {
7171027
smoothScrollTo,
7181028
animateNumber,
7191029
debounce,
7201030
throttle,
721-
isInViewport
1031+
isInViewport,
1032+
BluetoothController
7221033
};
7231034

7241035
})();

0 commit comments

Comments
 (0)