1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::prelude::*;
use crate::supervisor;
use crate::Result;
use chrono::{DateTime, Local};
use crossbeam_channel::Sender;
use eventsource::reqwest::Client;
use failure::format_err;
use rouille::url::Url;
use serde::Deserialize;
use std::collections::HashMap;
const URL: &str = "https://developer-api.nest.com";
#[derive(Deserialize, Debug, Clone)]
pub struct Settings {
token: String,
}
pub fn spawn(service_id: &str, settings: &Settings, tx: &Sender<Message>) -> Result<Vec<Sender<Message>>> {
let service_id = service_id.to_string();
let settings = settings.clone();
let tx = tx.clone();
supervisor::spawn(
format!("my-iot::nest::{}", &service_id),
tx.clone(),
move || -> Result<()> {
let client = Client::new(Url::parse_with_params(URL, &[("auth", &settings.token)]).unwrap());
for event in client {
if let Ok(event) = event {
if let Some(event_type) = event.event_type {
if event_type == "put" {
send_readings(&service_id, &serde_json::from_str(&event.data)?, &tx)?;
}
}
}
}
Err(format_err!("Event source client is unexpectedly exhausted"))
},
)?;
Ok(vec![])
}
fn send_readings(service_id: &str, event: &NestEvent, tx: &Sender<Message>) -> Result<()> {
let now = Local::now();
for (id, thermostat) in event.data.devices.thermostats.iter() {
tx.send(
Composer::new(format!("{}::thermostat::{}::ambient_temperature", service_id, &id))
.value(Value::Celsius(thermostat.ambient_temperature_c))
.timestamp(now)
.into(),
)?;
tx.send(
Composer::new(format!("{}::thermostat::{}::humidity", service_id, &id))
.value(Value::Rh(thermostat.humidity))
.timestamp(now)
.into(),
)?;
}
for (id, camera) in event.data.devices.cameras.iter() {
tx.send(
Composer::new(format!("{}::camera::{}::snapshot_url", service_id, &id))
.value(Value::ImageUrl(camera.snapshot_url.clone()))
.timestamp(now)
.into(),
)?;
if let Some(ref event) = camera.last_event {
tx.send(
Composer::new(format!("{}::camera::{}::animated_image_url", service_id, &id))
.value(Value::ImageUrl(event.animated_image_url.clone()))
.timestamp(event.start_time)
.into(),
)?;
}
}
Ok(())
}
#[derive(Deserialize, Debug)]
struct NestEvent {
data: NestData,
}
#[derive(Deserialize, Debug)]
struct NestData {
devices: NestDevices,
}
#[derive(Deserialize, Debug)]
struct NestDevices {
thermostats: HashMap<String, NestThermostat>,
cameras: HashMap<String, NestCamera>,
}
#[derive(Deserialize, Debug)]
struct NestThermostat {
ambient_temperature_c: f64,
humidity: f64,
}
#[derive(Deserialize, Debug)]
struct NestCamera {
snapshot_url: String,
last_event: Option<NestCameraLastEvent>,
}
#[derive(Deserialize, Debug)]
struct NestCameraLastEvent {
has_sound: bool,
has_motion: bool,
has_person: bool,
start_time: DateTime<Local>,
urls_expire_time: DateTime<Local>,
animated_image_url: String,
}