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::db::Db;
use crate::reading::{Message, Reading, Type};
use crate::services::Service;
use crate::threading;
use crate::value::Value;
use crate::Result;
use bus::Bus;
use chrono::Local;
use crossbeam_channel::Sender;
use eventsource::reqwest::Client;
use rouille::url::Url;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

const URL: &str = "https://developer-api.nest.com";

pub struct Nest {
    service_id: String,
    token: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Settings {
    /// Nest API token.
    token: String,
}

impl Nest {
    pub fn new(service_id: &str, settings: &Settings) -> Nest {
        Nest {
            service_id: service_id.into(),
            token: settings.token.clone(),
        }
    }
}

impl Service for Nest {
    fn spawn(self: Box<Self>, _db: Arc<Mutex<Db>>, tx: &Sender<Message>, _rx: &mut Bus<Message>) -> Result<()> {
        let tx = tx.clone();
        threading::spawn(format!("my-iot::nest:{}", &self.service_id), move || loop {
            let client = Client::new(Url::parse_with_params(URL, &[("auth", &self.token)]).unwrap());
            for event in client {
                if let Ok(event) = event {
                    if let Some(event_type) = event.event_type {
                        if event_type == "put" {
                            self.send_readings(&serde_json::from_str(&event.data).unwrap(), &tx)
                                .unwrap();
                        }
                    }
                }
            }
        })?;
        Ok(())
    }
}

impl Nest {
    fn send_readings(&self, event: &NestEvent, tx: &Sender<Message>) -> Result<()> {
        let now = Local::now();

        for (id, thermostat) in event.data.devices.thermostats.iter() {
            tx.try_send(Message {
                type_: Type::Actual,
                reading: Reading {
                    sensor: format!("{}::thermostat::{}::ambient_temperature", &self.service_id, &id),
                    value: Value::Celsius(thermostat.ambient_temperature_c),
                    timestamp: now,
                },
            })?;
            tx.try_send(Message {
                type_: Type::Actual,
                reading: Reading {
                    sensor: format!("{}::thermostat::{}::humidity", &self.service_id, &id),
                    value: Value::Rh(thermostat.humidity),
                    timestamp: now,
                },
            })?;
        }

        for (id, camera) in event.data.devices.cameras.iter() {
            tx.try_send(Message {
                type_: Type::Actual,
                reading: Reading {
                    sensor: format!("{}::camera::{}::snapshot_url", &self.service_id, &id),
                    value: Value::ImageUrl(camera.snapshot_url.clone()),
                    timestamp: now,
                },
            })?;
        }

        Ok(())
    }
}

/// Server-side `put` event.
#[derive(Deserialize, Debug)]
struct NestEvent {
    data: NestData,
}

#[derive(Deserialize, Debug)]
struct NestData {
    devices: NestDevices,
    // TODO: structures.
}

#[derive(Deserialize, Debug)]
struct NestDevices {
    thermostats: HashMap<String, NestThermostat>,
    cameras: HashMap<String, NestCamera>,
    // TODO: smoke_co_alarms
}

#[derive(Deserialize, Debug)]
struct NestThermostat {
    ambient_temperature_c: f64,
    humidity: f64,
}

#[derive(Deserialize, Debug)]
struct NestCamera {
    snapshot_url: String,
}