1use futures::executor;
3use std::time;
4
5pub mod errors;
6pub mod types;
7
8const CLIENT_ID: &str = "https://halcyon.casa";
9
10#[derive(Debug)]
11pub struct HomeAssistantAPI {
12 instance_urls: Vec<String>,
13 token: Option<Token>,
14 client: reqwest::Client,
15 webhook_id: Option<String>,
16 cloudhook_url: Option<String>,
17 remote_ui_url: Option<String>,
18}
19
20#[derive(Debug, Clone)]
21pub enum Token {
22 Oauth(OAuthToken),
23 LongLived(LongLivedToken),
24}
25
26#[derive(Debug, Clone)]
27pub struct OAuthToken {
28 token: String,
29 token_expiration: std::time::SystemTime,
30 refresh_token: String,
31}
32
33#[derive(Debug, Clone)]
34pub struct LongLivedToken {
35 token: String,
36}
37
38impl HomeAssistantAPI {
39 pub fn new(instance_urls: Vec<String>) -> Self {
40 return Self {
41 instance_urls: instance_urls,
42 client: reqwest::Client::new(),
43 token: None,
44 webhook_id: None,
45 cloudhook_url: None,
46 remote_ui_url: None,
47 }
48 }
49
50 pub fn auth_token(
51 &mut self,
52 oauth_token: String,
53 refresh_token: String,
54 token_expiration: u64,
55 ) -> Result<(), errors::Error> {
56 let oauth = OAuthToken {
57 token: oauth_token,
58 refresh_token: refresh_token,
59 token_expiration: (time::UNIX_EPOCH + time::Duration::from_secs(token_expiration)),
60 };
61 self.token = Some(Token::Oauth(oauth));
62 return Ok(());
63 }
64
65 pub fn auth_authorization_code(
66 &mut self,
67 authorization_code: String,
68 ) -> Result<(), errors::Error> {
69 let client = reqwest::Client::new();
70 let response = client
71 .post(self.instance_urls[0].as_str())
72 .query(&[
73 ("grant_type", "authorization_code"),
74 ("client_id", CLIENT_ID),
75 ("code", authorization_code.as_str()),
76 ])
77 .send();
78 match executor::block_on(response) {
79 Ok(response) => {
80 match response.error_for_status() {
81 Ok(response) => {
82 match executor::block_on(response.json::<types::AuthorizationCode>()) {
83 Ok(response_data) => {
84 let token_time = (time::SystemTime::now()
85 + time::Duration::from_secs(1800))
86 .duration_since(time::SystemTime::UNIX_EPOCH)
87 .unwrap()
88 .as_secs();
89
90 return self.auth_token(
91 response_data.access_token,
92 response_data.refresh_token,
93 token_time,
94 );
95 }
96 Err(err) => {
97 return Err(errors::Error::from(err));
98 }
99 };
100 }
101 Err(err) => {
102 return Err(errors::Error::from(err));
103 }
104 };
105 }
106 Err(err) => {
107 return Err(errors::Error::from(err));
108 }
109 };
110 }
111
112 pub fn auth_long_lived_token(
113 &mut self,
114 long_lived_token: String,
115 ) -> Result<(), errors::Error> {
116 let token = LongLivedToken {
117 token: long_lived_token,
118 };
119 self.token = Some(Token::LongLived(token));
120 return Ok(());
121 }
122
123 fn get_token(&self) -> Result<String, errors::Error> {
124 let token = self.token.clone();
125 match token {
126 Some(token) => {
127 return match token {
128 Token::Oauth(oauth) => Ok(oauth.refresh_token),
129 Token::LongLived(long_lived) => Ok(long_lived.token)
130 }
131 },
132 None => return Err(errors::Error::NoAuth())
133 }
134
135 }
136
137 pub fn need_refresh(&self) -> bool {
138 match &self.token {
139 Some(token) => {
140 match token {
141 Token::Oauth(oauth) => {
142 match time::SystemTime::now().duration_since(oauth.token_expiration) {
143 Ok(sec_left) => {
144 if sec_left <= time::Duration::from_secs(10) {
145 return false;
146 } else {
147 return true;
148 }
149 }
150 Err(_) => return false,
151 };
152 },
153 Token::LongLived(_) => {
154 return false;
155 }
156 }
157 },
158 None => {
159 return false;
160 }
161 }
162 }
163
164 pub fn refresh_token(&mut self) -> Result<(), errors::Error> {
165 let token = self.token.clone(); let refresh_token: String;
167 match token {
168 Some(token) => {
169 refresh_token = match token {
170 Token::Oauth(oauth) => oauth.refresh_token,
171 Token::LongLived(_) => {
172 return Err(errors::Error::Refresh());
173 }
174 };
175 },
176 None => return Err(errors::Error::NoAuth())
177 }
178
179
180 let response = self
181 .client
182 .post(&self.instance_urls[0])
183 .query(&[
184 ("grant_type", "refresh_token"),
185 ("client_id", CLIENT_ID),
186 ("refresh_token", refresh_token.as_str()),
187 ])
188 .send();
189 match executor::block_on(response) {
190 Ok(response) => {
191 match response.error_for_status() {
192 Ok(response) => {
193 match executor::block_on(response.json::<types::RefreshToken>()) {
194 Ok(response_data) => {
195 let oauth = OAuthToken {
196 token: response_data.access_token,
197 token_expiration: time::SystemTime::now()
198 + time::Duration::from_secs(response_data.expires_in),
199 refresh_token: refresh_token.to_string(),
200 };
201 self.token = Some(Token::Oauth(oauth));
202 return Ok(());
203 }
204 Err(err) => {
205 return Err(errors::Error::from(err));
206 }
207 };
208 }
209 Err(err) => {
210 return Err(errors::Error::from(err));
211 }
212 };
213 }
214 Err(err) => {
215 return Err(errors::Error::from(err));
216 }
217 };
218 }
219
220 pub fn register_device(
221 &mut self,
222 device_data: types::DeviceRegistrationRequest,
223 ) -> Result<(), errors::Error> {
224 if self.need_refresh() {
225 self.refresh_token().unwrap()
226 }
227 let url = format!(
228 "{}/api/mobile_app/registrations",
229 self.instance_urls[0].as_str()
230 );
231
232 println!("{:?}", self.get_token());
233
234 let token = match self.get_token() {
235 Ok(token) => {
236 token
237 },
238 Err(err) => return Err(err)
239 };
240
241 let response = self
242 .client
243 .post(&url)
244 .bearer_auth(token)
245 .json(&device_data);
246
247 match executor::block_on(response.send()) {
248 Ok(response) => {
249 match response.error_for_status() {
250 Ok(response) => {
251 match executor::block_on(
252 response.json::<types::DeviceRegistrationResponse>(),
253 ) {
254 Ok(response_data) => {
255 self.webhook_id = Some(response_data.webhook_id);
256 self.cloudhook_url = response_data.cloudhook_url;
257 self.remote_ui_url = response_data.remote_ui_url;
258 return Ok(());
259 }
260 Err(err) => {
261 return Err(errors::Error::from(err));
262 }
263 };
264 }
265 Err(err) => {
266 return Err(errors::Error::from(err));
267 }
268 };
269 }
270 Err(err) => {
271 return Err(errors::Error::from(err));
272 }
273 }
274 }
275
276 pub fn register_sensor(
277 &mut self,
278 sensor_data: types::SensorRegistrationData,
279 ) -> Result<(), errors::Error> {
280 if self.need_refresh() {
281 self.refresh_token().unwrap()
282 }
283 let register_sensor = types::SensorRegistrationRequest {
284 data: sensor_data,
285 r#type: String::from("register_sensor"),
286 };
287 match &self.webhook_id {
288 Some(webhook_id) => {
289 let url = format!(
290 "{}/api/webhook/{}",
291 self.instance_urls[0].as_str(),
292 webhook_id
293 );
294
295 let token = match self.get_token() {
296 Ok(token) => {
297 token
298 },
299 Err(err) => return Err(err)
300 };
301
302 let response = self
303 .client
304 .post(&url)
305 .bearer_auth(token)
306 .json(®ister_sensor)
307 .send();
308 match executor::block_on(response) {
309 Ok(response) => {
310 match response.error_for_status() {
311 Ok(_) => {
312 return Ok(());
313 }
314 Err(err) => {
315 return Err(errors::Error::from(err));
316 }
317 };
318 }
319 Err(err) => {
320 return Err(errors::Error::from(err));
321 }
322 };
323 }
324 None => {
325 return Err(errors::Error::Config(String::from("Missing Webhook ID")));
326 }
327 }
328 }
329
330 pub fn update_sensor(
331 &mut self,
332 sensor_data: types::SensorUpdateData,
333 ) -> Result<(), errors::Error> {
334 if self.need_refresh() {
335 self.refresh_token().unwrap()
336 }
337 let register_sensor = types::SensorUpdateRequest {
338 data: sensor_data,
339 r#type: String::from("update_sensor_states"),
340 };
341 match &self.webhook_id {
342 Some(webhook_id) => {
343 let url = format!(
344 "{}/api/webhook/{}",
345 self.instance_urls[0].as_str(),
346 webhook_id
347 );
348
349 let token = match self.get_token() {
350 Ok(token) => {
351 token
352 },
353 Err(err) => return Err(err)
354 };
355
356 let response = self
357 .client
358 .post(&url)
359 .bearer_auth(token)
360 .json(®ister_sensor)
361 .send();
362
363 match executor::block_on(response) {
364 Ok(response) => {
365 match response.error_for_status() {
366 Ok(response) => {
367 match executor::block_on(response.json::<types::RefreshToken>()) {
368 Ok(_response_data) => {
369 return Ok(());
370 }
371 Err(err) => {
372 return Err(errors::Error::from(err));
373 }
374 };
375 }
376 Err(err) => {
377 return Err(errors::Error::from(err));
378 }
379 };
380 }
381 Err(err) => {
382 return Err(errors::Error::from(err));
383 }
384 };
385 }
386 None => {
387 return Err(errors::Error::Config(String::from("Missing Webhook ID")));
388 }
389 }
390 }
391}