diff --git a/Cargo.lock b/Cargo.lock index 9ce6d5e..91a8394 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + [[package]] name = "autocfg" version = "1.1.0" @@ -1056,7 +1062,10 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" name = "weather_exporter" version = "0.1.0" dependencies = [ + "anyhow", "bme280", + "byteorder", + "embedded-hal", "linux-embedded-hal", "tokio", "warp", diff --git a/Cargo.toml b/Cargo.toml index 6668e1f..4603701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,8 @@ description = "A Prometheus exporter for weather data." [dependencies] bme280 = "0.4.4" linux-embedded-hal = "0.4.0-alpha.2" +embedded-hal = "1.0.0-alpha.7" tokio = { version = "1.23.0", features = ["macros", "rt"] } warp = "0.3.3" +byteorder = "1.4.3" +anyhow = "1.0.68" diff --git a/src/main.rs b/src/main.rs index 38464b1..46dc0b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,110 @@ -use bme280; -use linux_embedded_hal::{I2cdev, Delay}; -use std::thread; -use std::time::Duration; +use bme280::{self, Measurements}; +use linux_embedded_hal::{I2cdev, Delay, I2CError}; +use embedded_hal::i2c::blocking::I2c; +use byteorder::{LittleEndian, ByteOrder}; +use std::sync::{Arc, Mutex}; +use anyhow::{Result, anyhow}; +use warp::Filter; -fn main() { +struct LightSensor { + addr: u8, + reg: u8, + bus: I2cdev, +} - let loop_time = Duration::from_secs(5); +impl LightSensor { + fn new(bus: I2cdev, addr: u8, port: u8) -> Self { + Self { bus, addr, reg: port + 0x10 } + } - let i2c = I2cdev::new("/dev/i2c-1") - .expect("Could not open i2c bus"); - - let mut sensor = bme280::i2c::BME280::new_primary(i2c); - - sensor.init(&mut Delay).expect("Could not initialize BME280 sensor"); - - loop { - let baro = sensor.measure(&mut Delay).expect("Could not perform measurement"); - println!("H {}%, P {} PA, T {}°C", baro.humidity, baro.pressure, baro.temperature); - thread::sleep(loop_time); + fn measure(&mut self) -> Result { + self.bus.write(self.addr, &[self.reg])?; + //thread::sleep(Duration::from_millis(10)); + let mut buf = [0; 2]; + self.bus.write_read(self.addr, &[self.reg], &mut buf[..])?; + Ok(LittleEndian::read_u16(&buf)) } } + +struct Station { + light: LightSensor, + bme280: bme280::i2c::BME280, +} + +impl Station { + fn new() -> Result { + + let i2c = I2cdev::new("/dev/i2c-1") + .map_err(|e| anyhow!("Could not open i2c bus: {}", e))?; + + // TODO properly share the device across the two consumers. Probably need to implement I2cdev for RefCell or something + // for now just open it twice :/ + let i2c2 = I2cdev::new("/dev/i2c-1") + .map_err(|e| anyhow!("Could not open second i2c bus: {}", e))?; + + let mut bme280 = bme280::i2c::BME280::new_primary(i2c); + bme280.init(&mut Delay).expect("Could not initialize BME280 sensor"); + + let light = LightSensor::new(i2c2, 0x08, 0x00); + + Ok(Self { light, bme280 }) + } + + fn measure(&mut self) -> Result<(Measurements, u16)> { + let baro = self.bme280.measure(&mut Delay) + .map_err(|e| anyhow!("Cannot measure bme280: {:?}", e))?; + let lux = self.light.measure() + .map_err(|e| anyhow!("Cannot measure light: {:?}", e))?; + Ok((baro, lux)) + } + + pub fn scrape(&mut self) -> String { + + let ( Measurements { temperature, pressure, humidity, .. }, lux) = match self.measure() { + Err(e) => { + return format!("# Error measuring: {:?}", e) + }, + Ok(v) => v, + }; + + let lux = lux as f64 / 1000f64; + + eprintln!("Scraped data: T={temperature}°C P={pressure}Pa H={humidity}% L={lux}"); + + format!( +"# HELP weather_temperature_celsius Temperature in °C +# TYPE weather_temperature_celsius gauge +weather_temperature_celsius {temperature} +# HELP weather_humidity_percent Humidity percentage +# TYPE weather_humidity_percent gauge +weather_humidity_percent {humidity} +# HELP weather_pressure_pascals Atmospheric pressure in Pascals +# TYPE weather_pressure_pascals gauge +weather_pressure_pascals {pressure} +# HELP weather_illumination_relative Relative illumination as a fraction of the maximum +# TYPE weather_illumination_relative gauge +weather_illumination_relative {lux} +") + + } + +} + +#[tokio::main(flavor="current_thread")] +async fn main() -> Result<()> { + + let home = format!("

Weather Station v0.1

"); + let home: &'static str = Box::leak(home.into_boxed_str()); + + let station = Station::new()?; + let station = Arc::new(Mutex::new(station)); + + let filter = warp::path!("metrics").map(move || station.lock().unwrap().scrape()) + .or(warp::path!().map(move || { warp::reply::html(home) })); + + eprintln!("Starting web server."); + Ok(warp::serve(filter).run(([0,0,0,0], 9073)).await) + + +}