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