#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
#![allow(clippy::redundant_field_names)]
#![forbid(unsafe_code)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid API URL: cannot be a valid base")]
InvalidBase(String),
#[error("invalid API URL")]
InvalidUrl(#[from] url::ParseError),
#[error("request error")]
Request(#[from] reqwest::Error),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AirData {
pub timestamp: DateTime<Utc>,
pub score: u8,
pub dew_point: f32,
#[serde(rename = "temp")]
pub temperature: f32,
#[serde(rename = "humid")]
pub humidity: f32,
#[serde(rename = "abs_humid")]
pub absolute_humidity: f32,
pub co2: u32,
#[serde(rename = "co2_est")]
pub estimated_co2: u32,
#[serde(rename = "co2_est_baseline")]
pub estimated_co2_baseline: u32,
pub voc: u32,
pub voc_baseline: u32,
pub voc_h2_raw: u32,
pub voc_ethanol_raw: u32,
pub pm25: u32,
#[serde(rename = "pm10_est")]
pub estimated_pm10: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LedConfig {
pub mode: String,
pub brightness: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DeviceConfig {
#[serde(rename = "device_uuid")]
pub device_id: String,
pub wifi_mac: String,
pub ssid: String,
pub ip: String,
pub netmask: String,
pub gateway: String,
#[serde(rename = "fw_version")]
pub firmware_version: String,
pub timezone: String,
pub display: String,
pub led: LedConfig,
pub voc_feature_set: u32,
}
#[derive(Debug)]
pub struct Awair {
api_base: url::Url,
http: reqwest::blocking::Client,
}
impl Awair {
pub fn new(api_base: &str) -> Result<Self, Error> {
let api_base = url::Url::parse(api_base)?;
if api_base.cannot_be_a_base() {
return Err(Error::InvalidBase(api_base.into()));
}
Ok(Self {
api_base,
http: reqwest::blocking::Client::new(),
})
}
pub fn poll(&self) -> Result<AirData, Error> {
let latest = self.api_base.join("/air-data/latest")?;
Ok(self
.http
.get(latest)
.send()?
.error_for_status()?
.json::<AirData>()?)
}
pub fn config(&self) -> Result<DeviceConfig, Error> {
let config = self.api_base.join("/settings/config/data")?;
Ok(self
.http
.get(config)
.send()?
.error_for_status()?
.json::<DeviceConfig>()?)
}
}