netatmo_api_rs/
lib.rs

1use lazy_static::lazy_static;
2use std::fmt;
3use std::sync::RwLock;
4
5mod types;
6mod utils;
7
8const ENV_VAR_CLIENT_ID: &str = "NETATMO_CLIENT_ID";
9const ENV_VAR_CLIENT_SECRET: &str = "NETATMO_CLIENT_SECRET";
10const ENV_VAR_USERNAME: &str = "NETATMO_USERNAME";
11const ENV_VAR_PASSWORD: &str = "NETATMO_PASSWORD";
12
13const _AUTH_REQ : &str = "https://api.netatmo.com/oauth2/token";
14const _GET_STATIONS_DATA : &str = "https://api.netatmo.com/api/getstationsdata";
15
16#[derive(Clone, Default)]
17pub struct Credentials {
18    pub client_id: String,
19    pub client_secret: String,
20    pub username: String,
21    pub password: String
22}
23
24#[derive(Clone, Default)]
25pub struct Authentication { 
26    client_id: String,
27    client_secret: String,
28    access_token: String,
29    refresh_token: String,
30    scope: String,
31    expiration: u64
32}
33
34lazy_static! {
35    static ref CREDENTIALS: RwLock<Credentials> = RwLock::new(Credentials{
36        client_id: "".to_string(),
37        client_secret: "".to_string(),
38        username: "".to_string(),
39        password: "".to_string(),
40    });
41}
42
43lazy_static! {
44    static ref LATEST_AUTH: RwLock<Authentication> = RwLock::new(Authentication {
45        client_id: "".to_string(),
46        client_secret: "".to_string(),
47        access_token: "".to_string(),
48        refresh_token: "".to_string(),
49        scope: "read_station read_camera access_camera write_camera read_presence access_presence write_presence read_thermostat write_thermostat read_smokedetector".to_string(),
50        expiration: 0
51    });
52}
53
54impl Credentials {
55    pub fn from_params(client_id: String, client_secret: String, username: String, password: String) -> Credentials {
56        let mut cred = CREDENTIALS.write().unwrap();
57        cred.client_id = client_id.clone();
58        cred.client_secret = client_secret.clone();
59        cred.username = username.clone();
60        cred.password = password.clone();
61
62        return Credentials { client_id: client_id, client_secret: client_secret, username: username, password: password};
63    }
64
65    pub fn from_custom_path(env_client_id_name: String, env_client_secret_name: String, env_username_name: String, env_password_name: String) -> Credentials {
66        let mut cred = CREDENTIALS.write().unwrap();
67        cred.client_id = utils::get_var_from_path(&env_client_id_name);
68        cred.client_secret = utils::get_var_from_path(&env_client_secret_name);
69        cred.username = utils::get_var_from_path(&env_username_name);
70        cred.password = utils::get_var_from_path(&env_password_name);
71
72        return Credentials {
73            client_id: utils::get_var_from_path(&env_client_id_name),
74            client_secret: utils::get_var_from_path(&env_client_secret_name),
75            username: utils::get_var_from_path(&env_username_name),
76            password: utils::get_var_from_path(&env_password_name),
77        };
78    }
79
80    pub fn from_path() -> Credentials {
81        let mut cred = CREDENTIALS.write().unwrap();
82        cred.client_id = utils::get_var_from_path(&ENV_VAR_CLIENT_ID);
83        cred.client_secret = utils::get_var_from_path(&ENV_VAR_CLIENT_SECRET);
84        cred.username = utils::get_var_from_path(&ENV_VAR_USERNAME);
85        cred.password = utils::get_var_from_path(&ENV_VAR_PASSWORD);
86
87        return Credentials {
88            client_id: utils::get_var_from_path(&ENV_VAR_CLIENT_ID),
89            client_secret: utils::get_var_from_path(&ENV_VAR_CLIENT_SECRET),
90            username: utils::get_var_from_path(&ENV_VAR_USERNAME),
91            password: utils::get_var_from_path(&ENV_VAR_PASSWORD),
92        };
93    }
94}
95
96impl fmt::Display for Credentials {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        write!(f, "Credentials: client_id:{}\t client_secret:{}\t username:{}\t password:{}", self.client_id, self.client_secret, self.username, self.password)
99    }
100}
101
102impl fmt::Display for Authentication {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        write!(f, "Authentication: client_id:{}\t client_secret:{}\t access_token:{}\t refresh_token:{}\t scope:{}\t expiration:{}", self.client_id, self.client_secret, self.access_token, self.refresh_token, self.scope, self.expiration)
105    }
106}
107
108pub fn auth(creds: Credentials) -> Authentication {
109    let now = utils::get_current_timestamp();
110    let current_auth = LATEST_AUTH.read().unwrap();
111
112    if current_auth.expiration < now {
113        println!("Auth should be renew");
114        let client = reqwest::blocking::Client::new();
115        let res = client.post(_AUTH_REQ)
116            .form(&[ ("grant_type", "password"), ("client_id", &creds.client_id), ("client_secret", &creds.client_secret), ("username", &creds.username), ("password", &creds.password), ("scope", &LATEST_AUTH.read().unwrap().scope) ])
117            .send();
118    
119        match res {
120            Ok(res) => {
121                if res.status() == 200 {
122                    let api_resp: types::AuthApiResponse = res.json().unwrap();
123                    std::mem::drop(current_auth);
124                    let mut auth = LATEST_AUTH.write().unwrap();
125                    auth.client_id = creds.client_id.clone();
126                    auth.client_secret = creds.client_secret.clone();
127                    auth.access_token = api_resp.access_token.clone();
128                    auth.refresh_token = api_resp.refresh_token.clone();
129                    auth.expiration = api_resp.expire_in + now;
130                    return Authentication{ 
131                        client_id: creds.client_id,
132                        client_secret: creds.client_secret,
133                        access_token: api_resp.access_token, 
134                        refresh_token: api_resp.refresh_token,
135                        scope: api_resp.scope.join(" "),
136                        expiration: api_resp.expire_in + now
137                    };
138                } else {
139                    println!("Status: {}", res.status());
140                    let api_error_resp: types::AuthApiErrorResponse = res.json().unwrap();
141                    println!("API error: {} : {}", api_error_resp.error, api_error_resp.error_description);
142                    return Authentication::default();
143                }
144            },
145            Err(err) => {
146                println!("Error: {}", err);
147                return Authentication::default();
148            }
149        }
150    } else {
151        println!("Auth should not be renew");
152        return Authentication{ 
153            client_id: current_auth.client_id.clone(),
154            client_secret: current_auth.client_secret.clone(),
155            access_token: current_auth.access_token.clone(), 
156            refresh_token: current_auth.refresh_token.clone(),
157            scope: current_auth.scope.clone(),
158            expiration: current_auth.expiration.clone()
159        };
160    }
161}
162
163pub fn get_stations_data() -> types::ApiResponseStationsRoot {
164    let creds = CREDENTIALS.read().unwrap();
165    let current_auth = auth(Credentials{
166        client_id: creds.client_id.clone(),
167        client_secret: creds.client_secret.clone(),
168        username:creds.username.clone(),
169        password:creds.password.clone()
170    });
171
172    let client = reqwest::blocking::Client::new();
173    let res = client.post(_GET_STATIONS_DATA).form(&[ ("access_token", current_auth.access_token) ]).send();
174
175    match res {
176        Ok(res) => {
177            if res.status() == 200 {
178                let api_resp: types::ApiResponseStationsRoot = res.json().unwrap();
179                return api_resp;
180            } else {
181                println!("Status: {}", res.status());
182                let api_error_resp: types::AuthApiErrorResponse = res.json().unwrap();
183                println!("API error: {} : {}", api_error_resp.error, api_error_resp.error_description);
184                return types::ApiResponseStationsRoot::default();
185            }
186        },
187        Err(err) => {
188            println!("Error: {}", err);
189            return types::ApiResponseStationsRoot::default();
190        }
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    #[test]
197    fn it_works() {
198        assert_eq!(2 + 2, 4);
199    }
200}