@@ -415,6 +415,116 @@ async def __rest_login(self):
415415 )
416416 return True
417417
418+ @staticmethod
419+ def __first_value (data : dict [str , Any ], * keys : str ) -> Any :
420+ """Return the first non-None value from data for the given keys."""
421+ for key in keys :
422+ if key in data and data [key ] is not None :
423+ return data [key ]
424+ return None
425+
426+ @staticmethod
427+ def __to_bool (value : Any , default : bool = True ) -> bool :
428+ """Convert mixed payload boolean values to bool."""
429+ if value is None :
430+ return default
431+ if isinstance (value , bool ):
432+ return value
433+ if isinstance (value , (int , float )):
434+ return bool (value )
435+ if isinstance (value , str ):
436+ return value .strip ().lower () in ("1" , "true" , "yes" , "on" , "up" )
437+ return default
438+
439+ def __build_rest_device (
440+ self , entry : dict [str , Any ], interface_type : str | None
441+ ) -> Device :
442+ """Map a REST host entry to Device."""
443+ detected_interface = self .__first_value (
444+ entry ,
445+ "interface_type" ,
446+ "interfaceType" ,
447+ "interface" ,
448+ "connectionType" ,
449+ "connection_type" ,
450+ "type" ,
451+ )
452+ if interface_type is None and isinstance (detected_interface , str ):
453+ normalized = detected_interface .lower ()
454+ if "wifi" in normalized or "wireless" in normalized or "wlan" in normalized :
455+ interface_type = "wifi"
456+ elif "ethernet" in normalized or "eth" in normalized or "lan" in normalized :
457+ interface_type = "ethernet"
458+ else :
459+ interface_type = detected_interface
460+
461+ return Device (
462+ uid = self .__first_value (entry , "id" , "uid" ),
463+ phys_address = self .__first_value (
464+ entry , "macAddress" , "mac_address" , "phys_address"
465+ ),
466+ ip_address = self .__first_value (entry , "ipAddress" , "ip_address" ),
467+ host_name = self .__first_value (entry , "hostname" , "host_name" , "name" ),
468+ user_host_name = self .__first_value (
469+ entry , "friendlyname" , "friendly_name" , "user_host_name"
470+ ),
471+ active = self .__to_bool (
472+ self .__first_value (entry , "active" , "isActive" ), True
473+ ),
474+ interface_type = interface_type ,
475+ detected_device_type = self .__first_value (
476+ entry , "devicetype" , "deviceType" , "detected_device_type"
477+ ),
478+ )
479+
480+ def __extract_rest_home_hosts (self , data : Any ) -> list [Device ]:
481+ """Parse /api/v1/home hosts payload."""
482+ if isinstance (data , list ):
483+ if not data :
484+ return []
485+ home = data [0 ]
486+ elif isinstance (data , dict ):
487+ home = data
488+ else :
489+ raise UnknownException ("Invalid response from /api/v1/home" )
490+
491+ if not isinstance (home , dict ):
492+ raise UnknownException ("Invalid response from /api/v1/home" )
493+
494+ devices : list [Device ] = []
495+ for entry in home .get ("wirelessListDevice" , []):
496+ if isinstance (entry , dict ):
497+ devices .append (self .__build_rest_device (entry , "wifi" ))
498+
499+ for entry in home .get ("ethernetListDevice" , []):
500+ if isinstance (entry , dict ):
501+ devices .append (self .__build_rest_device (entry , "ethernet" ))
502+
503+ return devices
504+
505+ def __extract_rest_hosts (self , data : Any ) -> list [Device ]:
506+ """Parse /api/v1/hosts payload."""
507+ hosts : list [dict [str , Any ]]
508+ if isinstance (data , list ):
509+ hosts = [entry for entry in data if isinstance (entry , dict )]
510+ elif isinstance (data , dict ):
511+ raw_hosts = self .__first_value (
512+ data ,
513+ "hosts" ,
514+ "Hosts" ,
515+ "list" ,
516+ "listDevice" ,
517+ "list_device" ,
518+ "devices" ,
519+ )
520+ if not isinstance (raw_hosts , list ):
521+ raise UnknownException ("Invalid response from /api/v1/hosts" )
522+ hosts = [entry for entry in raw_hosts if isinstance (entry , dict )]
523+ else :
524+ raise UnknownException ("Invalid response from /api/v1/hosts" )
525+
526+ return [self .__build_rest_device (entry , None ) for entry in hosts ]
527+
418528 def __should_fallback_to_rest (self , exception : Exception ) -> bool :
419529 """Return True when legacy API failure indicates a REST-only router."""
420530 if isinstance (exception , UnsupportedHostException ):
@@ -663,40 +773,23 @@ async def get_device_info(self) -> DeviceInfo:
663773 async def get_hosts (self , only_active : bool | None = False ) -> list [Device ]:
664774 """Retrieve hosts connected to Sagemcom F@st device."""
665775 if self ._active_api_mode == ApiMode .REST :
666- data = await self .__rest_request ("GET" , "/api/v1/home" )
667- if not data or not isinstance (data , list ):
668- raise UnknownException ("Invalid response from /api/v1/home" )
669-
670- home = data [0 ]
776+ rest_errors : list [Exception ] = []
671777 devices : list [Device ] = []
672778
673- for entry in home .get ("wirelessListDevice" , []):
674- devices .append (
675- Device (
676- uid = entry .get ("id" ),
677- phys_address = entry .get ("macAddress" ),
678- ip_address = entry .get ("ipAddress" ),
679- host_name = entry .get ("hostname" ),
680- user_host_name = entry .get ("friendlyname" ),
681- active = entry .get ("active" , True ),
682- interface_type = "wifi" ,
683- detected_device_type = entry .get ("devicetype" ),
684- )
685- )
686-
687- for entry in home .get ("ethernetListDevice" , []):
688- devices .append (
689- Device (
690- uid = entry .get ("id" ),
691- phys_address = entry .get ("macAddress" ),
692- ip_address = entry .get ("ipAddress" ),
693- host_name = entry .get ("hostname" ),
694- user_host_name = entry .get ("friendlyname" ),
695- active = entry .get ("active" , True ),
696- interface_type = "ethernet" ,
697- detected_device_type = entry .get ("devicetype" ),
698- )
699- )
779+ for endpoint , parser in (
780+ ("/api/v1/home" , self .__extract_rest_home_hosts ),
781+ ("/api/v1/hosts" , self .__extract_rest_hosts ),
782+ ):
783+ try :
784+ data = await self .__rest_request ("GET" , endpoint )
785+ devices = parser (data )
786+ break
787+ except (UnknownException , UnsupportedHostException ) as exception :
788+ rest_errors .append (exception )
789+ else :
790+ if rest_errors :
791+ raise rest_errors [- 1 ]
792+ raise UnknownException ("Unable to retrieve hosts using REST endpoints" )
700793
701794 if only_active :
702795 return [d for d in devices if d .active is True ]
@@ -746,7 +839,9 @@ async def get_port_mappings(self) -> list[PortMapping]:
746839 )
747840 async def reboot (self ):
748841 """Reboot Sagemcom F@st device."""
749- self .__ensure_legacy_api ()
842+ if self ._active_api_mode == ApiMode .REST :
843+ return await self .__rest_request ("POST" , "/api/v1/device/reboot" )
844+
750845 action = {
751846 "id" : 0 ,
752847 "method" : "reboot" ,
0 commit comments