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 {
    /// Nest API token.
    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(())
}

/// 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,
    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,
}