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}