@@ -455,3 +455,259 @@ def test_disconnect_desktop_mode(self):
455455 WifiService .disconnect (network_module = None )
456456
457457
458+ class TestWifiServiceRSSISorting (unittest .TestCase ):
459+ """Test RSSI-based network prioritization."""
460+
461+ def setUp (self ):
462+ """Set up test fixtures."""
463+ MockSharedPreferences .reset_all ()
464+ WifiService .access_points = {}
465+ WifiService .wifi_busy = False
466+
467+ def tearDown (self ):
468+ """Clean up after tests."""
469+ WifiService .access_points = {}
470+ WifiService .wifi_busy = False
471+ MockSharedPreferences .reset_all ()
472+
473+ def test_networks_sorted_by_rssi_strongest_first (self ):
474+ """Test that networks are sorted by RSSI with strongest first."""
475+ # Create mock networks with different RSSI values
476+ # Format: (ssid, bssid, channel, rssi, security, hidden)
477+ mock_network = MockNetwork (connected = False )
478+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
479+
480+ # Unsorted networks (weak, strong, medium)
481+ mock_wlan ._scan_results = [
482+ (b'WeakNetwork' , b'\xaa \xbb \xcc \xdd \xee \xff ' , 6 , - 85 , 3 , False ),
483+ (b'StrongNetwork' , b'\x11 \x22 \x33 \x44 \x55 \x66 ' , 1 , - 45 , 3 , False ),
484+ (b'MediumNetwork' , b'\x77 \x88 \x99 \xaa \xbb \xcc ' , 11 , - 65 , 3 , False ),
485+ ]
486+
487+ # Configure all as saved networks
488+ WifiService .access_points = {
489+ 'WeakNetwork' : {'password' : 'weak123' },
490+ 'StrongNetwork' : {'password' : 'strong123' },
491+ 'MediumNetwork' : {'password' : 'medium123' }
492+ }
493+
494+ # Track connection attempts
495+ connection_attempts = []
496+
497+ def mock_connect (ssid , password ):
498+ connection_attempts .append (ssid )
499+ # Succeed on first attempt
500+ mock_wlan ._connected = True
501+
502+ mock_wlan .connect = mock_connect
503+
504+ result = WifiService .connect (network_module = mock_network )
505+
506+ self .assertTrue (result )
507+ # Should try strongest first (-45 dBm)
508+ self .assertEqual (connection_attempts [0 ], 'StrongNetwork' )
509+ # Should only try one (first succeeds)
510+ self .assertEqual (len (connection_attempts ), 1 )
511+
512+ def test_multiple_networks_tried_in_rssi_order (self ):
513+ """Test that multiple networks are tried in RSSI order when first fails."""
514+ mock_network = MockNetwork (connected = False )
515+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
516+
517+ # Three networks with different signal strengths
518+ mock_wlan ._scan_results = [
519+ (b'BadNetwork1' , b'\xaa \xbb \xcc \xdd \xee \xff ' , 1 , - 40 , 3 , False ),
520+ (b'BadNetwork2' , b'\x11 \x22 \x33 \x44 \x55 \x66 ' , 6 , - 50 , 3 , False ),
521+ (b'GoodNetwork' , b'\x77 \x88 \x99 \xaa \xbb \xcc ' , 11 , - 60 , 3 , False ),
522+ ]
523+
524+ WifiService .access_points = {
525+ 'BadNetwork1' : {'password' : 'pass1' },
526+ 'BadNetwork2' : {'password' : 'pass2' },
527+ 'GoodNetwork' : {'password' : 'pass3' }
528+ }
529+
530+ # Track attempts and make first two fail
531+ connection_attempts = []
532+
533+ def mock_connect (ssid , password ):
534+ connection_attempts .append (ssid )
535+ # Only succeed on third attempt
536+ if len (connection_attempts ) >= 3 :
537+ mock_wlan ._connected = True
538+
539+ mock_wlan .connect = mock_connect
540+
541+ result = WifiService .connect (network_module = mock_network )
542+
543+ self .assertTrue (result )
544+ # Verify order: strongest to weakest
545+ self .assertEqual (connection_attempts [0 ], 'BadNetwork1' ) # RSSI -40
546+ self .assertEqual (connection_attempts [1 ], 'BadNetwork2' ) # RSSI -50
547+ self .assertEqual (connection_attempts [2 ], 'GoodNetwork' ) # RSSI -60
548+ self .assertEqual (len (connection_attempts ), 3 )
549+
550+ def test_duplicate_ssid_strongest_tried_first (self ):
551+ """Test that with duplicate SSIDs, strongest signal is tried first."""
552+ mock_network = MockNetwork (connected = False )
553+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
554+
555+ # Real-world scenario: Multiple APs with same SSID
556+ mock_wlan ._scan_results = [
557+ (b'MyNetwork' , b'\xaa \xbb \xcc \xdd \xee \xff ' , 1 , - 70 , 3 , False ),
558+ (b'MyNetwork' , b'\x11 \x22 \x33 \x44 \x55 \x66 ' , 6 , - 50 , 3 , False ), # Strongest
559+ (b'MyNetwork' , b'\x77 \x88 \x99 \xaa \xbb \xcc ' , 11 , - 85 , 3 , False ),
560+ ]
561+
562+ WifiService .access_points = {
563+ 'MyNetwork' : {'password' : 'mypass123' }
564+ }
565+
566+ connection_attempts = []
567+
568+ def mock_connect (ssid , password ):
569+ connection_attempts .append (ssid )
570+ # Succeed on first
571+ mock_wlan ._connected = True
572+
573+ mock_wlan .connect = mock_connect
574+
575+ result = WifiService .connect (network_module = mock_network )
576+
577+ self .assertTrue (result )
578+ # Should only try once (first is strongest and succeeds)
579+ self .assertEqual (len (connection_attempts ), 1 )
580+ self .assertEqual (connection_attempts [0 ], 'MyNetwork' )
581+
582+ def test_rssi_order_with_real_scan_data (self ):
583+ """Test with real scan data from actual ESP32 device."""
584+ mock_network = MockNetwork (connected = False )
585+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
586+
587+ # Real scan output from user's example
588+ mock_wlan ._scan_results = [
589+ (b'Channel 8' , b'\xde \xec ^\x8f \x00 A' , 11 , - 47 , 3 , False ),
590+ (b'Baptistus' , b'\xd8 \xec ^\x8f \x00 A' , 11 , - 48 , 7 , False ),
591+ (b'telenet-BD74DC9' , b'TgQ>t\xe7 ' , 11 , - 70 , 3 , False ),
592+ (b'Galaxy S10+64bf' , b'b\x19 \xdf \xef \xb0 \x8f ' , 11 , - 83 , 3 , False ),
593+ (b'Najeeb\xe2 \x80 \x99 s iPhone' , b"F\x07 '\xb8 \x0b 0" , 6 , - 84 , 7 , False ),
594+ (b'DIRECT-83-HP OfficeJet Pro 7740' , b'\x1a `$dk\x83 ' , 1 , - 87 , 3 , False ),
595+ (b'Channel 8' , b'\xde \xec ^\xe1 #w' , 1 , - 91 , 3 , False ),
596+ (b'Baptistus' , b'\xd8 \xec ^\xe1 #w' , 1 , - 91 , 7 , False ),
597+ (b'Proximus-Home-596457' , b'\xf4 \x05 \x95 \xf9 A\xf1 ' , 1 , - 93 , 3 , False ),
598+ (b'Proximus-Home-596457' , b'\xcc \x00 \xf1 j}\x94 ' , 1 , - 93 , 3 , False ),
599+ (b'BASE-9104320' , b'4,\xc4 \xe7 \x01 \xb7 ' , 1 , - 94 , 3 , False ),
600+ ]
601+
602+ # Save several networks
603+ WifiService .access_points = {
604+ 'Channel 8' : {'password' : 'pass1' },
605+ 'Baptistus' : {'password' : 'pass2' },
606+ 'telenet-BD74DC9' : {'password' : 'pass3' },
607+ 'Galaxy S10+64bf' : {'password' : 'pass4' },
608+ }
609+
610+ # Track attempts and fail first to see ordering
611+ connection_attempts = []
612+
613+ def mock_connect (ssid , password ):
614+ connection_attempts .append (ssid )
615+ # Succeed on second attempt
616+ if len (connection_attempts ) >= 2 :
617+ mock_wlan ._connected = True
618+
619+ mock_wlan .connect = mock_connect
620+
621+ result = WifiService .connect (network_module = mock_network )
622+
623+ self .assertTrue (result )
624+ # Expected order: Channel 8 (-47), Baptistus (-48), telenet (-70), Galaxy (-83)
625+ self .assertEqual (connection_attempts [0 ], 'Channel 8' )
626+ self .assertEqual (connection_attempts [1 ], 'Baptistus' )
627+ self .assertEqual (len (connection_attempts ), 2 )
628+
629+ def test_sorting_preserves_network_data_integrity (self ):
630+ """Test that sorting doesn't corrupt or lose network data."""
631+ mock_network = MockNetwork (connected = False )
632+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
633+
634+ # Networks with various attributes
635+ mock_wlan ._scan_results = [
636+ (b'Net3' , b'\xaa \xaa \xaa \xaa \xaa \xaa ' , 11 , - 90 , 3 , False ),
637+ (b'Net1' , b'\xbb \xbb \xbb \xbb \xbb \xbb ' , 1 , - 40 , 7 , True ), # Hidden
638+ (b'Net2' , b'\xcc \xcc \xcc \xcc \xcc \xcc ' , 6 , - 60 , 2 , False ),
639+ ]
640+
641+ WifiService .access_points = {
642+ 'Net1' : {'password' : 'p1' },
643+ 'Net2' : {'password' : 'p2' },
644+ 'Net3' : {'password' : 'p3' }
645+ }
646+
647+ # Track attempts to verify all are tried
648+ connection_attempts = []
649+
650+ def mock_connect (ssid , password ):
651+ connection_attempts .append (ssid )
652+ # Never succeed, try all
653+ pass
654+
655+ mock_wlan .connect = mock_connect
656+
657+ result = WifiService .connect (network_module = mock_network )
658+
659+ self .assertFalse (result ) # No connection succeeded
660+ # Verify all 3 were attempted in RSSI order
661+ self .assertEqual (len (connection_attempts ), 3 )
662+ self .assertEqual (connection_attempts [0 ], 'Net1' ) # RSSI -40
663+ self .assertEqual (connection_attempts [1 ], 'Net2' ) # RSSI -60
664+ self .assertEqual (connection_attempts [2 ], 'Net3' ) # RSSI -90
665+
666+ def test_no_saved_networks_in_scan (self ):
667+ """Test behavior when scan finds no saved networks."""
668+ mock_network = MockNetwork (connected = False )
669+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
670+
671+ mock_wlan ._scan_results = [
672+ (b'UnknownNet1' , b'\xaa \xbb \xcc \xdd \xee \xff ' , 1 , - 50 , 3 , False ),
673+ (b'UnknownNet2' , b'\x11 \x22 \x33 \x44 \x55 \x66 ' , 6 , - 60 , 3 , False ),
674+ ]
675+
676+ WifiService .access_points = {
677+ 'SavedNetwork' : {'password' : 'pass123' }
678+ }
679+
680+ connection_attempts = []
681+
682+ def mock_connect (ssid , password ):
683+ connection_attempts .append (ssid )
684+
685+ mock_wlan .connect = mock_connect
686+
687+ result = WifiService .connect (network_module = mock_network )
688+
689+ self .assertFalse (result )
690+ # No attempts should be made
691+ self .assertEqual (len (connection_attempts ), 0 )
692+
693+ def test_rssi_logging_shows_signal_strength (self ):
694+ """Test that RSSI value is logged during scan (for debugging)."""
695+ # This is more of a documentation test to verify the log format
696+ mock_network = MockNetwork (connected = False )
697+ mock_wlan = mock_network .WLAN (mock_network .STA_IF )
698+
699+ mock_wlan ._scan_results = [
700+ (b'TestNet' , b'\xaa \xbb \xcc \xdd \xee \xff ' , 1 , - 55 , 3 , False ),
701+ ]
702+
703+ WifiService .access_points = {
704+ 'TestNet' : {'password' : 'pass' }
705+ }
706+
707+ # The connect method now logs "Found network 'TestNet' (RSSI: -55 dBm)"
708+ # This test just verifies it doesn't crash
709+ result = WifiService .connect (network_module = mock_network )
710+ # Since mock doesn't actually connect, this will likely be False
711+ # but the important part is the code runs without error
712+
713+
0 commit comments