4444 AuthenticationException ,
4545 BadRequestException ,
4646 InvalidSessionException ,
47+ LoginConnectionException ,
4748 LoginRetryErrorException ,
4849 LoginTimeoutException ,
4950 MaximumSessionCountException ,
5354 UnknownPathException ,
5455 UnsupportedHostException ,
5556)
56- from .models import Device , DeviceInfo , PortMapping
57+ from .models import Device , DeviceInfo , PortMapping , SpeedTestResult
5758
5859
5960async def retry_login (invocation : Mapping [str , Any ]) -> None :
@@ -77,6 +78,7 @@ def __init__(
7778 session : ClientSession | None = None ,
7879 ssl : bool | None = False ,
7980 verify_ssl : bool | None = True ,
81+ keep_keys : bool = False ,
8082 ):
8183 """Create a SagemCom client.
8284
@@ -85,11 +87,13 @@ def __init__(
8587 :param password: the password for your Sagemcom router
8688 :param authentication_method: the auth method of your Sagemcom router
8789 :param session: use a custom session, for example to configure the timeout
90+ :param keep_keys: return response keys as originally written (no snake_case conversion)
8891 """
8992 self .host = host
9093 self .username = username
9194 self .authentication_method = authentication_method
9295 self .password = password
96+ self .keep_keys = keep_keys
9397 self ._current_nonce = None
9498 self ._password_hash = self .__generate_hash (password )
9599 self .protocol = "https" if ssl else "http"
@@ -191,7 +195,7 @@ def __get_response(self, response, index=0):
191195
192196 return value
193197
194- def __get_response_value (self , response , index = 0 ):
198+ def __get_response_value (self , response , index = 0 , keep_keys : bool | None = None ):
195199 """Retrieve response value from value."""
196200 try :
197201 value = self .__get_response (response , index )["value" ]
@@ -200,8 +204,9 @@ def __get_response_value(self, response, index=0):
200204 except IndexError :
201205 value = None
202206
203- # Rewrite result to snake_case
204- if value is not None :
207+ # Rewrite result to snake_case unless keep_keys is requested
208+ should_keep = keep_keys if keep_keys is not None else self .keep_keys
209+ if value is not None and not should_keep :
205210 value = humps .decamelize (value )
206211
207212 return value
@@ -335,6 +340,8 @@ async def login(self):
335340 raise LoginTimeoutException (
336341 "Login request timed-out. This could be caused by using the wrong encryption method, or using a (non) SSL connection."
337342 ) from exception
343+ except (ClientConnectorError , ClientOSError ) as exception :
344+ raise LoginConnectionException ("Unable to connect to the device. Please check the host address." ) from exception
338345
339346 data = self .__get_response (response )
340347
@@ -552,6 +559,22 @@ async def get_port_mappings(self) -> list[PortMapping]:
552559 max_tries = 1 ,
553560 on_backoff = retry_login ,
554561 )
562+ async def get_logs (self ) -> str :
563+ """Retrieve system logs from Sagemcom F@st device."""
564+ actions = {
565+ "id" : 0 ,
566+ "method" : "getVendorLogDownloadURI" ,
567+ "xpath" : urllib .parse .quote ("Device/DeviceInfo/VendorLogFiles/VendorLogFile[@uid='1']" ),
568+ }
569+
570+ response = await self .__api_request_async ([actions ], False )
571+ log_path = response ["reply" ]["actions" ][0 ]["callbacks" ][0 ]["parameters" ]["uri" ]
572+
573+ log_uri = f"{ self .protocol } ://{ self .host } { log_path } "
574+ log_response = await self .session .get (log_uri )
575+
576+ return await log_response .text ()
577+
555578 async def reboot (self ):
556579 """Reboot Sagemcom F@st device."""
557580 action = {
@@ -565,3 +588,32 @@ async def reboot(self):
565588 data = self .__get_response_value (response )
566589
567590 return data
591+
592+ async def run_speed_test (self , block_traffic : bool = False ):
593+ """Run Speed Test on Sagemcom F@st device."""
594+ actions = [
595+ {
596+ "id" : 0 ,
597+ "method" : "speedTestClient" ,
598+ "xpath" : "Device/IP/Diagnostics/SpeedTest" ,
599+ "parameters" : {"BlockTraffic" : block_traffic },
600+ }
601+ ]
602+ return await self .__api_request_async (actions , False )
603+
604+ async def get_speed_test_results (self ) -> list [SpeedTestResult ]:
605+ """Retrieve Speed Test results from Sagemcom F@st device."""
606+ ret = await self .get_value_by_xpath ("Device/IP/Diagnostics/SpeedTest" )
607+ history = ret ["speed_test" ]["history" ]
608+ if history :
609+ timestamps = (int (k ) for k in history ["timestamp" ].split ("," ))
610+ server_address = history ["selected_server_address" ].split ("," )
611+ block_traffic = history ["block_traffic" ].split ("," )
612+ latency = history ["latency" ].split ("," )
613+ upload = (float (k ) for k in history ["upload" ].split ("," ))
614+ download = (float (k ) for k in history ["download" ].split ("," ))
615+ results = [
616+ SpeedTestResult (* data ) for data in zip (timestamps , server_address , block_traffic , latency , upload , download , strict = True )
617+ ]
618+ return results
619+ return []
0 commit comments