Example #1
0
 def _safe_reboot(self, max_retries: int=3) -> str:
     t0 = time.time()
     try_count = 1
     while try_count <= max_retries+1 or max_retries < 0:
         self._logger.debug("Starting reboot attempt "+str(try_count)+" of " +
                            (str(max_retries+1) if max_retries >= 0 else "<unlimited>")+"...")
         self._logger.debug("Requesting reboot...")
         self._reboot()
         sleep_length = max(0, round(self._reboot_delay / 1000.0))
         self._logger.debug("Waiting "+str(sleep_length)+" seconds while device reboots...")
         time.sleep(sleep_length)
         self._logger.debug("Verifying device is back up and responsive after reboot...")
         if self._check():
             break
         # This reboot attempt failed.  Try again or give up:
         if try_count >= max_retries+1:
             raise linkplayctl.APIException("Failed to bring device back up after " + str(max_retries) +
                                            " reboot attempts. Giving up.")
         self._logger.debug("Device is not responding after reboot. Trying again...")
         try_count = try_count + 1
     # Reboot was successful
     elapsed_time = "{:,}".format(round((time.time()-t0), 1))
     if try_count > 1:
         self._logger.info("Device required " + str(try_count) + " reboots to become responsive." +
                           " Elapsed time: "+elapsed_time+"s")
     else:
         self._logger.debug("Device is responsive after first reboot. Elapsed time: " + elapsed_time + "s")
     return "OK"
Example #2
0
 def _reboot(self) -> str:
     """Internal, non-blocking method for performing reboots"""
     response = self._send("reboot")
     if response.status_code != 200 or response.content.decode("utf-8") != "OK":
         raise linkplayctl.APIException("Failed to reboot: " +
                                        "Status "+str(response.status_code)+" Content: "+response.content)
     return response.content.decode("utf-8")
Example #3
0
 def prompt_off(self) -> str:
     """Disable voice prompts and notifications"""
     self._logger.info("Turning voice prompts off...")
     response = self._send("PromptDisable")
     if response.status_code != 200:
         raise linkplayctl.APIException("Failed to disable prompts: Status code="+str(response.status_code))
     return response.content.decode("utf-8")
Example #4
0
 def shutdown(self) -> str:
     """Request an immediate device shutdown"""
     self._logger.info("Requesting shutdown...")
     response = self._send("getShutdown")
     if response.status_code != 200:
         raise linkplayctl.APIException("Failed to shutdown: Status code="+str(response.status_code))
     return response.content.decode("utf-8")
Example #5
0
 def _validate_preset(self, number: object) -> int:
     """Internal method to validate and return preset as an integer between 1 and 6, inclusive"""
     try:
         number = int(number)
         if number < 1 or number > 6:
             raise linkplayctl.APIException
     except (ValueError, linkplayctl.APIException):
         raise linkplayctl.APIException("Preset number must be an integer between 1 and 6, inclusive")
     return number
Example #6
0
 def wifi_status(self) -> str:
     """Get the current status of the WiFi connection"""
     self._logger.info("Retrieving WiFi connection status...")
     inverse_wifi_statuses = {v: k for k, v in self._wifi_statuses.items()}
     response = self._send("wlanGetConnectState").content.decode("utf-8")
     try:
         return inverse_wifi_statuses[response]
     except KeyError:
         raise linkplayctl.APIException("Received unrecognized wifi status: '"+str(response)+"'")
Example #7
0
    def wifi_auth(self, auth_type: str = None, new_pass: str = None):
        """Get or set the network authentication parameters

            :returns: str Device response (usually "OK") if set, dict of auth values if get
        """
        if auth_type is None:
            self._logger.info("Retrieving WiFi authentication information...")
            return {k: v for (k, v) in self._device_info().items() if (k in ["securemode", "auth", "encry", "psk"])}
        self._logger.info("Setting WiFi authentication type to '"+str(auth_type)+"'"
                          +(" with pass '"+str(new_pass)+"'" if new_pass else "")+"...")
        try:
            auth_value = self._auth_types[auth_type]
        except KeyError:
            raise linkplayctl.APIException("Authentication type must be one of ["+", ".join(self._auth_types.keys())+"]")
        if auth_value and not new_pass:
            raise linkplayctl.APIException("Authentication type '"+str(auth_type)+"' requires a non-empty password")
        response = self._send("setNetwork:"+str(auth_value)+":"+str(new_pass) if new_pass is not None else "")
        self._logger.debug("Authentication set.  Device is rebooting...")
        return response.content.decode("utf-8")
Example #8
0
 def _json_decode(self, s: object) -> object:
     """Decode the given object as JSON"""
     try:
         s = s.content
     except AttributeError: pass
     try:
         s = s.decode("utf-8")
     except UnicodeDecodeError: pass
     try:
         return json.JSONDecoder().decode(str(s))
     except ValueError:  # json.JSONDecodeError is better for > 3.4
         raise linkplayctl.APIException("Expected JSON from API, got: '"+str(s)+"'")
Example #9
0
 def name(self, name: str = None) -> str:
     """Get or set the device name to be used for services such as Airplay"""
     if not name:
         self._logger.info("Retrieving device name...")
         return self._device_info().get("DeviceName")
     self._logger.info("Setting device name to '"+str(name)+"'...")
     if not isinstance(name, str) or not name:
         raise AttributeError("Device name must be a non-empty string")
     response = self._send("setDeviceName:"+name)
     if response.status_code != 200:
         raise linkplayctl.APIException("Failed to set device name to '"+name+"'")
     return response.content.decode("utf-8")
