tapo 0.9.0

Unofficial Tapo API Client. Works with TP-Link Tapo smart devices. Tested with light bulbs (L510, L520, L530, L535, L610, L630), light strips (L900, L920, L930), plugs (P100, P105, P110, P110M, P115), power strips (P300, P304M, P306, P316M), hubs (H100), switches (S200B, S200D, S210) and sensors (KE100, T100, T110, T300, T310, T315).
Documentation
/// Response Error from the Tapo API.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum TapoResponseError {
    #[error("Device error {code}: {kind}")]
    DeviceError { code: i64, kind: &'static str },
    #[error("Unexpected empty result")]
    EmptyResult,
    #[error("HTTP error {status_code}: {description}")]
    HttpError {
        status_code: u16,
        description: String,
    },
    #[error("Response error: {description}")]
    ResponseError { description: String },
    #[error("Unauthorized: {kind}: {description}")]
    Unauthorized {
        kind: &'static str,
        description: String,
    },
}

impl TapoResponseError {
    pub(crate) fn session_expired(kind: &'static str) -> Self {
        Self::Unauthorized {
            kind,
            description: "Session has expired. Re-authentication is required.".to_string(),
        }
    }

    pub(crate) fn hash_mismatch() -> Self {
        Self::Unauthorized {
            kind: "HASH_MISMATCH",
            description: "The device response did not match the challenge issued by the library. Make sure that your email and password are correct -— both are case-sensitive. Before adding a new device, disconnect any existing TP-Link/Tapo devices on the network. The TP-Link Simple Setup (TSS) protocol, which shares credentials from previously configured devices, may interfere with authentication. If the problem continues, perform a factory reset on the new device and add it again with no other TP-Link devices active during setup.".to_string(),
        }
    }
}

/// Tapo API Client Error.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
    /// Response Error from the Tapo API.
    #[error(transparent)]
    Tapo(TapoResponseError),
    /// Validation Error of a provided field.
    #[error("Validation: {field} {message}")]
    Validation {
        /// The field that failed validation.
        field: String,
        /// The validation error message.
        message: String,
    },
    /// Serialization/Deserialization Error.
    #[error(transparent)]
    Serde(#[from] serde_json::Error),
    /// HTTP Error.
    #[error(transparent)]
    Http(#[from] reqwest::Error),
    /// Device not found
    #[error("Device not found")]
    DeviceNotFound,
    /// Other Error. This is a catch-all for errors that don't fit into the other categories.
    #[error(transparent)]
    Other(#[from] anyhow::Error),
}

#[cfg(feature = "python")]
impl From<Error> for pyo3::PyErr {
    fn from(err: Error) -> pyo3::PyErr {
        pyo3::exceptions::PyException::new_err(format!("{:?}", err))
    }
}

/// Discovery Error. Wraps an error that occurred while discovering a specific device.
#[derive(thiserror::Error, Debug)]
#[error("Failed to discover device at {ip}: {source}")]
pub struct DiscoveryError {
    /// The IP address of the device that failed to be discovered.
    pub ip: String,
    /// The underlying error.
    pub source: Error,
}

#[cfg(feature = "python")]
impl From<DiscoveryError> for pyo3::PyErr {
    fn from(err: DiscoveryError) -> pyo3::PyErr {
        pyo3::exceptions::PyException::new_err(format!("{:?}", err))
    }
}