Example #10
0
 def _loop(self, mode: str = None) -> str:
     """Internal method to get or set the current looping mode (includes shuffle and repeat setting)"""
     if mode is None:
         inverse_loop_modes = {v: k for k, v in self._loop_modes.items()}
         self._logger.debug("Requesting current loop mode...")
         loopval = self._player_info().get('loop')
         self._logger.debug("Current loop mode value is '"+str(loopval)+"'. Mapping to mode names...")
         try:
             return inverse_loop_modes[int(loopval)]
         except KeyError:
             raise linkplayctl.APIException("Received unknown loop mode value '"+str(loopval)+"' from device")
     try:
         value = self._loop_modes[str(mode)]
         self._logger.debug("Setting loop mode to '"+str(mode)+"' [value: '"+str(value)+"']...")
     except KeyError:
         try:
             value = int(mode) if int(mode) in self._loop_modes.values() else -1
         except:
             raise linkplayctl.APIException("Cannot set unknown loop mode '"+str(mode)+"'")
         inverse_loop_modes = {v: k for k, v in self._loop_modes.items()}
         self._logger.debug("Setting loop mode to '"+str(inverse_loop_modes[value])+"' [value: '" +str(value)+"']...")
     return self._send('setPlayerCmd:loopmode:'+str(value)).content.decode("utf-8")
Example #11
0
 def equalizer(self, mode: str=None) -> str:
     """Get or set the equalizer mode"""
     if mode is None:
         self._logger.info("Retrieving current equalizer setting...")
         inverse_eq_modes = {v: k for k, v in self._equalizer_modes.items()}
         value = self._json_decode(self._send("getEqualizer"))
         try:
             mode = inverse_eq_modes[value]
         except KeyError:
             raise linkplayctl.APIException("Received unknown equalizer mode value '"+str(value)+"'")
         self._logger.info("Received equalizer mode '"+mode+"' (value "+str(value)+")")
         return mode
     self._logger.info("Setting equalizer to '"+str(mode)+"'...")
     try:
         mode_value = self._equalizer_modes[mode]
     except KeyError:
         eq_values = ' '.join(self._equalizer_modes)
         raise AttributeError("Equalizer mode must be one of ["+eq_values+"], not '"+str(mode)+"'")
     self._logger.info("Equalizer mode '" + str(mode)+"' maps to value "+str(mode_value))
     response = self._send("setPlayerCmd:equalizer:"+str(mode_value))
     if response.status_code != 200:
         raise linkplayctl.APIException("Failed to set equalizer to mode '"+str(mode)+"' (value '"+str(mode_value)+"'")
     return response.content.decode("utf-8")
Example #12
0
 def quiet_reboot(self) -> str:
     """Reboot the device quietly, i.e., without boot jingle. Returns when complete, usually ~120 seconds."""
     t0 = time.time()
     sleep_length = max(0, round(self._reboot_delay/1000.0))
     self._logger.info("Requesting quiet reboot...")
     if self._reboot_delay > 5000:
         self._logger.info("Note: This request may take "+str(sleep_length)+" seconds or more to finish")
     self._logger.debug("Getting current volume...")
     old_volume = self._volume()
     self._logger.debug("Saving current volume '"+str(old_volume)+"' and setting new volume to '" +
                        str(self._quiet_reboot_volume)+"'...")
     self._volume(self._quiet_reboot_volume)
     self._logger.debug("Verifying volume has been correctly set to minimum...")
     if int(self._volume()) != self._quiet_reboot_volume:
         raise linkplayctl.APIException("Failed to set volume to minimum before quiet reboot")
     self._safe_reboot()
     self._logger.debug("Restoring previous volume '" + str(old_volume) + "'...")
     self._volume(old_volume)
     self._logger.debug("Confirming new volume is set to '" + str(old_volume) + "'...")
     if old_volume != int(self._volume()):
         raise linkplayctl.APIException("Failed to restore old volume '"+str(old_volume)+"' after reboot")
     elapsed_time = "{:,}".format(round((time.time()-t0)*1000, 1))
     self._logger.debug("Quiet reboot complete.  Elapsed time: "+str(elapsed_time)+"ms")
     return "OK"
Example #13
0
    def _volume(self, value: object = None):
        """ Internal method to get/set volume to an absolute value between 0 and 100 or a relative value -100 to +100

            :returns: int volume, or "OK" on volume set
        """
        if value is None:
            return int(self._player_info().get("vol"))
        try:
            if isinstance(value, str) and (value.startswith('+') or value.startswith('-')):
                self._logger.debug("Adjusting volume by " + str(value) + ". Getting old volume...")
                new_volume = max(0, min(100, self._volume()+int(math.floor(float(value)))))
                self._logger.debug("Adjusting volume "+str(value)+" to "+str(new_volume)+"...")
            else:
                new_volume = max(0, min(100, int(math.floor(float(value)))))
                self._logger.debug("Setting volume to " + str(int(new_volume)))
        except ValueError:
            raise AttributeError("Volume must be between 0 and 100 or -100 to +100, inclusive, not '"+str(value)+"'")
        response = self._send("setPlayerCmd:vol:" + str(new_volume))
        if response.status_code != 200:
            raise linkplayctl.APIException("Failed to set volume to '"+str(new_volume)+"'")
        return response.content.decode("utf-8")